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

Commit f21cab7

Browse files
authored
Merge pull request #9 from jonabc/more-filters
Add additional filters on content state and ignore comments
2 parents bc88672 + cd53e05 commit f21cab7

6 files changed

Lines changed: 165 additions & 9 deletions

File tree

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,17 @@ The `${{ secrets.GITHUB_TOKEN }}` token can be used only when all project column
5050
2. `repo` to access information in private repositories
5151
3. `user` to access information in user repositories (if needed)
5252

53-
### Filtering cards mirroring
53+
### Filtering mirrored cards
54+
55+
##### Manual filtering
56+
57+
`<!-- mirror ignore -->`
58+
59+
Filters cards based on the existence of a mirror ignore comment in the card content. The comment can be added to card notes, or to linked issue or PR bodies.
60+
61+
When added to cards in the source project column, the cards will be ignored and not added to the target column. When added to cards in the target project column, the cards will be ignored and will not be removed from the target column.
62+
63+
##### Filtering on action inputs
5464

5565
Cards can be filtered from mirroring by specifying additional inputs on the action workflow.
5666

@@ -71,3 +81,9 @@ Label filters use exact, case sensitive comparisons to determine whether to mirr
7181
Filters mirrored cards based on the displayed card content. Issue/PR titles is evaluated when they are linked as cards, otherwise the card's note text is used.
7282

7383
Content filters use partial, case insensitive comparisons when determining which cards to mirror. Content filter inputs can contain multiple filters separated by commas (`'first, second'`), and will be mirrored if any content matches are found (i.e. `OR` logic). Content filters containing commas must be wrapped in quotes (`'first, second, "matching, with a comma"'`)
84+
85+
**state_filter**
86+
87+
Filters mirrored cards based on linked issue or PR state. Note cards do not have a state, and will never be filtered based on this input. Must be one of:
88+
- `open`
89+
- `closed`

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ inputs:
2121
content_filter:
2222
description: "Filter to cards with matching note content or issue/PR title"
2323
required: false
24+
state_filter:
25+
description: "Filter to cards with matching issue or PR state, either 'open' or 'closed'"
26+
required: false
2427
runs:
2528
using: 'node12'
2629
main: 'dist/index.js'

src/filters.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,46 @@ function filterByLabel(cards) {
6969
});
7070
}
7171

72+
function filterByState(cards) {
73+
let stateFilter = core.getInput('state_filter', { required: false });
74+
if (!stateFilter) {
75+
return cards;
76+
}
77+
78+
stateFilter = stateFilter.toUpperCase();
79+
return cards.filter(card => {
80+
if (card.content) {
81+
// only include cards for issues and PRs in a matching state
82+
return card.content.state === stateFilter;
83+
}
84+
85+
// don't filter cards that can't be filtered by state
86+
return true;
87+
});
88+
}
89+
90+
const IGNORE_COMMENT = '<!-- mirror ignore -->';
91+
function filterIgnored(cards) {
92+
return cards.filter(card => {
93+
if (card.note) {
94+
// don't include cards that have the ignore comment in the note
95+
return !card.note.includes(IGNORE_COMMENT);
96+
}
97+
98+
if (card.content && card.content.body) {
99+
// don't include cards that have the ignore comment in content body
100+
return !card.content.body.includes(IGNORE_COMMENT);
101+
}
102+
103+
// do not filter cards that can't include ignored stamps
104+
return true;
105+
});
106+
}
107+
72108
module.exports = {
73109
type: filterByType,
74110
content: filterByContent,
75-
label: filterByLabel
111+
label: filterByLabel,
112+
state: filterByState,
113+
ignored: filterIgnored
76114
};

