Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow the processing of locked issues and pull requests #1173

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Every argument is optional.
| [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 | |
| [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

Expand Down Expand Up @@ -547,6 +548,16 @@ If set to `true`, only the issues or the pull requests with an assignee will be

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

See also [action.yml](./action.yml) for a comprehensive list of all the options.
Expand Down
3 changes: 2 additions & 1 deletion __tests__/constants/default-processor-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
ignorePrUpdates: undefined,
exemptDraftPr: false,
closeIssueReason: 'not_planned',
includeOnlyAssigned: false
includeOnlyAssigned: false,
exemptLocked: true
});
166 changes: 166 additions & 0 deletions __tests__/main.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,38 @@ test('locked issues will not be marked stale', async () => {
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 () => {
const TestIssueList: Issue[] = [
generateIssue(
Expand Down Expand Up @@ -960,6 +992,38 @@ test('stale locked issues will not be closed', async () => {
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 () => {
const TestIssueList: Issue[] = [
generateIssue(
Expand Down Expand Up @@ -988,6 +1052,38 @@ test('locked prs will not be marked stale', async () => {
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 () => {
const TestIssueList: Issue[] = [
generateIssue(
Expand Down Expand Up @@ -1018,6 +1114,38 @@ test('stale locked prs will not be closed', async () => {
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 () => {
expect.assertions(3);
const opts = {...DefaultProcessorOptions};
Expand Down Expand Up @@ -2516,6 +2644,44 @@ test('processing a locked issue with a close label will not remove the close lab
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 () => {
expect.assertions(3);
const opts: IIssuesProcessorOptions = {
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ inputs:
description: 'Only the issues or the pull requests with an assignee will be marked as stale automatically.'
default: 'false'
required: false
exempt-locked:
description: 'Issues and pull requests that are locked will not be marked as stale.'
default: 'true'
required: false
outputs:
closed-issues-prs:
description: 'List of all closed issues and pull requests.'
Expand Down
5 changes: 3 additions & 2 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ class IssuesProcessor {
IssuesProcessor._endIssueProcessing(issue);
return; // Don't process closed issues
}
if (issue.locked) {
if (issue.locked && this.options.exemptLocked) {
issueLogger.info(`Skipping this $$type because it is locked`);
IssuesProcessor._endIssueProcessing(issue);
return; // Don't process locked issues
Expand Down Expand Up @@ -2567,7 +2567,8 @@ function _getAndValidateArgs() {
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
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']) {
if (isNaN(parseFloat(core.getInput(numberInput)))) {
Expand Down
3 changes: 2 additions & 1 deletion src/classes/issue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ describe('Issue', (): void => {
ignorePrUpdates: undefined,
exemptDraftPr: false,
closeIssueReason: '',
includeOnlyAssigned: false
includeOnlyAssigned: false,
exemptLocked: true
};
issueInterface = {
title: 'dummy-title',
Expand Down
6 changes: 4 additions & 2 deletions src/classes/issues-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export class IssuesProcessor {
return; // Don't process closed issues
}

if (issue.locked) {
if (issue.locked && this.options.exemptLocked) {
issueLogger.info(`Skipping this $$type because it is locked`);
IssuesProcessor._endIssueProcessing(issue);
return; // Don't process locked issues
Expand Down Expand Up @@ -1196,7 +1196,9 @@ export class IssuesProcessor {
const issueLogger: IssueLogger = new IssueLogger(issue);

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) {
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/issues-processor-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ export interface IIssuesProcessorOptions {
exemptDraftPr: boolean;
closeIssueReason: string;
includeOnlyAssigned: boolean;
exemptLocked: boolean;
}
3 changes: 2 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
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']) {
Expand Down