Allow the processing of locked issues and pull requests
This commit is contained in:
parent
3f3b0175e8
commit
7e06b131d5
11
README.md
11
README.md
|
@ -97,6 +97,7 @@ Every argument is optional.
|
||||||
| [ignore-issue-updates](#ignore-issue-updates) | Override [ignore-updates](#ignore-updates) for issues only | |
|
| [ignore-issue-updates](#ignore-issue-updates) | Override [ignore-updates](#ignore-updates) for issues only | |
|
||||||
| [ignore-pr-updates](#ignore-pr-updates) | Override [ignore-updates](#ignore-updates) for PRs only | |
|
| [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` |
|
| [include-only-assigned](#include-only-assigned) | Process only assigned issues | `false` |
|
||||||
|
| [exempt-locked](#exempt-locked) | Issues and pull requests that are locked will not be marked as stale. | `true` |
|
||||||
|
|
||||||
### List of output options
|
### List of output options
|
||||||
|
|
||||||
|
@ -547,6 +548,16 @@ If set to `true`, only the issues or the pull requests with an assignee will be
|
||||||
|
|
||||||
Default value: `false`
|
Default value: `false`
|
||||||
|
|
||||||
|
#### exempt-locked
|
||||||
|
|
||||||
|
If set to `false` issues or pull requests that are locked will be marked as
|
||||||
|
stale automatically. If you process locked issues and pull requests and want to
|
||||||
|
add a closing message the default repo-token will not be sufficient. For that
|
||||||
|
you will have to use a PAT which has write access, is repository owner or
|
||||||
|
collaborator.
|
||||||
|
|
||||||
|
Default value: `true`
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
See also [action.yml](./action.yml) for a comprehensive list of all the options.
|
See also [action.yml](./action.yml) for a comprehensive list of all the options.
|
||||||
|
|
|
@ -55,5 +55,6 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
|
||||||
ignorePrUpdates: undefined,
|
ignorePrUpdates: undefined,
|
||||||
exemptDraftPr: false,
|
exemptDraftPr: false,
|
||||||
closeIssueReason: 'not_planned',
|
closeIssueReason: 'not_planned',
|
||||||
includeOnlyAssigned: false
|
includeOnlyAssigned: false,
|
||||||
|
exemptLocked: true
|
||||||
});
|
});
|
||||||
|
|
|
@ -930,6 +930,38 @@ test('locked issues will not be marked stale', async () => {
|
||||||
expect(processor.closedIssues).toHaveLength(0);
|
expect(processor.closedIssues).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('locked issues are marked stale', async () => {
|
||||||
|
const opts: IIssuesProcessorOptions = {...DefaultProcessorOptions};
|
||||||
|
opts.exemptLocked = false;
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
opts,
|
||||||
|
1,
|
||||||
|
'A locked issue that will be stale',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
];
|
||||||
|
const processor = new IssuesProcessorMock(
|
||||||
|
opts,
|
||||||
|
alwaysFalseStateMock,
|
||||||
|
async p => (p === 1 ? TestIssueList : []),
|
||||||
|
async () => [],
|
||||||
|
async () => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// process our fake issue list
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
expect(processor.staleIssues).toHaveLength(1);
|
||||||
|
expect(processor.closedIssues).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
test('stale locked issues will not be closed', async () => {
|
test('stale locked issues will not be closed', async () => {
|
||||||
const TestIssueList: Issue[] = [
|
const TestIssueList: Issue[] = [
|
||||||
generateIssue(
|
generateIssue(
|
||||||
|
@ -960,6 +992,38 @@ test('stale locked issues will not be closed', async () => {
|
||||||
expect(processor.closedIssues).toHaveLength(0);
|
expect(processor.closedIssues).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('stale locked issues will be closed', async () => {
|
||||||
|
const opts: IIssuesProcessorOptions = {...DefaultProcessorOptions};
|
||||||
|
opts.exemptLocked = false;
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
opts,
|
||||||
|
1,
|
||||||
|
'A stale locked issue that will not be closed',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
['Stale'],
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
];
|
||||||
|
const processor = new IssuesProcessorMock(
|
||||||
|
opts,
|
||||||
|
alwaysFalseStateMock,
|
||||||
|
async p => (p === 1 ? TestIssueList : []),
|
||||||
|
async () => [],
|
||||||
|
async () => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// process our fake issue list
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
expect(processor.staleIssues).toHaveLength(0);
|
||||||
|
expect(processor.closedIssues).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('locked prs will not be marked stale', async () => {
|
test('locked prs will not be marked stale', async () => {
|
||||||
const TestIssueList: Issue[] = [
|
const TestIssueList: Issue[] = [
|
||||||
generateIssue(
|
generateIssue(
|
||||||
|
@ -988,6 +1052,38 @@ test('locked prs will not be marked stale', async () => {
|
||||||
expect(processor.closedIssues).toHaveLength(0);
|
expect(processor.closedIssues).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('locked prs will be marked stale', async () => {
|
||||||
|
const opts: IIssuesProcessorOptions = {...DefaultProcessorOptions};
|
||||||
|
opts.exemptLocked = false;
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
opts,
|
||||||
|
1,
|
||||||
|
'A locked PR that will not be marked stale',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
];
|
||||||
|
const processor = new IssuesProcessorMock(
|
||||||
|
opts,
|
||||||
|
alwaysFalseStateMock,
|
||||||
|
async p => (p === 1 ? TestIssueList : []),
|
||||||
|
async () => [],
|
||||||
|
async () => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// process our fake issue list
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
expect(processor.staleIssues).toHaveLength(1);
|
||||||
|
expect(processor.closedIssues).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
test('stale locked prs will not be closed', async () => {
|
test('stale locked prs will not be closed', async () => {
|
||||||
const TestIssueList: Issue[] = [
|
const TestIssueList: Issue[] = [
|
||||||
generateIssue(
|
generateIssue(
|
||||||
|
@ -1018,6 +1114,38 @@ test('stale locked prs will not be closed', async () => {
|
||||||
expect(processor.closedIssues).toHaveLength(0);
|
expect(processor.closedIssues).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('stale locked prs will be closed', async () => {
|
||||||
|
const opts: IIssuesProcessorOptions = {...DefaultProcessorOptions};
|
||||||
|
opts.exemptLocked = false;
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
opts,
|
||||||
|
1,
|
||||||
|
'A stale locked PR that will not be closed',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
['Stale'],
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
];
|
||||||
|
const processor = new IssuesProcessorMock(
|
||||||
|
opts,
|
||||||
|
alwaysFalseStateMock,
|
||||||
|
async p => (p === 1 ? TestIssueList : []),
|
||||||
|
async () => [],
|
||||||
|
async () => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// process our fake issue list
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
expect(processor.staleIssues).toHaveLength(0);
|
||||||
|
expect(processor.closedIssues).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('exempt issue labels will not be marked stale', async () => {
|
test('exempt issue labels will not be marked stale', async () => {
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
const opts = {...DefaultProcessorOptions};
|
const opts = {...DefaultProcessorOptions};
|
||||||
|
@ -2516,6 +2644,44 @@ test('processing a locked issue with a close label will not remove the close lab
|
||||||
expect(processor.removedLabelIssues).toHaveLength(0);
|
expect(processor.removedLabelIssues).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('processing a locked issue with a close label will remove the close label', async () => {
|
||||||
|
expect.assertions(1);
|
||||||
|
const opts: IIssuesProcessorOptions = {
|
||||||
|
...DefaultProcessorOptions,
|
||||||
|
closeIssueLabel: 'close',
|
||||||
|
staleIssueLabel: 'stale',
|
||||||
|
exemptLocked: false
|
||||||
|
};
|
||||||
|
const now: Date = new Date();
|
||||||
|
const oneWeekAgo: Date = new Date(now.setDate(now.getDate() - 7));
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
opts,
|
||||||
|
1,
|
||||||
|
'A closed issue with a close label',
|
||||||
|
oneWeekAgo.toDateString(),
|
||||||
|
now.toDateString(),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
['close'],
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
];
|
||||||
|
const processor = new IssuesProcessorMock(
|
||||||
|
opts,
|
||||||
|
alwaysFalseStateMock,
|
||||||
|
async p => (p === 1 ? TestIssueList : []),
|
||||||
|
async () => [],
|
||||||
|
async () => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// process our fake issue list
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
expect(processor.removedLabelIssues).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('processing an issue stale since less than the daysBeforeStale with a stale label created after daysBeforeClose should close the issue', async () => {
|
test('processing an issue stale since less than the daysBeforeStale with a stale label created after daysBeforeClose should close the issue', async () => {
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
const opts: IIssuesProcessorOptions = {
|
const opts: IIssuesProcessorOptions = {
|
||||||
|
|
|
@ -204,6 +204,10 @@ inputs:
|
||||||
description: 'Only the issues or the pull requests with an assignee will be marked as stale automatically.'
|
description: 'Only the issues or the pull requests with an assignee will be marked as stale automatically.'
|
||||||
default: 'false'
|
default: 'false'
|
||||||
required: false
|
required: false
|
||||||
|
exempt-locked:
|
||||||
|
description: 'Issues and pull requests that are locked will not be marked as stale.'
|
||||||
|
default: 'true'
|
||||||
|
required: false
|
||||||
outputs:
|
outputs:
|
||||||
closed-issues-prs:
|
closed-issues-prs:
|
||||||
description: 'List of all closed issues and pull requests.'
|
description: 'List of all closed issues and pull requests.'
|
||||||
|
|
|
@ -495,7 +495,7 @@ class IssuesProcessor {
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
return; // Don't process closed issues
|
return; // Don't process closed issues
|
||||||
}
|
}
|
||||||
if (issue.locked) {
|
if (issue.locked && this.options.exemptLocked) {
|
||||||
issueLogger.info(`Skipping this $$type because it is locked`);
|
issueLogger.info(`Skipping this $$type because it is locked`);
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
return; // Don't process locked issues
|
return; // Don't process locked issues
|
||||||
|
@ -2567,7 +2567,8 @@ function _getAndValidateArgs() {
|
||||||
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
|
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
|
||||||
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
|
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
|
||||||
closeIssueReason: core.getInput('close-issue-reason'),
|
closeIssueReason: core.getInput('close-issue-reason'),
|
||||||
includeOnlyAssigned: core.getInput('include-only-assigned') === 'true'
|
includeOnlyAssigned: core.getInput('include-only-assigned') === 'true',
|
||||||
|
exemptLocked: core.getInput('exempt-locked') === 'true'
|
||||||
};
|
};
|
||||||
for (const numberInput of ['days-before-stale']) {
|
for (const numberInput of ['days-before-stale']) {
|
||||||
if (isNaN(parseFloat(core.getInput(numberInput)))) {
|
if (isNaN(parseFloat(core.getInput(numberInput)))) {
|
||||||
|
|
|
@ -64,7 +64,8 @@ describe('Issue', (): void => {
|
||||||
ignorePrUpdates: undefined,
|
ignorePrUpdates: undefined,
|
||||||
exemptDraftPr: false,
|
exemptDraftPr: false,
|
||||||
closeIssueReason: '',
|
closeIssueReason: '',
|
||||||
includeOnlyAssigned: false
|
includeOnlyAssigned: false,
|
||||||
|
exemptLocked: true
|
||||||
};
|
};
|
||||||
issueInterface = {
|
issueInterface = {
|
||||||
title: 'dummy-title',
|
title: 'dummy-title',
|
||||||
|
|
|
@ -237,7 +237,7 @@ export class IssuesProcessor {
|
||||||
return; // Don't process closed issues
|
return; // Don't process closed issues
|
||||||
}
|
}
|
||||||
|
|
||||||
if (issue.locked) {
|
if (issue.locked && this.options.exemptLocked) {
|
||||||
issueLogger.info(`Skipping this $$type because it is locked`);
|
issueLogger.info(`Skipping this $$type because it is locked`);
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
return; // Don't process locked issues
|
return; // Don't process locked issues
|
||||||
|
@ -1196,7 +1196,9 @@ export class IssuesProcessor {
|
||||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
|
||||||
issueLogger.info(
|
issueLogger.info(
|
||||||
`The $$type is not closed nor locked. Trying to remove the close label...`
|
`The $$type is not closed${
|
||||||
|
this.options.exemptLocked ? ' nor locked' : ''
|
||||||
|
}. Trying to remove the close label...`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!closeLabel) {
|
if (!closeLabel) {
|
||||||
|
|
|
@ -54,4 +54,5 @@ export interface IIssuesProcessorOptions {
|
||||||
exemptDraftPr: boolean;
|
exemptDraftPr: boolean;
|
||||||
closeIssueReason: string;
|
closeIssueReason: string;
|
||||||
includeOnlyAssigned: boolean;
|
includeOnlyAssigned: boolean;
|
||||||
|
exemptLocked: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,8 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
|
||||||
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
|
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
|
||||||
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
|
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
|
||||||
closeIssueReason: core.getInput('close-issue-reason'),
|
closeIssueReason: core.getInput('close-issue-reason'),
|
||||||
includeOnlyAssigned: core.getInput('include-only-assigned') === 'true'
|
includeOnlyAssigned: core.getInput('include-only-assigned') === 'true',
|
||||||
|
exemptLocked: core.getInput('exempt-locked') === 'true'
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const numberInput of ['days-before-stale']) {
|
for (const numberInput of ['days-before-stale']) {
|
||||||
|
|
Loading…
Reference in New Issue