Skip to content
This repository was archived by the owner on Jul 11, 2023. It is now read-only.

Commit 477c78e

Browse files
committed
handle paginated cards
1 parent d480bc1 commit 477c78e

6 files changed

+182
-15
lines changed

dist/index.js

+48-6
Original file line numberDiff line numberDiff line change
@@ -1901,30 +1901,46 @@ content {
19011901
}
19021902
`.trim();
19031903

1904-
const projectColumnFields = `
1904+
// prettier-ignore
1905+
const projectColumnFields = ({ after = false } = {}) =>
1906+
`
19051907
id
19061908
name
19071909
url
19081910
project {
19091911
name
19101912
}
1911-
cards(first: 50, archivedStates: [NOT_ARCHIVED]) {
1913+
cards(first: 50, archivedStates: [NOT_ARCHIVED]${after ? 'after: $after' : ''}) {
19121914
nodes {
19131915
${projectCardFields}
19141916
}
1917+
pageInfo {
1918+
hasNextPage
1919+
endCursor
1920+
}
19151921
}
19161922
`.trim();
19171923

19181924
const GET_PROJECT_COLUMNS = `
19191925
query($sourceColumnIds: [ID!]!, $targetColumnId: ID!) {
19201926
sourceColumns: nodes(ids: $sourceColumnIds) {
19211927
... on ProjectColumn {
1922-
${projectColumnFields}
1928+
${projectColumnFields()}
19231929
}
19241930
}
19251931
targetColumn: node(id: $targetColumnId) {
19261932
... on ProjectColumn {
1927-
${projectColumnFields}
1933+
${projectColumnFields()}
1934+
}
1935+
}
1936+
}
1937+
`.trim();
1938+
1939+
const GET_SINGLE_PROJECT_COLUMN = `
1940+
query($id: ID!, $after: String) {
1941+
column: node(id: $id) {
1942+
... on ProjectColumn {
1943+
${projectColumnFields({ after: true })}
19281944
}
19291945
}
19301946
}
@@ -1964,6 +1980,7 @@ mutation deleteProjectCard($cardId: ID!) {
19641980

19651981
module.exports = {
19661982
GET_PROJECT_COLUMNS,
1983+
GET_SINGLE_PROJECT_COLUMN,
19671984
ADD_PROJECT_CARD,
19681985
MOVE_PROJECT_CARD,
19691986
DELETE_PROJECT_CARD
@@ -5948,6 +5965,29 @@ ${columnReferences.join('\n')}
59485965
`.trim();
59495966
}
59505967

