Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ Every argument is optional.
| [ignore-pr-updates](#ignore-pr-updates) | Override [ignore-updates](#ignore-updates) for PRs only | |
| [include-only-assigned](#include-only-assigned) | Process only assigned issues | `false` |
| [sort-by](#sort-by) | What to sort issues and PRs by | `created` |
| [exempt-issue-types](#exempt-issue-types) | Issue types on issues exempted from stale/closed. | |
| [only-issue-types](#only-issue-types) | Only issues with a matching type are processed as stale/closed. | |

### List of output options
Expand Down Expand Up @@ -563,6 +564,15 @@ Useful to sort the issues and PRs by the specified field. It accepts `created`,

Default value: `created`

#### exempt-issue-types

A comma separated list of issue types that can be assigned to issues to exclude them from being marked as stale
(e.g: `Bug,Feature`)

If unset (or an empty string), this option will not alter the stale workflow.

Default value: unset

#### only-issue-types

A comma separated list of allowed issue types. Only issues with a matching type will be processed (e.g.: `bug,question`).
Expand Down
130 changes: 130 additions & 0 deletions __tests__/exempt-issue-types.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import {Issue} from '../src/classes/issue';
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
import {IssuesProcessorMock} from './classes/issues-processor-mock';
import {DefaultProcessorOptions} from './constants/default-processor-options';
import {generateIssue} from './functions/generate-issue';
import {alwaysFalseStateMock} from './classes/state-mock';

describe('exempt-issue-types option', () => {
test('should skip issues with an exempt type', async () => {
const opts: IIssuesProcessorOptions = {
...DefaultProcessorOptions,
exemptIssueTypes: 'question,discussion'
};

const TestIssueList: Issue[] = [
generateIssue(
opts,
1,
'A bug',
'2020-01-01T17:00:00Z',
'2020-01-01T17:00:00Z',
false,
false,
[],
false,
false,
undefined,
[],
'bug'
),
generateIssue(
opts,
2,
'A question',
'2020-01-01T17:00:00Z',
'2020-01-01T17:00:00Z',
false,
false,
[],
false,
false,
undefined,
[],
'question'
),
generateIssue(
opts,
3,
'A discussion',
'2020-01-01T17:00:00Z',
'2020-01-01T17:00:00Z',
false,
false,
[],
false,
false,
undefined,
[],
'discussion'
)
];

const processor = new IssuesProcessorMock(
opts,
alwaysFalseStateMock,
async p => (p === 1 ? TestIssueList : []),
async () => [],
async () => new Date().toDateString()
);

await processor.processIssues(1);

// The exempt types should not be processed/marked stale
expect(processor.staleIssues.map(i => i.title)).toEqual(['A bug']);
});

test('should process all issues if exemptIssueTypes is unset', async () => {
const opts: IIssuesProcessorOptions = {
...DefaultProcessorOptions,
exemptIssueTypes: ''
};

const TestIssueList: Issue[] = [
generateIssue(
opts,
1,
'A bug',
'2020-01-01T17:00:00Z',
'2020-01-01T17:00:00Z',
false,
false,
[],
false,
false,
undefined,
[],
'bug'
),
generateIssue(
opts,
2,
'A feature',
'2020-01-01T17:00:00Z',
'2020-01-01T17:00:00Z',
false,
false,
[],
false,
false,
undefined,
[],
'feature'
)
];

const processor = new IssuesProcessorMock(
opts,
alwaysFalseStateMock,
async p => (p === 1 ? TestIssueList : []),
async () => [],
async () => new Date().toDateString()
);

await processor.processIssues(1);
expect(processor.staleIssues.map(i => i.title)).toEqual([
'A bug',
'A feature'
]);
});
});
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ inputs:
description: 'Only the issues or the pull requests with an assignee will be marked as stale automatically.'
default: 'false'
required: false
exempt-issue-types:
description: 'Issues with a matching type are exempt from being processed as stale/closed. Defaults to `[]` (disabled) and can be a comma-separated list of issue types.'
default: ''
required: false
only-issue-types:
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.'
default: ''
Expand Down
17 changes: 16 additions & 1 deletion dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,19 @@ class IssuesProcessor {
return;
}
}
// exemptIssueTypes wins if both it and onlyIssueTypes are specified
if (this.options.exemptIssueTypes) {
const exemptTypes = this.options.exemptIssueTypes
.split(',')
.map(t => t.trim().toLowerCase())
.filter(Boolean);
const issueType = (issue.issue_type || '').toLowerCase();
if (exemptTypes.includes(issueType)) {
issueLogger.info(`Skipping this $$type because its type ('${issue.issue_type}') is in exemptIssueTypes (${exemptTypes.join(', ')})`);
IssuesProcessor._endIssueProcessing(issue);
return;
}
}
const onlyLabels = (0, words_to_list_1.wordsToList)(this._getOnlyLabels(issue));
if (onlyLabels.length > 0) {
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)})`);
Expand Down Expand Up @@ -2244,6 +2257,7 @@ var Option;
Option["IgnorePrUpdates"] = "ignore-pr-updates";
Option["ExemptDraftPr"] = "exempt-draft-pr";
Option["CloseIssueReason"] = "close-issue-reason";
Option["ExemptIssueTypes"] = "exempt-issue-types";
Option["OnlyIssueTypes"] = "only-issue-types";
})(Option || (exports.Option = Option = {}));

Expand Down Expand Up @@ -2611,7 +2625,8 @@ function _getAndValidateArgs() {
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
closeIssueReason: core.getInput('close-issue-reason'),
includeOnlyAssigned: core.getInput('include-only-assigned') === 'true',
onlyIssueTypes: core.getInput('only-issue-types')
onlyIssueTypes: core.getInput('only-issue-types'),
exemptIssueTypes: core.getInput('exempt-issue-types')
};
for (const numberInput of ['days-before-stale']) {
if (isNaN(parseFloat(core.getInput(numberInput)))) {
Expand Down
18 changes: 18 additions & 0 deletions src/classes/issues-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,24 @@ export class IssuesProcessor {
}
}

// exemptIssueTypes wins if both it and onlyIssueTypes are specified
if (this.options.exemptIssueTypes) {
const exemptTypes = this.options.exemptIssueTypes
.split(',')
.map(t => t.trim().toLowerCase())
.filter(Boolean);
const issueType = (issue.issue_type || '').toLowerCase();
if (exemptTypes.includes(issueType)) {
issueLogger.info(
`Skipping this $$type because its type ('${
issue.issue_type
}') is in exemptIssueTypes (${exemptTypes.join(', ')})`
);
IssuesProcessor._endIssueProcessing(issue);
return;
}
}

const onlyLabels: string[] = wordsToList(this._getOnlyLabels(issue));

if (onlyLabels.length > 0) {
Expand Down
1 change: 1 addition & 0 deletions src/enums/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ export enum Option {
IgnorePrUpdates = 'ignore-pr-updates',
ExemptDraftPr = 'exempt-draft-pr',
CloseIssueReason = 'close-issue-reason',
ExemptIssueTypes = 'exempt-issue-types',
OnlyIssueTypes = 'only-issue-types'
}
1 change: 1 addition & 0 deletions src/interfaces/issues-processor-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ export interface IIssuesProcessorOptions {
exemptDraftPr: boolean;
closeIssueReason: string;
includeOnlyAssigned: boolean;
exemptIssueTypes?: string;
onlyIssueTypes?: string;
}
3 changes: 2 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
closeIssueReason: core.getInput('close-issue-reason'),
includeOnlyAssigned: core.getInput('include-only-assigned') === 'true',
onlyIssueTypes: core.getInput('only-issue-types')
onlyIssueTypes: core.getInput('only-issue-types'),
exemptIssueTypes: core.getInput('exempt-issue-types')
};

for (const numberInput of ['days-before-stale']) {
Expand Down