Skip to content

Commit 5f858e3

Browse files
Bibo-Joshiandig
andauthored
Add only-issue-types option to filter issues by type (#1255)
* Add `only-issue-types` Option to Filter Issues by Type * white-space fix in readme table Co-authored-by: andig <cpuidle@gmail.com> --------- Co-authored-by: andig <cpuidle@gmail.com>
1 parent 3a9db7e commit 5f858e3

File tree

10 files changed

+193
-4
lines changed

10 files changed

+193
-4
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ Every argument is optional.
9898
| [ignore-issue-updates](#ignore-issue-updates) | Override [ignore-updates](#ignore-updates) for issues only | |
9999
| [ignore-pr-updates](#ignore-pr-updates) | Override [ignore-updates](#ignore-updates) for PRs only | |
100100
| [include-only-assigned](#include-only-assigned) | Process only assigned issues | `false` |
101-
| [sort-by](#sort-by) | What to sort issues and PRs by | `created` |
101+
| [sort-by](#sort-by) | What to sort issues and PRs by | `created` |
102+
| [only-issue-types](#only-issue-types) | Only issues with a matching type are processed as stale/closed. | |
102103

103104
### List of output options
104105

@@ -555,6 +556,13 @@ Useful to sort the issues and PRs by the specified field. It accepts `created`,
555556

556557
Default value: `created`
557558

559+
#### only-issue-types
560+
561+
A comma separated list of allowed issue types. Only issues with a matching type will be processed (e.g.: `bug,question`).
562+
563+
If unset (or an empty string), this option will not alter the stale workflow.
564+
565+
Default value: unset
558566

559567
### Usage
560568

__tests__/functions/generate-issue.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export function generateIssue(
1515
isClosed = false,
1616
isLocked = false,
1717
milestone: string | undefined = undefined,
18-
assignees: string[] = []
18+
assignees: string[] = [],
19+
issue_type?: string
1920
): Issue {
2021
return new Issue(options, {
2122
number: id,
@@ -39,6 +40,7 @@ export function generateIssue(
3940
login: assignee,
4041
type: 'User'
4142
};
42-
})
43+
}),
44+
...(issue_type ? {type: {name: issue_type}} : {})
4345
});
4446
}

__tests__/only-issue-types.spec.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import {Issue} from '../src/classes/issue';
2+
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
3+
import {IssuesProcessorMock} from './classes/issues-processor-mock';
4+
import {DefaultProcessorOptions} from './constants/default-processor-options';
5+
import {generateIssue} from './functions/generate-issue';
6+
import {alwaysFalseStateMock} from './classes/state-mock';
7+
8+
describe('only-issue-types option', () => {
9+
test('should only process issues with allowed type', async () => {
10+
const opts: IIssuesProcessorOptions = {
11+
...DefaultProcessorOptions,
12+
onlyIssueTypes: 'bug,question'
13+
};
14+
const TestIssueList: Issue[] = [
15+
generateIssue(
16+
opts,
17+
1,
18+
'A bug',
19+
'2020-01-01T17:00:00Z',
20+
'2020-01-01T17:00:00Z',
21+
false,
22+
false,
23+
[],
24+
false,
25+
false,
26+
undefined,
27+
[],
28+
'bug'
29+
),
30+
generateIssue(
31+
opts,
32+
2,
33+
'A feature',
34+
'2020-01-01T17:00:00Z',
35+
'2020-01-01T17:00:00Z',
36+
false,
37+
false,
38+
[],
39+
false,
40+
false,
41+
undefined,
42+
[],
43+
'feature'
44+
),
45+
generateIssue(
46+
opts,
47+
3,
48+
'A question',
49+
'2020-01-01T17:00:00Z',
50+
'2020-01-01T17:00:00Z',
51+
false,
52+
false,
53+
[],
54+
false,
55+
false,
56+
undefined,
57+
[],
58+
'question'
59+
)
60+
];
61+
const processor = new IssuesProcessorMock(
62+
opts,
63+
alwaysFalseStateMock,
64+
async p => (p === 1 ? TestIssueList : []),
65+
async () => [],
66+
async () => new Date().toDateString()
67+
);
68+
await processor.processIssues(1);
69+
expect(processor.staleIssues.map(i => i.title)).toEqual([
70+
'A bug',
71+
'A question'
72+
]);
73+
});
74+
75+
test('should process all issues if onlyIssueTypes is unset', async () => {
76+
const opts: IIssuesProcessorOptions = {
77+
...DefaultProcessorOptions,
78+
onlyIssueTypes: ''
79+
};
80+
const TestIssueList: Issue[] = [
81+
generateIssue(
82+
opts,
83+
1,
84+
'A bug',
85+
'2020-01-01T17:00:00Z',
86+
'2020-01-01T17:00:00Z',
87+
false,
88+
false,
89+
[],
90+
false,
91+
false,
92+
undefined,
93+
[],
94+
'bug'
95+
),
96+
generateIssue(
97+
opts,
98+
2,
99+
'A feature',
100+
'2020-01-01T17:00:00Z',
101+
'2020-01-01T17:00:00Z',
102+
false,
103+
false,
104+
[],
105+
false,
106+
false,
107+
undefined,
108+
[],
109+
'feature'
110+
)
111+
];
112+
const processor = new IssuesProcessorMock(
113+
opts,
114+
alwaysFalseStateMock,
115+
async p => (p === 1 ? TestIssueList : []),
116+
async () => [],
117+
async () => new Date().toDateString()
118+
);
119+
await processor.processIssues(1);
120+
expect(processor.staleIssues.map(i => i.title)).toEqual([
121+
'A bug',
122+
'A feature'
123+
]);
124+
});
125+
});

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ inputs:
208208
description: 'Only the issues or the pull requests with an assignee will be marked as stale automatically.'
209209
default: 'false'
210210
required: false
211+
only-issue-types:
212+
description: 'Only issues with a matching type are processed as stale/closed. Defaults to `[]` (disabled) and can be a comma-separated list of issue types.'
213+
default: ''
214+
required: false
211215
outputs:
212216
closed-issues-prs:
213217
description: 'List of all closed issues and pull requests.'

dist/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,13 @@ class Issue {
289289
this.assignees = issue.assignees || [];
290290
this.isStale = (0, is_labeled_1.isLabeled)(this, this.staleLabel);
291291
this.markedStaleThisRun = false;
292+
if (typeof issue.type === 'object' &&
293+
issue.type !== null) {
294+
this.issue_type = issue.type.name;
295+
}
296+
else {
297+
this.issue_type = undefined;
298+
}
292299
}
293300
get isPullRequest() {
294301
return (0, is_pull_request_1.isPullRequest)(this);
@@ -506,6 +513,18 @@ class IssuesProcessor {
506513
IssuesProcessor._endIssueProcessing(issue);
507514
return; // If the issue has an 'include-only-assigned' option set, process only issues with nonempty assignees list
508515
}
516+
if (this.options.onlyIssueTypes) {
517+
const allowedTypes = this.options.onlyIssueTypes
518+
.split(',')
519+
.map(t => t.trim().toLowerCase())
520+
.filter(Boolean);
521+
const issueType = (issue.issue_type || '').toLowerCase();
522+
if (!allowedTypes.includes(issueType)) {
523+
issueLogger.info(`Skipping this $$type because its type ('${issue.issue_type}') is not in onlyIssueTypes (${allowedTypes.join(', ')})`);
524+
IssuesProcessor._endIssueProcessing(issue);
525+
return;
526+
}
527+
}
509528
const onlyLabels = (0, words_to_list_1.wordsToList)(this._getOnlyLabels(issue));
510529
if (onlyLabels.length > 0) {
511530
issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.OnlyLabels)} was specified to only process issues and pull requests with all those labels (${logger_service_1.LoggerService.cyan(onlyLabels.length)})`);
@@ -2225,6 +2244,7 @@ var Option;
22252244
Option["IgnorePrUpdates"] = "ignore-pr-updates";
22262245
Option["ExemptDraftPr"] = "exempt-draft-pr";
22272246
Option["CloseIssueReason"] = "close-issue-reason";
2247+
Option["OnlyIssueTypes"] = "only-issue-types";
22282248
})(Option || (exports.Option = Option = {}));
22292249

22302250

src/classes/issue.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class Issue implements IIssue {
2424
markedStaleThisRun: boolean;
2525
operations = new Operations();
2626
private readonly _options: IIssuesProcessorOptions;
27+
readonly issue_type?: string;
2728

2829
constructor(
2930
options: Readonly<IIssuesProcessorOptions>,
@@ -43,6 +44,15 @@ export class Issue implements IIssue {
4344
this.assignees = issue.assignees || [];
4445
this.isStale = isLabeled(this, this.staleLabel);
4546
this.markedStaleThisRun = false;
47+
48+
if (
49+
typeof (issue as any).type === 'object' &&
50+
(issue as any).type !== null
51+
) {
52+
this.issue_type = (issue as any).type.name;
53+
} else {
54+
this.issue_type = undefined;
55+
}
4656
}
4757

4858
get isPullRequest(): boolean {

src/classes/issues-processor.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,23 @@ export class IssuesProcessor {
252252
return; // If the issue has an 'include-only-assigned' option set, process only issues with nonempty assignees list
253253
}
254254

255+
if (this.options.onlyIssueTypes) {
256+
const allowedTypes = this.options.onlyIssueTypes
257+
.split(',')
258+
.map(t => t.trim().toLowerCase())
259+
.filter(Boolean);
260+
const issueType = (issue.issue_type || '').toLowerCase();
261+
if (!allowedTypes.includes(issueType)) {
262+
issueLogger.info(
263+
`Skipping this $$type because its type ('${
264+
issue.issue_type
265+
}') is not in onlyIssueTypes (${allowedTypes.join(', ')})`
266+
);
267+
IssuesProcessor._endIssueProcessing(issue);
268+
return;
269+
}
270+
}
271+
255272
const onlyLabels: string[] = wordsToList(this._getOnlyLabels(issue));
256273

257274
if (onlyLabels.length > 0) {

src/enums/option.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,6 @@ export enum Option {
4949
IgnoreIssueUpdates = 'ignore-issue-updates',
5050
IgnorePrUpdates = 'ignore-pr-updates',
5151
ExemptDraftPr = 'exempt-draft-pr',
52-
CloseIssueReason = 'close-issue-reason'
52+
CloseIssueReason = 'close-issue-reason',
53+
OnlyIssueTypes = 'only-issue-types'
5354
}

src/interfaces/issue.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface IIssue {
1515
locked: boolean;
1616
milestone?: IMilestone | null;
1717
assignees?: Assignee[] | null;
18+
issue_type?: string;
1819
}
1920

2021
export type OctokitIssue = components['schemas']['issue'];

src/interfaces/issues-processor-options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ export interface IIssuesProcessorOptions {
5555
exemptDraftPr: boolean;
5656
closeIssueReason: string;
5757
includeOnlyAssigned: boolean;
58+
onlyIssueTypes?: string;
5859
}

0 commit comments

Comments
 (0)