5968+
// Paginate project cards from all columns if/as needed
5969+
async function paginateColumnCards(api, columns) {
5970+
for (let i = 0; i < columns.length; i += 1) {
5971+
const originalColumn = columns[i];
5972+
5973+
let currentColumn = originalColumn;
5974+
while (currentColumn.cards.pageInfo.hasNextPage) {
5975+
core.info(
5976+
`paginating ${currentColumn.project.name}:${currentColumn.name} after ${currentColumn.cards.pageInfo.endCursor}`
5977+
);
5978+
5979+
// eslint-disable-next-line no-await-in-loop
5980+
const { column } = await api(queries.GET_SINGLE_PROJECT_COLUMN, {
5981+
id: currentColumn.id,
5982+
after: currentColumn.cards.pageInfo.endCursor
5983+
});
5984+
5985+
originalColumn.cards.nodes.push(...column.cards.nodes);
5986+
currentColumn = column;
5987+
}
5988+
}
5989+
}
5990+
59515991
// Find a card in an array of cards based on it's linked content, or it's note.
59525992
// Returns an array of [found card, index of found card]
59535993
function findCard(card, cards) {
@@ -6018,14 +6058,16 @@ async function run() {
60186058
const sourceColumnIds = utils.getInputList(core.getInput('source_column_id', { required: true }));
60196059
const targetColumnId = core.getInput('target_column_id', { required: true });
60206060

6021-
const response = await api(queries.GET_PROJECT_COLUMNS, {
6061+
const { sourceColumns, targetColumn } = await api(queries.GET_PROJECT_COLUMNS, {
60226062
sourceColumnIds,
60236063
targetColumnId
60246064
});
60256065

6066+
// paginate to gather all cards if needed
6067+
await paginateColumnCards(api, [...sourceColumns, targetColumn]);
6068+
60266069
// apply user supplied filters to cards from the source column and mirror the
60276070
// target column based on the remaining filters
6028-
const { sourceColumns, targetColumn } = response;
60296071
const sourceCards = sourceColumns.flatMap(column => {
60306072
return applyFilters(column.cards.nodes, [...Object.values(utils.filters)]);
60316073
});

src/graphql.js

+21-4
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,46 @@ content {
2323
}
2424
`.trim();
2525

26-
const projectColumnFields = `
26+
// prettier-ignore
27+
const projectColumnFields = ({ after = false } = {}) =>
28+
`
2729
id
2830
name
2931
url
3032
project {
3133
name
3234
}
33-
cards(first: 50, archivedStates: [NOT_ARCHIVED]) {
35+
cards(first: 50, archivedStates: [NOT_ARCHIVED]${after ? 'after: $after' : ''}) {
3436
nodes {
3537
${projectCardFields}
3638
}
39+
pageInfo {
40+
hasNextPage
41+
endCursor
42+
}
3743
}
3844
`.trim();
3945

4046
const GET_PROJECT_COLUMNS = `
4147
query($sourceColumnIds: [ID!]!, $targetColumnId: ID!) {
4248
sourceColumns: nodes(ids: $sourceColumnIds) {
4349
... on ProjectColumn {
44-
${projectColumnFields}
50+
${projectColumnFields()}
4551
}
4652
}
4753
targetColumn: node(id: $targetColumnId) {
4854
... on ProjectColumn {
49-
${projectColumnFields}
55+
${projectColumnFields()}
56+
}
57+
}
58+
}
59+
`.trim();
60+
61+
const GET_SINGLE_PROJECT_COLUMN = `
62+
query($id: ID!, $after: String) {
63+
column: node(id: $id) {
64+
... on ProjectColumn {
65+
${projectColumnFields({ after: true })}
5066
}
5167
}
5268
}
@@ -86,6 +102,7 @@ mutation deleteProjectCard($cardId: ID!) {
86102

87103
module.exports = {
88104
GET_PROJECT_COLUMNS,
105+
GET_SINGLE_PROJECT_COLUMN,
89106
ADD_PROJECT_CARD,
90107
MOVE_PROJECT_CARD,
91108
DELETE_PROJECT_CARD

src/linked-project-columns.js

+27-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,29 @@ ${columnReferences.join('\n')}
2424
`.trim();
2525
}
2626

27+
// Paginate project cards from all columns if/as needed
28+
async function paginateColumnCards(api, columns) {
29+
for (let i = 0; i < columns.length; i += 1) {
30+
const originalColumn = columns[i];
31+
32+
let currentColumn = originalColumn;
33+
while (currentColumn.cards.pageInfo.hasNextPage) {
34+
core.info(
35+
`paginating ${currentColumn.project.name}:${currentColumn.name} after ${currentColumn.cards.pageInfo.endCursor}`
36+
);
37+
38+
// eslint-disable-next-line no-await-in-loop
39+
const { column } = await api(queries.GET_SINGLE_PROJECT_COLUMN, {
40+
id: currentColumn.id,
41+
after: currentColumn.cards.pageInfo.endCursor
42+
});
43+
44+
originalColumn.cards.nodes.push(...column.cards.nodes);
45+
currentColumn = column;
46+
}
47+
}
48+
}
49+
2750
// Find a card in an array of cards based on it's linked content, or it's note.
2851
// Returns an array of [found card, index of found card]
2952
function findCard(card, cards) {
@@ -94,14 +117,16 @@ async function run() {
94117
const sourceColumnIds = utils.getInputList(core.getInput('source_column_id', { required: true }));
95118
const targetColumnId = core.getInput('target_column_id', { required: true });
96119

97-
const response = await api(queries.GET_PROJECT_COLUMNS, {
120+
const { sourceColumns, targetColumn } = await api(queries.GET_PROJECT_COLUMNS, {
98121
sourceColumnIds,
99122
targetColumnId
100123
});
101124

125+
// paginate to gather all cards if needed
126+
await paginateColumnCards(api, [...sourceColumns, targetColumn]);
127+
102128
// apply user supplied filters to cards from the source column and mirror the
103129
// target column based on the remaining filters
104-
const { sourceColumns, targetColumn } = response;
105130
const sourceCards = sourceColumns.flatMap(column => {
106131
return applyFilters(column.cards.nodes, [...Object.values(utils.filters)]);
107132
});

test/fixtures/get-project-columns.json

+10-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
"name": "source project"
99
},
1010
"cards": {
11-
"nodes": []
11+
"nodes": [],
12+
"pageInfo": {
13+
"hasNextPage": false,
14+
"endCursor": null
15+
}
1216
}
1317
}
1418
],
@@ -20,7 +24,11 @@
2024
"name": "target project"
2125
},
2226
"cards": {
23-
"nodes": []
27+
"nodes": [],
28+
"pageInfo": {
29+
"hasNextPage": false,
30+
"endCursor": null
31+
}
2432
}
2533
}
2634
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"column": {
3+
"id": 1,
4+
"name": "source column",
5+
"url": "https://example.com/projects/1/columns/1",
6+
"project": {
7+
"name": "source project"
8+
},
9+
"cards": {
10+
"nodes": [],
11+
"pageInfo": {
12+
"hasNextPage": false,
13+
"endCursor": null
14+
}
15+
}
16+
}
17+
}

test/linked-project-columns.test.js

+59-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ describe('linked-project-columns', () => {
1717
const getColumnsFixture = readFileSync(resolvePath(__dirname, './fixtures/get-project-columns.json'), {
1818
encoding: 'utf8'
1919
});
20+
const getSingleColumnFixture = readFileSync(resolvePath(__dirname, './fixtures/get-single-project-column.json'), {
21+
encoding: 'utf8'
22+
});
2023
const deleteCardFixture = readFileSync(resolvePath(__dirname, './fixtures/delete-project-card.json'), {
2124
encoding: 'utf8'
2225
});
@@ -28,6 +31,7 @@ describe('linked-project-columns', () => {
2831
});
2932

3033
let getColumnsResponse;
34+
let getSingleColumnResponse;
3135

3236
beforeEach(() => {
3337
process.env = {
@@ -45,8 +49,10 @@ describe('linked-project-columns', () => {
4549
sinon.stub(octokit.graphql, 'defaults').returns(api);
4650

4751
getColumnsResponse = JSON.parse(getColumnsFixture);
52+
getSingleColumnResponse = JSON.parse(getSingleColumnFixture);
4853

4954
api.withArgs(queries.GET_PROJECT_COLUMNS).resolves(getColumnsResponse);
55+
api.withArgs(queries.GET_SINGLE_PROJECT_COLUMN).resolves(getSingleColumnResponse);
5056
api.withArgs(queries.DELETE_PROJECT_CARD).callsFake((query, input) => {
5157
const response = JSON.parse(deleteCardFixture);
5258
response.deleteProjectCard.deletedCardId = input.cardId;
@@ -483,6 +489,54 @@ describe('linked-project-columns', () => {
483489
expect(api.getCall(0).args[0]).toContain('archivedStates: [NOT_ARCHIVED]');
484490
});
485491

492+
it('gathers additional pages of cards for source columns', async () => {
493+
getColumnsResponse.sourceColumns[0].cards.pageInfo.hasNextPage = true;
494+
getColumnsResponse.sourceColumns[0].cards.pageInfo.endCursor = 'abc';
495+
getSingleColumnResponse.column.cards.nodes.push({ id: 1, note: '1' }, { id: 2, note: '2' });
496+
497+
await run();
498+
499+
expect(core.warning.callCount).toEqual(0);
500+
expect(core.setFailed.callCount).toEqual(0);
501+
expect(api.callCount).toEqual(7);
502+
// call 0 -> get columns
503+
expect(api.getCall(1).args).toEqual([
504+
queries.GET_SINGLE_PROJECT_COLUMN,
505+
{
506+
id: getColumnsResponse.sourceColumns[0].id,
507+
after: 'abc'
508+
}
509+
]);
510+
// call 2 -> add automation note
511+
expect(api.getCall(3).args).toEqual([queries.ADD_PROJECT_CARD, { columnId: 2, note: '1' }]);
512+
expect(api.getCall(4).args).toEqual([queries.MOVE_PROJECT_CARD, { columnId: 2, cardId: 201, afterCardId: 200 }]);
513+
expect(api.getCall(5).args).toEqual([queries.ADD_PROJECT_CARD, { columnId: 2, note: '2' }]);
514+
expect(api.getCall(6).args).toEqual([queries.MOVE_PROJECT_CARD, { columnId: 2, cardId: 202, afterCardId: 201 }]);
515+
});
516+
517+
it('gathers additional pages of cards for the target column', async () => {
518+
getColumnsResponse.targetColumn.cards.pageInfo.hasNextPage = true;
519+
getColumnsResponse.targetColumn.cards.pageInfo.endCursor = 'abc';
520+
getSingleColumnResponse.column.cards.nodes.push({ id: 1, note: '1' }, { id: 2, note: '2' });
521+
522+
await run();
523+
524+
expect(core.warning.callCount).toEqual(0);
525+
expect(core.setFailed.callCount).toEqual(0);
526+
expect(api.callCount).toEqual(5);
527+
// call 0 -> get columns
528+
expect(api.getCall(1).args).toEqual([
529+
queries.GET_SINGLE_PROJECT_COLUMN,
530+
{
531+
id: getColumnsResponse.targetColumn.id,
532+
after: 'abc'
533+
}
534+
]);
535+
expect(api.getCall(2).args).toEqual([queries.DELETE_PROJECT_CARD, { cardId: 2 }]);
536+
expect(api.getCall(3).args).toEqual([queries.DELETE_PROJECT_CARD, { cardId: 1 }]);
537+
// call 4 -> add automation note
538+
});
539+
486540
describe('with multiple source columns', () => {
487541
const secondSourceColumnId = 'second';
488542

@@ -496,7 +550,11 @@ describe('linked-project-columns', () => {
496550
name: 'source project'
497551
},
498552
cards: {
499-
nodes: []
553+
nodes: [],
554+
pageInfo: {
555+
hasNextPage: false,
556+
endCursor: null
557+
}
500558
}
501559
});
502560
});

0 commit comments

Comments
 (0)