Allow the processing of locked issues and pull requests

This commit is contained in:
zhews 2024-08-14 15:54:08 +02:00
parent 3f3b0175e8
commit 7e06b131d5
9 changed files with 195 additions and 7 deletions

View File

@ -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.

View File

@ -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
}); });

View File

@ -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 = {

View File

@ -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.'

5
dist/index.js vendored
View File

@ -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)))) {

View File

@ -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',

View File

@ -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) {

View File

@ -54,4 +54,5 @@ export interface IIssuesProcessorOptions {
exemptDraftPr: boolean; exemptDraftPr: boolean;
closeIssueReason: string; closeIssueReason: string;
includeOnlyAssigned: boolean; includeOnlyAssigned: boolean;
exemptLocked: boolean;
} }

View File

@ -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']) {