src/graphql.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
const projectCardContentFields = `
22
id
33
title
4+
state
5+
body
46
labels(first: 20) {
57
nodes {
68
name

src/linked-project-columns.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ async function run() {
8989
// target column based on the remaining filters
9090
const { sourceColumn, targetColumn } = response;
9191
const sourceCards = applyFilters(sourceColumn.cards.nodes, [...Object.values(filters)]);
92-
const targetCards = targetColumn.cards.nodes;
92+
const targetCards = applyFilters(targetColumn.cards.nodes, [filters.ignored]);
9393

9494
// prepend the automation note card to the filtered source cards, so that
9595
// it will be created if needed in the target column.

test/linked-project-columns.test.js

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ describe('linked-project-columns', () => {
165165
expect(api.getCall(2).args).toEqual([queries.MOVE_PROJECT_CARD, { columnId: 2, cardId: 201, afterCardId: 200 }]);
166166
});
167167

168-
it('filters mirrored cards to note type', async () => {
168+
it('filters source cards to note type', async () => {
169169
process.env.INPUT_TYPE_FILTER = 'note';
170170
getColumnsResponse.sourceColumn.cards.nodes.push({ id: 1, note: '1' }, { id: 2, content: { id: 1000 } });
171171

@@ -179,7 +179,7 @@ describe('linked-project-columns', () => {
179179
expect(api.getCall(3).args).toEqual([queries.MOVE_PROJECT_CARD, { columnId: 2, cardId: 201, afterCardId: 200 }]);
180180
});
181181

182-
it('filters mirrored cards to content type', async () => {
182+
it('filters source cards to content type', async () => {
183183
process.env.INPUT_TYPE_FILTER = 'content';
184184
getColumnsResponse.sourceColumn.cards.nodes.push({ id: 1, note: '1' }, { id: 2, content: { id: 1000 } });
185185

@@ -193,7 +193,7 @@ describe('linked-project-columns', () => {
193193
expect(api.getCall(3).args).toEqual([queries.MOVE_PROJECT_CARD, { columnId: 2, cardId: 201, afterCardId: 200 }]);
194194
});
195195

196-
it('filters mirrored content cards based on labels', async () => {
196+
it('filters source content cards based on labels', async () => {
197197
process.env.INPUT_LABEL_FILTER = 'label 2';
198198
getColumnsResponse.sourceColumn.cards.nodes.push(
199199
{
@@ -235,7 +235,7 @@ describe('linked-project-columns', () => {
235235
expect(api.getCall(3).args).toEqual([queries.MOVE_PROJECT_CARD, { columnId: 2, cardId: 201, afterCardId: 200 }]);
236236
});
237237

238-
it('does not filter mirrored note cards based on labels', async () => {
238+
it('does not filter source note cards based on labels', async () => {
239239
process.env.INPUT_LABEL_FILTER = '1, 2, other';
240240
getColumnsResponse.sourceColumn.cards.nodes.push({ id: 1, note: '1' });
241241

@@ -249,7 +249,7 @@ describe('linked-project-columns', () => {
249249
expect(api.getCall(3).args).toEqual([queries.MOVE_PROJECT_CARD, { columnId: 2, cardId: 201, afterCardId: 200 }]);
250250
});
251251

252-
it('filters mirrored note cards based on note content', async () => {
252+
it('filters source note cards based on note content', async () => {
253253
process.env.INPUT_CONTENT_FILTER = '1, note 2, other';
254254
getColumnsResponse.sourceColumn.cards.nodes.push(
255255
{
@@ -278,7 +278,7 @@ describe('linked-project-columns', () => {
278278
expect(api.getCall(5).args).toEqual([queries.MOVE_PROJECT_CARD, { columnId: 2, cardId: 202, afterCardId: 201 }]);
279279
});
280280

281-
it('filters mirrored content cards based on title content', async () => {
281+
it('filters source content cards based on title content', async () => {
282282
process.env.INPUT_CONTENT_FILTER = '1, title 2, other';
283283
getColumnsResponse.sourceColumn.cards.nodes.push(
284284
{
@@ -315,4 +315,101 @@ describe('linked-project-columns', () => {
315315
expect(api.getCall(4).args).toEqual([queries.ADD_PROJECT_CARD, { columnId: 2, contentId: 1002 }]);
316316
expect(api.getCall(5).args).toEqual([queries.MOVE_PROJECT_CARD, { columnId: 2, cardId: 202, afterCardId: 201 }]);
317317
});
318+
319+
it('filters source content cards based on state', async () => {
320+
process.env.INPUT_STATE_FILTER = 'open';
321+
getColumnsResponse.sourceColumn.cards.nodes.push(
322+
{
323+
id: 1,
324+
content: {
325+
id: 1001,
326+
state: 'OPEN'
327+
}
328+
},
329+
{
330+
id: 2,
331+
content: {
332+
id: 1002,
333+
state: 'CLOSED'
334+
}
335+
}
336+
);
337+
338+
await run();
339+
340+
expect(core.setFailed.callCount).toEqual(0);
341+
expect(api.callCount).toEqual(4);
342+
// call 0 -> get columns
343+
// call 1 -> add automation note
344+
expect(api.getCall(2).args).toEqual([queries.ADD_PROJECT_CARD, { columnId: 2, contentId: 1001 }]);
345+
expect(api.getCall(3).args).toEqual([queries.MOVE_PROJECT_CARD, { columnId: 2, cardId: 201, afterCardId: 200 }]);
346+
});
347+
348+
it('does not filter source note cards based on state', async () => {
349+
process.env.INPUT_STATE_FILTER = 'open';
350+
getColumnsResponse.sourceColumn.cards.nodes.push({ id: 1, note: 'CLOSED' });
351+
352+
await run();
353+
354+
expect(core.setFailed.callCount).toEqual(0);
355+
expect(api.callCount).toEqual(4);
356+
// call 0 -> get columns
357+
// call 1 -> add automation note
358+
expect(api.getCall(2).args).toEqual([queries.ADD_PROJECT_CARD, { columnId: 2, note: 'CLOSED' }]);
359+
expect(api.getCall(3).args).toEqual([queries.MOVE_PROJECT_CARD, { columnId: 2, cardId: 201, afterCardId: 200 }]);
360+
});
361+
362+
it('filters source content cards with ignore comments', async () => {
363+
getColumnsResponse.sourceColumn.cards.nodes.push({
364+
id: 1,
365+
content: { id: 1001, body: 'test\n<!-- mirror ignore -->\ntest' }
366+
});
367+
368+
await run();
369+
370+
expect(core.setFailed.callCount).toEqual(0);
371+
expect(api.callCount).toEqual(2);
372+
// call 0 -> get columns
373+
// call 1 -> add automation note
374+
// no call to add the ignored item from the source column
375+
});
376+
377+
it('filters source note cards with ignore comments', async () => {
378+
getColumnsResponse.sourceColumn.cards.nodes.push({ id: 1, note: 'test\n<!-- mirror ignore -->\ntest' });
379+
380+
await run();
381+
382+
expect(core.setFailed.callCount).toEqual(0);
383+
expect(api.callCount).toEqual(2);
384+
// call 0 -> get columns
385+
// call 1 -> add automation note
386+
// no call to add the ignored item from the source column
387+
});
388+
389+
it('filters target content cards with ignore comments', async () => {
390+
getColumnsResponse.targetColumn.cards.nodes.push({
391+
id: 1,
392+
content: { id: 1001, body: 'test\n<!-- mirror ignore -->\ntest' }
393+
});
394+
395+
await run();
396+
397+
expect(core.setFailed.callCount).toEqual(0);
398+
expect(api.callCount).toEqual(2);
399+
// call 0 -> get columns
400+
// call 1 -> add automation note
401+
// no call to delete the item from the target column
402+
});
403+
404+
it('filters target note cards with ignore comments', async () => {
405+
getColumnsResponse.targetColumn.cards.nodes.push({ id: 1, note: 'test\n<!-- mirror ignore -->\ntest' });
406+
407+
await run();
408+
409+
expect(core.setFailed.callCount).toEqual(0);
410+
expect(api.callCount).toEqual(2);
411+
// call 0 -> get columns
412+
// call 1 -> add automation note
413+
// no call to delete the item from the target column
414+
});
318415
});

0 commit comments

Comments
 (0)