From 35020771a64cb381af663a6df9d56a77f89f3c3e Mon Sep 17 00:00:00 2001 From: TESTELIN Geoffrey Date: Mon, 7 Jun 2021 22:05:05 +0200 Subject: [PATCH] feat(options): add new options to avoid stale based on comments Helping to close #441, #470, #435? Closes #390 due to no activity BREAKING CHANGES: the options related to remove-stale-when-updated will only check the updates, not the comment. It is only impactint the configurations using the value at false --- README.md | 119 ++-- __tests__/any-of-labels.spec.ts | 28 +- .../constants/default-processor-options.ts | 3 + __tests__/functions/generate-issue.ts | 12 +- __tests__/main.spec.ts | 39 +- __tests__/only-labels.spec.ts | 28 +- __tests__/operations-per-run.spec.ts | 22 +- __tests__/remove-stale-when-commented.spec.ts | 556 ++++++++++++++++++ __tests__/remove-stale-when-updated.spec.ts | 117 ++-- action.yml | 18 +- dist/index.js | 52 +- src/classes/issue.spec.ts | 3 + src/classes/issues-processor.ts | 88 ++- src/classes/milestones.ts | 5 +- src/enums/option.ts | 3 + src/functions/is-labeled.spec.ts | 4 +- src/interfaces/issues-processor-options.ts | 3 + src/main.ts | 9 + 18 files changed, 914 insertions(+), 195 deletions(-) create mode 100644 __tests__/remove-stale-when-commented.spec.ts diff --git a/README.md b/README.md index 07a5b9899..293e3aef0 100644 --- a/README.md +++ b/README.md @@ -14,52 +14,55 @@ The default configuration will: Every argument is optional. -| Input | Description | Default | -| ------------------------------------------------------------------- | ------------------------------------------------------------------------ | --------------------- | -| [repo-token](#repo-token) | PAT for GitHub API authentication | `${{ github.token }}` | -| [days-before-stale](#days-before-stale) | Idle number of days before marking issues/PRs stale | `60` | -| [days-before-issue-stale](#days-before-issue-stale) | Override [days-before-stale](#days-before-stale) for issues only | | -| [days-before-pr-stale](#days-before-pr-stale) | Override [days-before-stale](#days-before-stale) for PRs only | | -| [days-before-close](#days-before-close) | Idle number of days before closing stale issues/PRs | `7` | -| [days-before-issue-close](#days-before-issue-close) | Override [days-before-close](#days-before-close) for issues only | | -| [days-before-pr-close](#days-before-pr-close) | Override [days-before-close](#days-before-close) for PRs only | | -| [stale-issue-message](#stale-issue-message) | Comment on the staled issues | | -| [stale-pr-message](#stale-pr-message) | Comment on the staled PRs | | -| [close-issue-message](#close-issue-message) | Comment on the staled issues while closed | | -| [close-pr-message](#close-pr-message) | Comment on the staled PRs while closed | | -| [stale-issue-label](#stale-issue-label) | Label to apply on staled issues | `Stale` | -| [close-issue-label](#close-issue-label) | Label to apply on closed issues | | -| [stale-pr-label](#stale-pr-label) | Label to apply on staled PRs | `Stale` | -| [close-pr-label](#close-pr-label) | Label to apply on closed PRs | | -| [exempt-issue-labels](#exempt-issue-labels) | Labels on issues exempted from stale | | -| [exempt-pr-labels](#exempt-pr-labels) | Labels on PRs exempted from stale | | -| [only-labels](#only-labels) | Only issues/PRs with ALL these labels are checked | | -| [only-issue-labels](#only-issue-labels) | Only issues with ALL these labels are checked | | -| [only-pr-labels](#only-pr-labels) | Only PRs with ALL these labels are checked | | -| [any-of-labels](#any-of-labels) | Only issues/PRs with ANY of these labels are checked | | -| [any-of-issue-labels](#any-of-issue-labels) | Only issues with ANY of these labels are checked | | -| [any-of-pr-labels](#any-of-pr-labels) | Only PRs with ANY of these labels are checked | | -| [operations-per-run](#operations-per-run) | Max number of operations per run | `30` | -| [remove-stale-when-updated](#remove-stale-when-updated) | Remove stale label from issues/PRs on updates/comments | `true` | -| [remove-issue-stale-when-updated](#remove-issue-stale-when-updated) | Remove stale label from issues on updates/comments | | -| [remove-pr-stale-when-updated](#remove-pr-stale-when-updated) | Remove stale label from PRs on updates/comments | | -| [debug-only](#debug-only) | Dry-run | `false` | -| [ascending](#ascending) | Order to get issues/PRs | `false` | -| [start-date](#start-date) | Skip stale action for issues/PRs created before it | | -| [delete-branch](#delete-branch) | Delete branch after closing a stale PR | `false` | -| [exempt-milestones](#exempt-milestones) | Milestones on issues/PRs exempted from stale | | -| [exempt-issue-milestones](#exempt-issue-milestones) | Override [exempt-milestones](#exempt-milestones) for issues only | | -| [exempt-pr-milestones](#exempt-pr-milestones) | Override [exempt-milestones](#exempt-milestones) for PRs only | | -| [exempt-all-milestones](#exempt-all-milestones) | Exempt all issues/PRs with milestones from stale | | -| [exempt-all-issue-milestones](#exempt-all-issue-milestones) | Override [exempt-all-milestones](#exempt-all-milestones) for issues only | | -| [exempt-all-pr-milestones](#exempt-all-pr-milestones) | Override [exempt-all-milestones](#exempt-all-milestones) for PRs only | | -| [exempt-assignees](#exempt-assignees) | Assignees on issues/PRs exempted from stale | | -| [exempt-issue-assignees](#exempt-issue-assignees) | Override [exempt-assignees](#exempt-assignees) for issues only | | -| [exempt-pr-assignees](#exempt-pr-assignees) | Override [exempt-assignees](#exempt-assignees) for PRs only | | -| [exempt-all-assignees](#exempt-all-assignees) | Exempt all issues/PRs with assignees from stale | | -| [exempt-all-issue-assignees](#exempt-all-issue-assignees) | Override [exempt-all-assignees](#exempt-all-assignees) for issues only | | -| [exempt-all-pr-assignees](#exempt-all-pr-assignees) | Override [exempt-all-assignees](#exempt-all-assignees) for PRs only | | -| [enable-statistics](#enable-statistics) | Display statistics in the logs | `true` | +| Input | Description | Default | +| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------- | +| [repo-token](#repo-token) | PAT for GitHub API authentication | `${{ github.token }}` | +| [days-before-stale](#days-before-stale) | Idle number of days before marking issues/PRs stale | `60` | +| [days-before-issue-stale](#days-before-issue-stale) | Override [days-before-stale](#days-before-stale) for issues only | | +| [days-before-pr-stale](#days-before-pr-stale) | Override [days-before-stale](#days-before-stale) for PRs only | | +| [days-before-close](#days-before-close) | Idle number of days before closing stale issues/PRs | `7` | +| [days-before-issue-close](#days-before-issue-close) | Override [days-before-close](#days-before-close) for issues only | | +| [days-before-pr-close](#days-before-pr-close) | Override [days-before-close](#days-before-close) for PRs only | | +| [stale-issue-message](#stale-issue-message) | Comment on the staled issues | | +| [stale-pr-message](#stale-pr-message) | Comment on the staled PRs | | +| [close-issue-message](#close-issue-message) | Comment on the staled issues while closed | | +| [close-pr-message](#close-pr-message) | Comment on the staled PRs while closed | | +| [stale-issue-label](#stale-issue-label) | Label to apply on staled issues | `Stale` | +| [close-issue-label](#close-issue-label) | Label to apply on closed issues | | +| [stale-pr-label](#stale-pr-label) | Label to apply on staled PRs | `Stale` | +| [close-pr-label](#close-pr-label) | Label to apply on closed PRs | | +| [exempt-issue-labels](#exempt-issue-labels) | Labels on issues exempted from stale | | +| [exempt-pr-labels](#exempt-pr-labels) | Labels on PRs exempted from stale | | +| [only-labels](#only-labels) | Only issues/PRs with ALL these labels are checked | | +| [only-issue-labels](#only-issue-labels) | Only issues with ALL these labels are checked | | +| [only-pr-labels](#only-pr-labels) | Only PRs with ALL these labels are checked | | +| [any-of-labels](#any-of-labels) | Only issues/PRs with ANY of these labels are checked | | +| [any-of-issue-labels](#any-of-issue-labels) | Only issues with ANY of these labels are checked | | +| [any-of-pr-labels](#any-of-pr-labels) | Only PRs with ANY of these labels are checked | | +| [operations-per-run](#operations-per-run) | Max number of operations per run | `30` | +| [remove-stale-when-updated](#remove-stale-when-updated) | Remove stale label from issues/PRs on updates | `true` | +| [remove-issue-stale-when-updated](#remove-issue-stale-when-updated) | Override [remove-stale-when-updated](#remove-stale-when-updated) for issues only | | +| [remove-pr-stale-when-updated](#remove-pr-stale-when-updated) | Override [remove-stale-when-updated](#remove-stale-when-updated) for PRs only | | +| [remove-stale-when-commented](#remove-stale-when-commented) | Remove stale label from issues/PRs on comments | `true` | +| [remove-issue-stale-when-commented](#remove-issue-stale-when-commented) | Override [remove-stale-when-commented](#remove-stale-when-commented) for issues only | | +| [remove-pr-stale-when-commented](#remove-pr-stale-when-commented) | Override [remove-stale-when-commented](#remove-stale-when-commented) for PRs only | | +| [debug-only](#debug-only) | Dry-run | `false` | +| [ascending](#ascending) | Order to get issues/PRs | `false` | +| [start-date](#start-date) | Skip stale action for issues/PRs created before it | | +| [delete-branch](#delete-branch) | Delete branch after closing a stale PR | `false` | +| [exempt-milestones](#exempt-milestones) | Milestones on issues/PRs exempted from stale | | +| [exempt-issue-milestones](#exempt-issue-milestones) | Override [exempt-milestones](#exempt-milestones) for issues only | | +| [exempt-pr-milestones](#exempt-pr-milestones) | Override [exempt-milestones](#exempt-milestones) for PRs only | | +| [exempt-all-milestones](#exempt-all-milestones) | Exempt all issues/PRs with milestones from stale | | +| [exempt-all-issue-milestones](#exempt-all-issue-milestones) | Override [exempt-all-milestones](#exempt-all-milestones) for issues only | | +| [exempt-all-pr-milestones](#exempt-all-pr-milestones) | Override [exempt-all-milestones](#exempt-all-milestones) for PRs only | | +| [exempt-assignees](#exempt-assignees) | Assignees on issues/PRs exempted from stale | | +| [exempt-issue-assignees](#exempt-issue-assignees) | Override [exempt-assignees](#exempt-assignees) for issues only | | +| [exempt-pr-assignees](#exempt-pr-assignees) | Override [exempt-assignees](#exempt-assignees) for PRs only | | +| [exempt-all-assignees](#exempt-all-assignees) | Exempt all issues/PRs with assignees from stale | | +| [exempt-all-issue-assignees](#exempt-all-issue-assignees) | Override [exempt-all-assignees](#exempt-all-assignees) for issues only | | +| [exempt-all-pr-assignees](#exempt-all-pr-assignees) | Override [exempt-all-assignees](#exempt-all-assignees) for PRs only | | +| [enable-statistics](#enable-statistics) | Display statistics in the logs | `true` | ### List of output options @@ -291,19 +294,37 @@ Default value: `30` #### remove-stale-when-updated -Automatically remove the stale label when the issues or the pull requests are updated (based on [GitHub issue](https://docs.github.com/en/rest/reference/issues) field `updated_at`) or commented. +Automatically remove the stale label when the issues or the pull requests are updated (based on [GitHub issue](https://docs.github.com/en/rest/reference/issues) field `updated_at`). Default value: `true` #### remove-issue-stale-when-updated -Override [remove-stale-when-updated](#remove-stale-when-updated) but only to automatically remove the stale label when the issues are updated (based on [GitHub issue](https://docs.github.com/en/rest/reference/issues) field `updated_at`) or commented. +Override [remove-stale-when-updated](#remove-stale-when-updated) but only to automatically remove the stale label when the issues are updated (based on [GitHub issue](https://docs.github.com/en/rest/reference/issues) field `updated_at`). Default value: unset #### remove-pr-stale-when-updated -Override [remove-stale-when-updated](#remove-stale-when-updated) but only to automatically remove the stale label when the pull requests are updated (based on [GitHub issue](https://docs.github.com/en/rest/reference/issues) field `updated_at`) or commented. +Override [remove-stale-when-updated](#remove-stale-when-updated) but only to automatically remove the stale label when the pull requests are updated (based on [GitHub issue](https://docs.github.com/en/rest/reference/issues) field `updated_at`). + +Default value: unset + +#### remove-stale-when-commented + +Automatically remove the stale label when the issues or the pull requests are commented. + +Default value: `true` + +#### remove-issue-stale-when-commented + +Override [remove-stale-when-commented](#remove-stale-when-commented) but only to automatically remove the stale label when the issues are commented. + +Default value: unset + +#### remove-pr-stale-when-commented + +Override [remove-stale-when-commented](#remove-stale-when-commented) but only to automatically remove the stale label when the pull requests are commented. Default value: unset diff --git a/__tests__/any-of-labels.spec.ts b/__tests__/any-of-labels.spec.ts index cfed44bde..6aa15601b 100644 --- a/__tests__/any-of-labels.spec.ts +++ b/__tests__/any-of-labels.spec.ts @@ -1112,14 +1112,12 @@ class IssuesProcessorBuilder { issues(issues: Partial[]): IssuesProcessorBuilder { this.issuesOrPrs( - issues.map( - (issue: Readonly>): Partial => { - return { - ...issue, - pull_request: null - }; - } - ) + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + pull_request: null + }; + }) ); return this; @@ -1127,14 +1125,12 @@ class IssuesProcessorBuilder { prs(issues: Partial[]): IssuesProcessorBuilder { this.issuesOrPrs( - issues.map( - (issue: Readonly>): Partial => { - return { - ...issue, - pull_request: {key: 'value'} - }; - } - ) + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + pull_request: {key: 'value'} + }; + }) ); return this; diff --git a/__tests__/constants/default-processor-options.ts b/__tests__/constants/default-processor-options.ts index 38067653e..ba34b8728 100644 --- a/__tests__/constants/default-processor-options.ts +++ b/__tests__/constants/default-processor-options.ts @@ -29,6 +29,9 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({ removeStaleWhenUpdated: false, removeIssueStaleWhenUpdated: undefined, removePrStaleWhenUpdated: undefined, + removeStaleWhenCommented: false, + removeIssueStaleWhenCommented: undefined, + removePrStaleWhenCommented: undefined, ascending: false, deleteBranch: false, startDate: '', diff --git a/__tests__/functions/generate-issue.ts b/__tests__/functions/generate-issue.ts index 3b3fe433d..6817d8e4e 100644 --- a/__tests__/functions/generate-issue.ts +++ b/__tests__/functions/generate-issue.ts @@ -32,12 +32,10 @@ export function generateIssue( title: milestone } : undefined, - assignees: assignees.map( - (assignee: Readonly): IAssignee => { - return { - login: assignee - }; - } - ) + assignees: assignees.map((assignee: Readonly): IAssignee => { + return { + login: assignee + }; + }) }); } diff --git a/__tests__/main.spec.ts b/__tests__/main.spec.ts index 1c56531f7..216496d5c 100644 --- a/__tests__/main.spec.ts +++ b/__tests__/main.spec.ts @@ -1220,7 +1220,7 @@ test('stale issues should not be closed if days is set to -1', async () => { }); test('stale label should be removed if a comment was added to a stale issue', async () => { - const opts = {...DefaultProcessorOptions, removeStaleWhenUpdated: true}; + const opts = {...DefaultProcessorOptions, removeStaleWhenCommented: true}; const TestIssueList: Issue[] = [ generateIssue( opts, @@ -1255,8 +1255,37 @@ test('stale label should be removed if a comment was added to a stale issue', as expect(processor.removedLabelIssues).toHaveLength(1); }); -test('stale label should not be removed if a comment was added by the bot (and the issue should be closed)', async () => { +test('stale label should be removed if a stale issue was updated', async () => { const opts = {...DefaultProcessorOptions, removeStaleWhenUpdated: true}; + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'An issue that should un-stale', + new Date().toDateString(), + '2020-01-01T17:00:00Z', + false, + ['Stale'] + ) + ]; + const processor = new IssuesProcessorMock( + opts, + async () => 'abot', + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => '2020-01-02T17:00:00Z' + ); + + // process our fake issue list + await processor.processIssues(1); + + expect(processor.closedIssues).toHaveLength(0); + expect(processor.staleIssues).toHaveLength(0); + expect(processor.removedLabelIssues).toHaveLength(1); +}); + +test('stale label should not be removed if a comment was added by the bot (and the issue should be closed)', async () => { + const opts = {...DefaultProcessorOptions, removeStaleWhenCommented: true}; github.context.actor = 'abot'; const TestIssueList: Issue[] = [ generateIssue( @@ -1295,7 +1324,7 @@ test('stale label should not be removed if a comment was added by the bot (and t test('stale label containing a space should be removed if a comment was added to a stale issue', async () => { const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, - removeStaleWhenUpdated: true, + removeStaleWhenCommented: true, staleIssueLabel: 'stat: stale' }; const TestIssueList: Issue[] = [ @@ -2234,7 +2263,7 @@ test('processing an issue stale since less than the daysBeforeStale with a stale daysBeforeStale: 30, daysBeforeClose: 7, closeIssueMessage: 'close message', - removeStaleWhenUpdated: false + removeStaleWhenCommented: false }; const now: Date = new Date(); const updatedAt: Date = new Date(now.setDate(now.getDate() - 9)); @@ -2276,7 +2305,7 @@ test('processing an issue stale since less than the daysBeforeStale without a st daysBeforeStale: 30, daysBeforeClose: 7, closeIssueMessage: 'close message', - removeStaleWhenUpdated: false + removeStaleWhenCommented: false }; const now: Date = new Date(); const updatedAt: Date = new Date(now.setDate(now.getDate() - 9)); diff --git a/__tests__/only-labels.spec.ts b/__tests__/only-labels.spec.ts index d8ceddc9d..042642bad 100644 --- a/__tests__/only-labels.spec.ts +++ b/__tests__/only-labels.spec.ts @@ -1112,14 +1112,12 @@ class IssuesProcessorBuilder { issues(issues: Partial[]): IssuesProcessorBuilder { this.issuesOrPrs( - issues.map( - (issue: Readonly>): Partial => { - return { - ...issue, - pull_request: null - }; - } - ) + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + pull_request: null + }; + }) ); return this; @@ -1127,14 +1125,12 @@ class IssuesProcessorBuilder { prs(issues: Partial[]): IssuesProcessorBuilder { this.issuesOrPrs( - issues.map( - (issue: Readonly>): Partial => { - return { - ...issue, - pull_request: {key: 'value'} - }; - } - ) + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + pull_request: {key: 'value'} + }; + }) ); return this; diff --git a/__tests__/operations-per-run.spec.ts b/__tests__/operations-per-run.spec.ts index 46035cb5d..e73593c77 100644 --- a/__tests__/operations-per-run.spec.ts +++ b/__tests__/operations-per-run.spec.ts @@ -188,18 +188,16 @@ class SUT { } private _setTestIssueList(): SUT { - this._testIssueList = this._sutIssues.map( - (sutIssue: SUTIssue): Issue => { - return generateIssue( - this._opts, - 1, - 'My first issue', - sutIssue.updatedAt, - sutIssue.updatedAt, - false - ); - } - ); + this._testIssueList = this._sutIssues.map((sutIssue: SUTIssue): Issue => { + return generateIssue( + this._opts, + 1, + 'My first issue', + sutIssue.updatedAt, + sutIssue.updatedAt, + false + ); + }); return this; } diff --git a/__tests__/remove-stale-when-commented.spec.ts b/__tests__/remove-stale-when-commented.spec.ts new file mode 100644 index 000000000..531223d2c --- /dev/null +++ b/__tests__/remove-stale-when-commented.spec.ts @@ -0,0 +1,556 @@ +import {Issue} from '../src/classes/issue'; +import {IIssue} from '../src/interfaces/issue'; +import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; +import {ILabel} from '../src/interfaces/label'; +import {IssuesProcessorMock} from './classes/issues-processor-mock'; +import {DefaultProcessorOptions} from './constants/default-processor-options'; +import {generateIssue} from './functions/generate-issue'; + +let issuesProcessorBuilder: IssuesProcessorBuilder; +let issuesProcessor: IssuesProcessorMock; + +/** + * @description + * Assuming there is a comment on the issue + */ +describe('remove-stale-when-commented option', (): void => { + beforeEach((): void => { + issuesProcessorBuilder = new IssuesProcessorBuilder(); + }); + + describe('when the option "remove-stale-when-commented" is disabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.keepStaleWhenCommented(); + }); + + test('should not remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + + test('should not remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + }); + + describe('when the option "remove-stale-when-commented" is enabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.removeStaleWhenCommented(); + }); + + test('should remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + + test('should remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + }); +}); + +describe('remove-issue-stale-when-commented option', (): void => { + beforeEach((): void => { + issuesProcessorBuilder = new IssuesProcessorBuilder(); + }); + + describe('when the option "remove-stale-when-commented" is disabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.keepStaleWhenCommented(); + }); + + describe('when the option "remove-issue-stale-when-commented" is unset', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.unsetIssueStaleWhenCommented(); + }); + + test('should not remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + + test('should not remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + }); + + describe('when the option "remove-issue-stale-when-commented" is disabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.keepIssueStaleWhenCommented(); + }); + + test('should not remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + + test('should not remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + }); + + describe('when the option "remove-issue-stale-when-commented" is enabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.removeIssueStaleWhenCommented(); + }); + + test('should remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + + test('should not remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + }); + }); + + describe('when the option "remove-stale-when-commented" is enabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.removeStaleWhenCommented(); + }); + + describe('when the option "remove-issue-stale-when-commented" is unset', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.unsetIssueStaleWhenCommented(); + }); + + test('should remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + + test('should remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + }); + + describe('when the option "remove-issue-stale-when-commented" is disabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.keepIssueStaleWhenCommented(); + }); + + test('should not remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + + test('should remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + }); + + describe('when the option "remove-issue-stale-when-commented" is enabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.removeIssueStaleWhenCommented(); + }); + + test('should remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + + test('should remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + }); + }); +}); + +describe('remove-pr-stale-when-commented option', (): void => { + beforeEach((): void => { + issuesProcessorBuilder = new IssuesProcessorBuilder(); + }); + + describe('when the option "remove-stale-when-commented" is disabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.keepStaleWhenCommented(); + }); + + describe('when the option "remove-pr-stale-when-commented" is unset', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.unsetPrStaleWhenCommented(); + }); + + test('should not remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + + test('should not remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + }); + + describe('when the option "remove-pr-stale-when-commented" is disabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.keepPrStaleWhenCommented(); + }); + + test('should not remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + + test('should not remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + }); + + describe('when the option "remove-pr-stale-when-commented" is enabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.removePrStaleWhenCommented(); + }); + + test('should not remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + + test('should remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + }); + }); + + describe('when the option "remove-stale-when-commented" is enabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.removeStaleWhenCommented(); + }); + + describe('when the option "remove-pr-stale-when-commented" is unset', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.unsetPrStaleWhenCommented(); + }); + + test('should remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + + test('should remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + }); + + describe('when the option "remove-pr-stale-when-commented" is disabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.keepPrStaleWhenCommented(); + }); + + test('should remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + + test('should not remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(0); + }); + }); + + describe('when the option "remove-pr-stale-when-commented" is enabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.removePrStaleWhenCommented(); + }); + + test('should remove the stale label on the issue', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + + test('should remove the stale label on the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.removedLabelIssues).toHaveLength(1); + }); + }); + }); +}); + +class IssuesProcessorBuilder { + private _options: IIssuesProcessorOptions = { + ...DefaultProcessorOptions + }; + private _issues: Issue[] = []; + + keepStaleWhenCommented(): IssuesProcessorBuilder { + this._options.removeStaleWhenCommented = false; + + return this; + } + + removeStaleWhenCommented(): IssuesProcessorBuilder { + this._options.removeStaleWhenCommented = true; + + return this; + } + + unsetIssueStaleWhenCommented(): IssuesProcessorBuilder { + delete this._options.removeIssueStaleWhenCommented; + + return this; + } + + keepIssueStaleWhenCommented(): IssuesProcessorBuilder { + this._options.removeIssueStaleWhenCommented = false; + + return this; + } + + removeIssueStaleWhenCommented(): IssuesProcessorBuilder { + this._options.removeIssueStaleWhenCommented = true; + + return this; + } + + unsetPrStaleWhenCommented(): IssuesProcessorBuilder { + delete this._options.removePrStaleWhenCommented; + + return this; + } + + keepPrStaleWhenCommented(): IssuesProcessorBuilder { + this._options.removePrStaleWhenCommented = false; + + return this; + } + + removePrStaleWhenCommented(): IssuesProcessorBuilder { + this._options.removePrStaleWhenCommented = true; + + return this; + } + + issuesOrPrs(issues: Partial[]): IssuesProcessorBuilder { + this._issues = issues.map( + (issue: Readonly>, index: Readonly): Issue => + generateIssue( + this._options, + index, + issue.title ?? 'dummy-title', + issue.updated_at ?? new Date().toDateString(), + issue.created_at ?? new Date().toDateString(), + !!issue.pull_request, + issue.labels ? issue.labels.map(label => label.name) : [] + ) + ); + + return this; + } + + issues(issues: Partial[]): IssuesProcessorBuilder { + this.issuesOrPrs( + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + pull_request: null + }; + }) + ); + + return this; + } + + staleIssues(issues: Partial[]): IssuesProcessorBuilder { + this.issues( + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + updated_at: '2020-01-01T17:00:00Z', + created_at: '2020-01-01T17:00:00Z', + labels: issue.labels?.map((label: Readonly): ILabel => { + return { + ...label, + name: 'Stale' + }; + }) ?? [ + { + name: 'Stale' + } + ] + }; + }) + ); + + return this; + } + + prs(issues: Partial[]): IssuesProcessorBuilder { + this.issuesOrPrs( + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + pull_request: {key: 'value'} + }; + }) + ); + + return this; + } + + stalePrs(issues: Partial[]): IssuesProcessorBuilder { + this.prs( + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + updated_at: '2020-01-01T17:00:00Z', + created_at: '2020-01-01T17:00:00Z', + labels: issue.labels?.map((label: Readonly): ILabel => { + return { + ...label, + name: 'Stale' + }; + }) ?? [ + { + name: 'Stale' + } + ] + }; + }) + ); + + return this; + } + + build(): IssuesProcessorMock { + return new IssuesProcessorMock( + this._options, + async () => 'abot', + async p => (p === 1 ? this._issues : []), + async () => [ + { + // Note this comment + user: { + login: 'notme', + type: 'User' + } + } + ], + async () => new Date().toDateString() + ); + } +} diff --git a/__tests__/remove-stale-when-updated.spec.ts b/__tests__/remove-stale-when-updated.spec.ts index 24eeef592..26eb0aff8 100644 --- a/__tests__/remove-stale-when-updated.spec.ts +++ b/__tests__/remove-stale-when-updated.spec.ts @@ -11,7 +11,7 @@ let issuesProcessor: IssuesProcessorMock; /** * @description - * Assuming there is a comment on the issue + * Assuming there is an update on the issue */ describe('remove-stale-when-updated option', (): void => { beforeEach((): void => { @@ -464,14 +464,12 @@ class IssuesProcessorBuilder { issues(issues: Partial[]): IssuesProcessorBuilder { this.issuesOrPrs( - issues.map( - (issue: Readonly>): Partial => { - return { - ...issue, - pull_request: null - }; - } - ) + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + pull_request: null + }; + }) ); return this; @@ -479,27 +477,24 @@ class IssuesProcessorBuilder { staleIssues(issues: Partial[]): IssuesProcessorBuilder { this.issues( - issues.map( - (issue: Readonly>): Partial => { - return { - ...issue, - updated_at: '2020-01-01T17:00:00Z', - created_at: '2020-01-01T17:00:00Z', - labels: issue.labels?.map( - (label: Readonly): ILabel => { - return { - ...label, - name: 'Stale' - }; - } - ) ?? [ - { - name: 'Stale' - } - ] - }; - } - ) + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + // Note this update + updated_at: new Date().toDateString(), + created_at: new Date().toDateString(), + labels: issue.labels?.map((label: Readonly): ILabel => { + return { + ...label, + name: 'Stale' + }; + }) ?? [ + { + name: 'Stale' + } + ] + }; + }) ); return this; @@ -507,14 +502,12 @@ class IssuesProcessorBuilder { prs(issues: Partial[]): IssuesProcessorBuilder { this.issuesOrPrs( - issues.map( - (issue: Readonly>): Partial => { - return { - ...issue, - pull_request: {key: 'value'} - }; - } - ) + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + pull_request: {key: 'value'} + }; + }) ); return this; @@ -522,27 +515,24 @@ class IssuesProcessorBuilder { stalePrs(issues: Partial[]): IssuesProcessorBuilder { this.prs( - issues.map( - (issue: Readonly>): Partial => { - return { - ...issue, - updated_at: '2020-01-01T17:00:00Z', - created_at: '2020-01-01T17:00:00Z', - labels: issue.labels?.map( - (label: Readonly): ILabel => { - return { - ...label, - name: 'Stale' - }; - } - ) ?? [ - { - name: 'Stale' - } - ] - }; - } - ) + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + // Note this update + updated_at: new Date().toDateString(), + created_at: new Date().toDateString(), + labels: issue.labels?.map((label: Readonly): ILabel => { + return { + ...label, + name: 'Stale' + }; + }) ?? [ + { + name: 'Stale' + } + ] + }; + }) ); return this; @@ -553,14 +543,7 @@ class IssuesProcessorBuilder { this._options, async () => 'abot', async p => (p === 1 ? this._issues : []), - async () => [ - { - user: { - login: 'notme', - type: 'User' - } - } - ], + async () => [], async () => new Date().toDateString() ); } diff --git a/action.yml b/action.yml index 4adb85a7d..d346279ca 100644 --- a/action.yml +++ b/action.yml @@ -113,15 +113,27 @@ inputs: default: '30' required: false remove-stale-when-updated: - description: 'Remove stale labels from issues and pull requests when they are updated or commented on.' + description: 'Remove stale labels from issues and pull requests when they are updated.' default: 'true' required: false remove-issue-stale-when-updated: - description: 'Remove stale labels from issues when they are updated or commented on. Override "remove-stale-when-updated" option regarding only the issues.' + description: 'Remove stale labels from issues when they are updated. Override "remove-stale-when-updated" option regarding only the issues.' default: '' required: false remove-pr-stale-when-updated: - description: 'Remove stale labels from pull requests when they are updated or commented on. Override "remove-stale-when-updated" option regarding only the pull requests.' + description: 'Remove stale labels from pull requests when they are updated. Override "remove-stale-when-updated" option regarding only the pull requests.' + default: '' + required: false + remove-stale-when-commented: + description: 'Remove stale labels from issues and pull requests when they are commented on.' + default: 'true' + required: false + remove-issue-stale-when-commented: + description: 'Remove stale labels from issues when they are commented on. Override "remove-stale-when-commented" option regarding only the issues.' + default: '' + required: false + remove-pr-stale-when-commented: + description: 'Remove stale labels from pull requests when they are commented on. Override "remove-stale-when-commented" option regarding only the pull requests.' default: '' required: false debug-only: diff --git a/dist/index.js b/dist/index.js index f31a6a812..75556c4ed 100644 --- a/dist/index.js +++ b/dist/index.js @@ -568,14 +568,28 @@ class IssuesProcessor { const shouldRemoveStaleWhenUpdated = this._shouldRemoveStaleWhenUpdated(issue); issueLogger.info(`The option ${issueLogger.createOptionLink(this._getRemoveStaleWhenUpdatedUsedOptionName(issue))} is: ${logger_service_1.LoggerService.cyan(shouldRemoveStaleWhenUpdated)}`); if (shouldRemoveStaleWhenUpdated) { - issueLogger.info(`The stale label should not be removed`); + issueLogger.info(`The stale label should not be removed due to an update`); + } + else { + issueLogger.info(`The stale label should be removed if all conditions met`); + } + const shouldRemoveStaleWhenCommented = this._shouldRemoveStaleWhenCommented(issue); + issueLogger.info(`The option ${issueLogger.createOptionLink(this._getRemoveStaleWhenCommentedUsedOptionName(issue))} is: ${logger_service_1.LoggerService.cyan(shouldRemoveStaleWhenCommented)}`); + if (shouldRemoveStaleWhenCommented) { + issueLogger.info(`The stale label should not be removed due to a comment`); } else { issueLogger.info(`The stale label should be removed if all conditions met`); } // Should we un-stale this issue? - if (shouldRemoveStaleWhenUpdated && issueHasComments) { - issueLogger.info(`Remove the stale label since the $$type has a comment and the workflow should remove the stale label when updated`); + if (shouldRemoveStaleWhenUpdated && issueHasUpdate) { + issueLogger.info(`Remove the stale label since the $$type has an update and the workflow should remove the stale label when updated`); + yield this._removeStaleLabel(issue, staleLabel); + issueLogger.info(`Skipping the process since the $$type is now un-stale`); + return; // Nothing to do because it is no longer stale + } + else if (shouldRemoveStaleWhenCommented && issueHasComments) { + issueLogger.info(`Remove the stale label since the $$type has a comment and the workflow should remove the stale label when commented`); yield this._removeStaleLabel(issue, staleLabel); issueLogger.info(`Skipping the process since the $$type is now un-stale`); return; // Nothing to do because it is no longer stale @@ -845,6 +859,18 @@ class IssuesProcessor { } return this.options.removeStaleWhenUpdated; } + _shouldRemoveStaleWhenCommented(issue) { + if (issue.isPullRequest) { + if (is_boolean_1.isBoolean(this.options.removePrStaleWhenCommented)) { + return this.options.removePrStaleWhenCommented; + } + return this.options.removeStaleWhenCommented; + } + if (is_boolean_1.isBoolean(this.options.removeIssueStaleWhenCommented)) { + return this.options.removeIssueStaleWhenCommented; + } + return this.options.removeStaleWhenCommented; + } _removeStaleLabel(issue, staleLabel) { var _a; return __awaiter(this, void 0, void 0, function* () { @@ -906,6 +932,18 @@ class IssuesProcessor { } return option_1.Option.RemoveStaleWhenUpdated; } + _getRemoveStaleWhenCommentedUsedOptionName(issue) { + if (issue.isPullRequest) { + if (is_boolean_1.isBoolean(this.options.removePrStaleWhenCommented)) { + return option_1.Option.RemovePrStaleWhenCommented; + } + return option_1.Option.RemoveStaleWhenCommented; + } + if (is_boolean_1.isBoolean(this.options.removeIssueStaleWhenCommented)) { + return option_1.Option.RemoveIssueStaleWhenCommented; + } + return option_1.Option.RemoveStaleWhenCommented; + } } exports.IssuesProcessor = IssuesProcessor; @@ -1635,6 +1673,9 @@ var Option; Option["RemoveStaleWhenUpdated"] = "remove-stale-when-updated"; Option["RemoveIssueStaleWhenUpdated"] = "remove-issue-stale-when-updated"; Option["RemovePrStaleWhenUpdated"] = "remove-pr-stale-when-updated"; + Option["RemoveStaleWhenCommented"] = "remove-stale-when-commented"; + Option["RemoveIssueStaleWhenCommented"] = "remove-issue-stale-when-commented"; + Option["RemovePrStaleWhenCommented"] = "remove-pr-stale-when-commented"; Option["DebugOnly"] = "debug-only"; Option["Ascending"] = "ascending"; Option["DeleteBranch"] = "delete-branch"; @@ -1917,6 +1958,9 @@ function _getAndValidateArgs() { removeStaleWhenUpdated: !(core.getInput('remove-stale-when-updated') === 'false'), removeIssueStaleWhenUpdated: _toOptionalBoolean(core.getInput('remove-issue-stale-when-updated')), removePrStaleWhenUpdated: _toOptionalBoolean(core.getInput('remove-pr-stale-when-updated')), + removeStaleWhenCommented: !(core.getInput('remove-stale-when-commented') === 'false'), + removeIssueStaleWhenCommented: _toOptionalBoolean(core.getInput('remove-issue-stale-when-commented')), + removePrStaleWhenCommented: _toOptionalBoolean(core.getInput('remove-pr-stale-when-commented')), debugOnly: core.getInput('debug-only') === 'true', ascending: core.getInput('ascending') === 'true', deleteBranch: core.getInput('delete-branch') === 'true', @@ -8897,4 +8941,4 @@ module.exports = require("zlib");; /******/ // Load entry module and return exports /******/ return __nccwpck_require__(3109); /******/ })() -; +; \ No newline at end of file diff --git a/src/classes/issue.spec.ts b/src/classes/issue.spec.ts index 1fd8690f4..8cf0167f5 100644 --- a/src/classes/issue.spec.ts +++ b/src/classes/issue.spec.ts @@ -37,6 +37,9 @@ describe('Issue', (): void => { removeStaleWhenUpdated: false, removeIssueStaleWhenUpdated: undefined, removePrStaleWhenUpdated: undefined, + removeStaleWhenCommented: false, + removeIssueStaleWhenCommented: undefined, + removePrStaleWhenCommented: undefined, repoToken: '', staleIssueMessage: '', stalePrMessage: '', diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts index 98f721780..735f05fac 100644 --- a/src/classes/issues-processor.ts +++ b/src/classes/issues-processor.ts @@ -37,7 +37,8 @@ export class IssuesProcessor { } private static _endIssueProcessing(issue: Issue): void { - const consumedOperationsCount: number = issue.operations.getConsumedOperationsCount(); + const consumedOperationsCount: number = + issue.operations.getConsumedOperationsCount(); if (consumedOperationsCount > 0) { const issueLogger: IssueLogger = new IssueLogger(issue); @@ -484,16 +485,15 @@ export class IssuesProcessor { try { this.operations.consumeOperation(); - const issueResult: OctoKitIssueList = await this.client.issues.listForRepo( - { + const issueResult: OctoKitIssueList = + await this.client.issues.listForRepo({ owner: context.repo.owner, repo: context.repo.repo, state: 'open', per_page: 100, direction: this.options.ascending ? 'asc' : 'desc', page - } - ); + }); this._statistics?.incrementFetchedItemsCount(issueResult.data.length); return issueResult.data.map( @@ -579,9 +579,8 @@ export class IssuesProcessor { `$$type has been updated: ${LoggerService.cyan(issueHasUpdate)}` ); - const shouldRemoveStaleWhenUpdated: boolean = this._shouldRemoveStaleWhenUpdated( - issue - ); + const shouldRemoveStaleWhenUpdated: boolean = + this._shouldRemoveStaleWhenUpdated(issue); issueLogger.info( `The option ${issueLogger.createOptionLink( @@ -590,7 +589,28 @@ export class IssuesProcessor { ); if (shouldRemoveStaleWhenUpdated) { - issueLogger.info(`The stale label should not be removed`); + issueLogger.info( + `The stale label should not be removed due to an update` + ); + } else { + issueLogger.info( + `The stale label should be removed if all conditions met` + ); + } + + const shouldRemoveStaleWhenCommented: boolean = + this._shouldRemoveStaleWhenCommented(issue); + + issueLogger.info( + `The option ${issueLogger.createOptionLink( + this._getRemoveStaleWhenCommentedUsedOptionName(issue) + )} is: ${LoggerService.cyan(shouldRemoveStaleWhenCommented)}` + ); + + if (shouldRemoveStaleWhenCommented) { + issueLogger.info( + `The stale label should not be removed due to a comment` + ); } else { issueLogger.info( `The stale label should be removed if all conditions met` @@ -598,9 +618,18 @@ export class IssuesProcessor { } // Should we un-stale this issue? - if (shouldRemoveStaleWhenUpdated && issueHasComments) { + if (shouldRemoveStaleWhenUpdated && issueHasUpdate) { + issueLogger.info( + `Remove the stale label since the $$type has an update and the workflow should remove the stale label when updated` + ); + await this._removeStaleLabel(issue, staleLabel); + + issueLogger.info(`Skipping the process since the $$type is now un-stale`); + + return; // Nothing to do because it is no longer stale + } else if (shouldRemoveStaleWhenCommented && issueHasComments) { issueLogger.info( - `Remove the stale label since the $$type has a comment and the workflow should remove the stale label when updated` + `Remove the stale label since the $$type has a comment and the workflow should remove the stale label when commented` ); await this._removeStaleLabel(issue, staleLabel); @@ -952,6 +981,22 @@ export class IssuesProcessor { return this.options.removeStaleWhenUpdated; } + private _shouldRemoveStaleWhenCommented(issue: Issue): boolean { + if (issue.isPullRequest) { + if (isBoolean(this.options.removePrStaleWhenCommented)) { + return this.options.removePrStaleWhenCommented; + } + + return this.options.removeStaleWhenCommented; + } + + if (isBoolean(this.options.removeIssueStaleWhenCommented)) { + return this.options.removeIssueStaleWhenCommented; + } + + return this.options.removeStaleWhenCommented; + } + private async _removeStaleLabel( issue: Issue, staleLabel: Readonly @@ -1063,4 +1108,25 @@ export class IssuesProcessor { return Option.RemoveStaleWhenUpdated; } + + private _getRemoveStaleWhenCommentedUsedOptionName( + issue: Readonly + ): + | Option.RemovePrStaleWhenCommented + | Option.RemoveStaleWhenCommented + | Option.RemoveIssueStaleWhenCommented { + if (issue.isPullRequest) { + if (isBoolean(this.options.removePrStaleWhenCommented)) { + return Option.RemovePrStaleWhenCommented; + } + + return Option.RemoveStaleWhenCommented; + } + + if (isBoolean(this.options.removeIssueStaleWhenCommented)) { + return Option.RemoveIssueStaleWhenCommented; + } + + return Option.RemoveStaleWhenCommented; + } } diff --git a/src/classes/milestones.ts b/src/classes/milestones.ts index af804d5de..db002ea4a 100644 --- a/src/classes/milestones.ts +++ b/src/classes/milestones.ts @@ -195,9 +195,8 @@ export class Milestones { return false; } - const cleanMilestone: CleanMilestone = Milestones._cleanMilestone( - milestone - ); + const cleanMilestone: CleanMilestone = + Milestones._cleanMilestone(milestone); const isSameMilestone: boolean = cleanMilestone === diff --git a/src/enums/option.ts b/src/enums/option.ts index 0ff794a03..15b624b54 100644 --- a/src/enums/option.ts +++ b/src/enums/option.ts @@ -24,6 +24,9 @@ export enum Option { RemoveStaleWhenUpdated = 'remove-stale-when-updated', RemoveIssueStaleWhenUpdated = 'remove-issue-stale-when-updated', RemovePrStaleWhenUpdated = 'remove-pr-stale-when-updated', + RemoveStaleWhenCommented = 'remove-stale-when-commented', + RemoveIssueStaleWhenCommented = 'remove-issue-stale-when-commented', + RemovePrStaleWhenCommented = 'remove-pr-stale-when-commented', DebugOnly = 'debug-only', Ascending = 'ascending', DeleteBranch = 'delete-branch', diff --git a/src/functions/is-labeled.spec.ts b/src/functions/is-labeled.spec.ts index 249fcd08a..30719f94d 100644 --- a/src/functions/is-labeled.spec.ts +++ b/src/functions/is-labeled.spec.ts @@ -7,9 +7,9 @@ describe('isLabeled()', (): void => { describe('when the given issue contains no label', (): void => { beforeEach((): void => { - issue = ({ + issue = { labels: [] - } as unknown) as Issue; + } as unknown as Issue; }); describe('when the given label is a simple label', (): void => { diff --git a/src/interfaces/issues-processor-options.ts b/src/interfaces/issues-processor-options.ts index 6ce79db18..82db2a258 100644 --- a/src/interfaces/issues-processor-options.ts +++ b/src/interfaces/issues-processor-options.ts @@ -28,6 +28,9 @@ export interface IIssuesProcessorOptions { removeStaleWhenUpdated: boolean; removeIssueStaleWhenUpdated: boolean | undefined; removePrStaleWhenUpdated: boolean | undefined; + removeStaleWhenCommented: boolean; + removeIssueStaleWhenCommented: boolean | undefined; + removePrStaleWhenCommented: boolean | undefined; debugOnly: boolean; ascending: boolean; deleteBranch: boolean; diff --git a/src/main.ts b/src/main.ts index 98e667434..0d11616f6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -62,6 +62,15 @@ function _getAndValidateArgs(): IIssuesProcessorOptions { removePrStaleWhenUpdated: _toOptionalBoolean( core.getInput('remove-pr-stale-when-updated') ), + removeStaleWhenCommented: !( + core.getInput('remove-stale-when-commented') === 'false' + ), + removeIssueStaleWhenCommented: _toOptionalBoolean( + core.getInput('remove-issue-stale-when-commented') + ), + removePrStaleWhenCommented: _toOptionalBoolean( + core.getInput('remove-pr-stale-when-commented') + ), debugOnly: core.getInput('debug-only') === 'true', ascending: core.getInput('ascending') === 'true', deleteBranch: core.getInput('delete-branch') === 'true',