@@ -7,9 +7,24 @@ const board = require('../shared/project-board-client.cjs');
77 */
88module . exports = async function transitionStatus ( { github, context, core } ) {
99 const includeCurrentPullRequest = 'true' === ( process . env . INPUT_INCLUDE_CURRENT_PULL_REQUEST ?? '' ) . toLowerCase ( ) ;
10- const fromStatus = process . env . INPUT_FROM_STATUS ;
10+ const sourceStatuses = [
11+ ...( process . env . INPUT_FROM_STATUSES ?? '' ) . split ( ',' ) ,
12+ process . env . INPUT_FROM_STATUS ?? '' ,
13+ ]
14+ . map ( ( status ) => status . trim ( ) )
15+ . filter ( ( status , index , statuses ) => '' !== status && statuses . indexOf ( status ) === index ) ;
1116 const toStatus = process . env . INPUT_TO_STATUS ;
1217
18+ core . setOutput ( 'source-statuses' , sourceStatuses . join ( ',' ) ) ;
19+
20+ if ( 0 === sourceStatuses . length ) {
21+ core . info ( 'No source project statuses were provided. Skipping status transition.' ) ;
22+ core . setOutput ( 'moved-count' , '0' ) ;
23+ core . setOutput ( 'skipped-count' , '0' ) ;
24+
25+ return ;
26+ }
27+
1328 const project = await board . loadConfiguredProject (
1429 github ,
1530 context . repo . owner ,
@@ -18,6 +33,8 @@ module.exports = async function transitionStatus({ github, context, core }) {
1833
1934 if ( ! project ) {
2035 core . info ( 'No configured GitHub Project V2 was resolved. Skipping status transition.' ) ;
36+ core . setOutput ( 'moved-count' , '0' ) ;
37+ core . setOutput ( 'skipped-count' , '0' ) ;
2138
2239 return ;
2340 }
@@ -27,107 +44,105 @@ module.exports = async function transitionStatus({ github, context, core }) {
2744
2845 if ( ! statusField || ! targetOption ) {
2946 core . info ( `Project "${ project . title } " does not expose the expected target status "${ toStatus } ".` ) ;
47+ core . setOutput ( 'moved-count' , '0' ) ;
48+ core . setOutput ( 'skipped-count' , '0' ) ;
3049
3150 return ;
3251 }
3352
34- const result = await github . graphql (
35- `query($owner: String!, $repo: String!, $pullRequestNumber: Int!) {
36- repository(owner: $owner, name: $repo) {
37- issues(first: 100, orderBy: {field: UPDATED_AT, direction: DESC}, states: CLOSED) {
38- nodes {
39- number
40- projectItems(first: 20) {
41- nodes {
42- id
43- project {
44- ... on ProjectV2 {
45- id
46- }
47- }
48- fieldValues(first: 20) {
49- nodes {
50- __typename
51- ... on ProjectV2ItemFieldSingleSelectValue {
52- field {
53- ... on ProjectV2SingleSelectField {
54- name
55- }
56- }
57- name
53+ const loadProjectItems = async ( ) => {
54+ const items = [ ] ;
55+ let cursor = null ;
56+
57+ do {
58+ const result = await github . graphql (
59+ `query($project: ID!, $cursor: String) {
60+ node(id: $project) {
61+ ... on ProjectV2 {
62+ items(first: 100, after: $cursor) {
63+ pageInfo {
64+ hasNextPage
65+ endCursor
5866 }
59- }
60- }
61- }
62- }
63- }
64- }
65- pullRequests(first: 100, orderBy: {field: UPDATED_AT, direction: DESC}, states: [MERGED, CLOSED]) {
66- nodes {
67- number
68- projectItems(first: 20) {
69- nodes {
70- id
71- project {
72- ... on ProjectV2 {
73- id
74- }
75- }
76- fieldValues(first: 20) {
77- nodes {
78- __typename
79- ... on ProjectV2ItemFieldSingleSelectValue {
80- field {
81- ... on ProjectV2SingleSelectField {
82- name
67+ nodes {
68+ id
69+ content {
70+ __typename
71+ ... on Issue {
72+ number
73+ repository {
74+ nameWithOwner
75+ }
76+ title
77+ url
78+ }
79+ ... on PullRequest {
80+ number
81+ repository {
82+ nameWithOwner
83+ }
84+ title
85+ url
8386 }
8487 }
85- name
86- }
87- }
88- }
89- }
90- }
91- }
92- }
93- pullRequest(number: $pullRequestNumber) {
94- number
95- projectItems(first: 20) {
96- nodes {
97- id
98- project {
99- ... on ProjectV2 {
100- id
101- }
102- }
103- fieldValues(first: 20) {
104- nodes {
105- __typename
106- ... on ProjectV2ItemFieldSingleSelectValue {
107- field {
108- ... on ProjectV2SingleSelectField {
109- name
88+ fieldValues(first: 20) {
89+ nodes {
90+ __typename
91+ ... on ProjectV2ItemFieldSingleSelectValue {
92+ field {
93+ ... on ProjectV2SingleSelectField {
94+ name
95+ }
96+ }
97+ name
98+ }
99+ }
110100 }
111101 }
112- name
113102 }
114103 }
115104 }
116- }
117- }
118- }
119- }
120- }` ,
121- {
122- owner : context . repo . owner ,
123- repo : context . repo . repo ,
124- pullRequestNumber : context . payload . pull_request ?. number ?? 0 ,
125- } ,
126- ) ;
105+ }` ,
106+ {
107+ project : project . id ,
108+ cursor,
109+ } ,
110+ ) ;
111+
112+ const page = result . node ?. items ;
113+
114+ items . push ( ...( page ?. nodes ?? [ ] ) ) ;
115+ cursor = page ?. pageInfo ?. hasNextPage ? page . pageInfo . endCursor : null ;
116+ } while ( null !== cursor ) ;
117+
118+ return items ;
119+ } ;
120+
121+ const formatLabel = ( item ) => {
122+ const content = item . content ;
123+
124+ if ( 'Issue' === content ?. __typename ) {
125+ return `Issue #${ content . number } ` ;
126+ }
127+
128+ if ( 'PullRequest' === content ?. __typename ) {
129+ return `PR #${ content . number } ` ;
130+ }
131+
132+ return `Project item ${ item . id } ` ;
133+ } ;
134+
135+ const belongsToCurrentRepository = ( item ) => {
136+ const repository = item . content ?. repository ?. nameWithOwner ;
137+
138+ return `${ context . repo . owner } /${ context . repo . repo } ` === repository ;
139+ } ;
127140
128141 const moveToStatus = async ( item , label ) => {
129- if ( ! item || board . getExistingFieldValue ( item , 'Status' ) !== fromStatus ) {
130- return ;
142+ const currentStatus = board . getExistingFieldValue ( item , 'Status' ) ;
143+
144+ if ( ! sourceStatuses . includes ( currentStatus ) ) {
145+ return false ;
131146 }
132147
133148 await board . updateSingleSelectField (
@@ -138,27 +153,35 @@ module.exports = async function transitionStatus({ github, context, core }) {
138153 targetOption . id ,
139154 ) ;
140155
141- core . info ( `${ label } moved to ${ toStatus } .` ) ;
156+ core . info ( `${ label } moved from ${ currentStatus } to ${ toStatus } .` ) ;
157+
158+ return true ;
142159 } ;
143160
144161 if ( includeCurrentPullRequest ) {
145- await moveToStatus (
146- board . findProjectItem ( result . repository . pullRequest ?. projectItems ?. nodes ?? [ ] , project . id ) ,
147- `Pull request #${ context . payload . pull_request . number } ` ,
148- ) ;
162+ core . info ( 'The include-current-pull-request input is kept for compatibility; project item pagination already includes the current pull request when it is on the board.' ) ;
149163 }
150164
151- for ( const pullRequest of result . repository . pullRequests . nodes ) {
152- await moveToStatus (
153- board . findProjectItem ( pullRequest . projectItems . nodes , project . id ) ,
154- `PR #${ pullRequest . number } ` ,
155- ) ;
156- }
165+ let movedCount = 0 ;
166+ let skippedCount = 0 ;
157167
158- for ( const issue of result . repository . issues . nodes ) {
159- await moveToStatus (
160- board . findProjectItem ( issue . projectItems . nodes , project . id ) ,
161- `Issue #${ issue . number } ` ,
162- ) ;
168+ for ( const item of await loadProjectItems ( ) ) {
169+ if ( ! belongsToCurrentRepository ( item ) ) {
170+ skippedCount ++ ;
171+
172+ continue ;
173+ }
174+
175+ if ( await moveToStatus ( item , formatLabel ( item ) ) ) {
176+ movedCount ++ ;
177+
178+ continue ;
179+ }
180+
181+ skippedCount ++ ;
163182 }
183+
184+ core . info ( `${ movedCount } project item(s) moved to ${ toStatus } ; ${ skippedCount } inspected item(s) skipped.` ) ;
185+ core . setOutput ( 'moved-count' , String ( movedCount ) ) ;
186+ core . setOutput ( 'skipped-count' , String ( skippedCount ) ) ;
164187} ;
0 commit comments