diff --git a/README.md b/README.md index 512ed470..71d5eaae 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,10 @@ Warns and then closes issues and PRs that have had no activity for a specified a | `stale-pr-message` | Message to post on the stale PR. | Optional | | `close-issue-message` | Message to post on the stale issue while closing it. | Optional | | `close-pr-message` | Message to post on the stale PR while closing it. | Optional | -| `stale-issue-label` | Label to apply on the stale issue. _Defaults to **stale**_ | Optional | -| `close-issue-label` | Label to apply on closing issue. | Optional | -| `stale-pr-label` | Label to apply on the stale PR. | Optional | -| `close-pr-label` | Label to apply on the closing PR. | Optional | +| `stale-issue-label` | Label to apply on the stale issue. _Defaults to **Stale**_ | Optional | +| `close-issue-label` | Label to apply on closing issue (automatically removed if no longer closed nor locked). | Optional | +| `stale-pr-label` | Label to apply on the stale PR. _Defaults to **Stale**_ | Optional | +| `close-pr-label` | Label to apply on the closing PR (automatically removed if no longer closed nor locked). | Optional | | `exempt-issue-labels` | Labels on an issue exempted from being marked as stale. | Optional | | `exempt-pr-labels` | Labels on the PR exempted from being marked as stale. | Optional | | `only-labels` | Only labels checked for stale issue/PR. | Optional | diff --git a/__tests__/main.spec.ts b/__tests__/main.spec.ts index 46ea62fb..fcb55440 100644 --- a/__tests__/main.spec.ts +++ b/__tests__/main.spec.ts @@ -2112,3 +2112,111 @@ test('processing a pull request opened since 2 days and with the option "daysBef expect(processor.staleIssues.length).toEqual(1); expect(processor.closedIssues.length).toEqual(0); }); + +test('processing a previously closed issue with a close label will remove the close label', async () => { + expect.assertions(1); + const opts: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + closeIssueLabel: 'close', + staleIssueLabel: 'stale' + }; + const now: Date = new Date(); + const oneWeekAgo: Date = new Date(now.getDate() - 7); + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'An opened issue with a close label', + oneWeekAgo.toDateString(), + now.toDateString(), + false, + ['close'], + false, + false + ) + ]; + const processor = new IssuesProcessor( + opts, + async () => 'abot', + 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 a closed issue with a close label will not remove the close label', async () => { + expect.assertions(1); + const opts: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + closeIssueLabel: 'close', + staleIssueLabel: 'stale' + }; + const now: Date = new Date(); + const oneWeekAgo: Date = new Date(now.getDate() - 7); + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'A closed issue with a close label', + oneWeekAgo.toDateString(), + now.toDateString(), + false, + ['close'], + true, + false + ) + ]; + const processor = new IssuesProcessor( + opts, + async () => 'abot', + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + // process our fake issue list + await processor.processIssues(1); + + expect(processor.removedLabelIssues).toHaveLength(0); +}); + +test('processing a locked issue with a close label will not remove the close label', async () => { + expect.assertions(1); + const opts: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + closeIssueLabel: 'close', + staleIssueLabel: 'stale' + }; + const now: Date = new Date(); + const oneWeekAgo: Date = new Date(now.getDate() - 7); + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'A closed issue with a close label', + oneWeekAgo.toDateString(), + now.toDateString(), + false, + ['close'], + false, + true + ) + ]; + const processor = new IssuesProcessor( + opts, + async () => 'abot', + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + // process our fake issue list + await processor.processIssues(1); + + expect(processor.removedLabelIssues).toHaveLength(0); +}); diff --git a/dist/index.js b/dist/index.js index 9465aefc..ec5014e1 100644 --- a/dist/index.js +++ b/dist/index.js @@ -299,6 +299,8 @@ class IssuesProcessor { issueLogger.info(`Skipping $$type because it is locked`); continue; // don't process locked issues } + // Try to remove the close label when not close/locked issue or PR + yield this._removeCloseLabel(issue, closeLabel); if (this.options.startDate) { const startDate = new Date(this.options.startDate); const createdAt = new Date(issue.created_at); @@ -677,10 +679,24 @@ class IssuesProcessor { _removeStaleLabel(issue, staleLabel) { return __awaiter(this, void 0, void 0, function* () { const issueLogger = new issue_logger_1.IssueLogger(issue); - issueLogger.info(`$$type is no longer stale. Removing stale label.`); + issueLogger.info(`The $$type is no longer stale. Removing the stale label...`); return this._removeLabel(issue, staleLabel); }); } + _removeCloseLabel(issue, closeLabel) { + return __awaiter(this, void 0, void 0, function* () { + const issueLogger = new issue_logger_1.IssueLogger(issue); + issueLogger.info(`The $$type is not closed nor locked. Trying to remove the close label...`); + if (!closeLabel) { + issueLogger.info(`There is no close label on this $$type. Skip`); + return Promise.resolve(); + } + if (is_labeled_1.isLabeled(issue, closeLabel)) { + issueLogger.info(`The $$type has a close label "${closeLabel}". Removing the close label...`); + return this._removeLabel(issue, closeLabel); + } + }); + } } exports.IssuesProcessor = IssuesProcessor; diff --git a/src/classes/issue.ts b/src/classes/issue.ts index 816de1f8..d48add6b 100644 --- a/src/classes/issue.ts +++ b/src/classes/issue.ts @@ -15,7 +15,7 @@ export class Issue implements IIssue { updated_at: IsoDateString; readonly labels: ILabel[]; readonly pull_request: Object | null | undefined; - readonly state: string; + readonly state: string | 'closed' | 'open'; readonly locked: boolean; readonly milestone: IMilestone | undefined; readonly assignees: IAssignee[]; diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts index 758950bd..cf7650fc 100644 --- a/src/classes/issues-processor.ts +++ b/src/classes/issues-processor.ts @@ -140,6 +140,9 @@ export class IssuesProcessor { continue; // don't process locked issues } + // Try to remove the close label when not close/locked issue or PR + await this._removeCloseLabel(issue, closeLabel); + if (this.options.startDate) { const startDate: Date = new Date(this.options.startDate); const createdAt: Date = new Date(issue.created_at); @@ -663,8 +666,35 @@ export class IssuesProcessor { ): Promise { const issueLogger: IssueLogger = new IssueLogger(issue); - issueLogger.info(`$$type is no longer stale. Removing stale label.`); + issueLogger.info( + `The $$type is no longer stale. Removing the stale label...` + ); return this._removeLabel(issue, staleLabel); } + + private async _removeCloseLabel( + issue: Issue, + closeLabel: Readonly + ): Promise { + const issueLogger: IssueLogger = new IssueLogger(issue); + + issueLogger.info( + `The $$type is not closed nor locked. Trying to remove the close label...` + ); + + if (!closeLabel) { + issueLogger.info(`There is no close label on this $$type. Skip`); + + return Promise.resolve(); + } + + if (isLabeled(issue, closeLabel)) { + issueLogger.info( + `The $$type has a close label "${closeLabel}". Removing the close label...` + ); + + return this._removeLabel(issue, closeLabel); + } + } }