diff --git a/README.md b/README.md index 71d5eaae..97b4557c 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ Warns and then closes issues and PRs that have had no activity for a specified a | `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 | +| `only-issue-labels` | Only labels checked for stale issue (override `only-labels`). | Optional | +| `only-pr-labels` | Only labels checked for stale PR (override `only-labels`). | Optional | | `operations-per-run` | Maximum number of operations per run (GitHub API CRUD related). _Defaults to **30**_ | Optional | | `remove-stale-when-updated` | Remove stale label from issue/PR on updates or comments. _Defaults to **true**_ | Optional | | `debug-only` | Dry-run on action. _Defaults to **false**_ | Optional | diff --git a/__tests__/constants/default-processor-options.ts b/__tests__/constants/default-processor-options.ts index 23fe99b1..da7b7ca6 100644 --- a/__tests__/constants/default-processor-options.ts +++ b/__tests__/constants/default-processor-options.ts @@ -19,6 +19,8 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({ closePrLabel: '', exemptPrLabels: '', onlyLabels: '', + onlyIssueLabels: '', + onlyPrLabels: '', operationsPerRun: 100, debugOnly: true, removeStaleWhenUpdated: false, diff --git a/__tests__/only-labels.spec.ts b/__tests__/only-labels.spec.ts new file mode 100644 index 00000000..2ca5167c --- /dev/null +++ b/__tests__/only-labels.spec.ts @@ -0,0 +1,1152 @@ +import {Issue} from '../src/classes/issue'; +import {IssuesProcessor} from '../src/classes/issues-processor'; +import {IIssue} from '../src/interfaces/issue'; +import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; +import {DefaultProcessorOptions} from './constants/default-processor-options'; +import {generateIssue} from './functions/generate-issue'; + +let issuesProcessorBuilder: IssuesProcessorBuilder; +let issuesProcessor: IssuesProcessor; + +describe('only-labels option', (): void => { + beforeEach((): void => { + issuesProcessorBuilder = new IssuesProcessorBuilder(); + }); + + test('should stale when not set even if the issue has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyLabels() + .issuesOrPrs([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should stale when not set even if the issue has a label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyLabels() + .issuesOrPrs([{labels: [{name: 'label'}]}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should not stale when set and the issue has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyLabels('dummy-label') + .issuesOrPrs([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the issue has a different label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyLabels('dummy-label') + .issuesOrPrs([ + { + labels: [ + { + name: 'label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the issue has different labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyLabels('dummy-label') + .issuesOrPrs([ + { + labels: [ + { + name: 'label-1' + }, + { + name: 'label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the issue has the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyLabels('dummy-label') + .issuesOrPrs([ + { + labels: [ + { + name: 'dummy-label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should not stale when set and the issue has only one of the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyLabels('dummy-label-1,dummy-label-2') + .issuesOrPrs([ + { + labels: [ + { + name: 'dummy-label-1' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the issue has all the same labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyLabels('dummy-label-1,dummy-label-2') + .issuesOrPrs([ + { + labels: [ + { + name: 'dummy-label-1' + }, + { + name: 'dummy-label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); +}); + +describe('only-issue-labels option', (): void => { + beforeEach((): void => { + issuesProcessorBuilder = new IssuesProcessorBuilder(); + }); + + describe('when the only-labels options is not set', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.emptyOnlyLabels(); + }); + + test('should stale when not set even if the issue has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyIssueLabels() + .issues([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should stale when not set even if the issue has a label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyIssueLabels() + .issues([{labels: [{name: 'dummy-label'}]}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should not stale when set and the issue has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the issue has a different label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([ + { + labels: [ + { + name: 'label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the issue has different labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([ + { + labels: [ + { + name: 'label-1' + }, + { + name: 'label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the issue has the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([ + { + labels: [ + { + name: 'dummy-label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should not stale when set and the issue has only one of the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label-1,dummy-label-2') + .issues([ + { + labels: [ + { + name: 'dummy-label-1' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the issue has all the same labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label-1,dummy-label-2') + .issues([ + { + labels: [ + { + name: 'dummy-label-1' + }, + { + name: 'dummy-label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + }); + + describe('when the only-labels options is set (same as only-issue-labels)', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.onlyLabels('dummy-label'); + }); + + test('should not stale when not set even if the issue has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyIssueLabels() + .issues([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when not set even if the issue has a label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyIssueLabels() + .issues([{labels: [{name: 'label'}]}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the issue has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the issue has a different label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([ + { + labels: [ + { + name: 'label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the issue has different labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([ + { + labels: [ + { + name: 'label-1' + }, + { + name: 'label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the issue has the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([ + { + labels: [ + { + name: 'dummy-label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should not stale when set and the issue has only one of the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label-1,dummy-label-2') + .issues([ + { + labels: [ + { + name: 'dummy-label-1' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the issue has all the same labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label-1,dummy-label-2') + .issues([ + { + labels: [ + { + name: 'dummy-label-1' + }, + { + name: 'dummy-label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + }); + + describe('when the only-labels options is set (different than only-issue-labels)', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.onlyLabels('dummy-only-label'); + }); + + test('should not stale when not set even if the issue has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyIssueLabels() + .issues([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when not set even if the issue has a label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyIssueLabels() + .issues([{labels: [{name: 'label'}]}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the issue has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the issue has a different label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([ + { + labels: [ + { + name: 'label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the issue has different labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([ + { + labels: [ + { + name: 'label-1' + }, + { + name: 'label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the issue has the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label') + .issues([ + { + labels: [ + { + name: 'dummy-label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should not stale when set and the issue has only one of the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label-1,dummy-label-2') + .issues([ + { + labels: [ + { + name: 'dummy-label-1' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the issue has all the same labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyIssueLabels('dummy-label-1,dummy-label-2') + .issues([ + { + labels: [ + { + name: 'dummy-label-1' + }, + { + name: 'dummy-label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + }); +}); + +describe('only-pr-labels option', (): void => { + beforeEach((): void => { + issuesProcessorBuilder = new IssuesProcessorBuilder(); + }); + + describe('when the only-labels options is not set', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.emptyOnlyLabels(); + }); + + test('should stale when not set even if the pr has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyPrLabels() + .prs([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should stale when not set even if the pr has a label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyPrLabels() + .prs([{labels: [{name: 'dummy-label'}]}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should not stale when set and the pr has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the pr has a different label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([ + { + labels: [ + { + name: 'label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the pr has different labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([ + { + labels: [ + { + name: 'label-1' + }, + { + name: 'label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the pr has the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([ + { + labels: [ + { + name: 'dummy-label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should not stale when set and the pr has only one of the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label-1,dummy-label-2') + .prs([ + { + labels: [ + { + name: 'dummy-label-1' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the pr has all the same labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label-1,dummy-label-2') + .prs([ + { + labels: [ + { + name: 'dummy-label-1' + }, + { + name: 'dummy-label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + }); + + describe('when the only-labels options is set (same as only-pr-labels)', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.onlyLabels('dummy-label'); + }); + + test('should not stale when not set even if the pr has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyPrLabels() + .prs([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when not set even if the pr has a label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyPrLabels() + .prs([{labels: [{name: 'label'}]}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the pr has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the pr has a different label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([ + { + labels: [ + { + name: 'label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the pr has different labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([ + { + labels: [ + { + name: 'label-1' + }, + { + name: 'label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the pr has the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([ + { + labels: [ + { + name: 'dummy-label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should not stale when set and the pr has only one of the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label-1,dummy-label-2') + .prs([ + { + labels: [ + { + name: 'dummy-label-1' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the pr has all the same labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label-1,dummy-label-2') + .prs([ + { + labels: [ + { + name: 'dummy-label-1' + }, + { + name: 'dummy-label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + }); + + describe('when the only-labels options is set (different than only-pr-labels)', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.onlyLabels('dummy-only-label'); + }); + + test('should not stale when not set even if the pr has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyPrLabels() + .prs([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when not set even if the pr has a label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .emptyOnlyPrLabels() + .prs([{labels: [{name: 'label'}]}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the pr has no label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([{labels: []}]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the pr has a different label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([ + { + labels: [ + { + name: 'label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should not stale when set and the pr has different labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([ + { + labels: [ + { + name: 'label-1' + }, + { + name: 'label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the pr has the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label') + .prs([ + { + labels: [ + { + name: 'dummy-label' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + + test('should not stale when set and the pr has only one of the same label', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label-1,dummy-label-2') + .prs([ + { + labels: [ + { + name: 'dummy-label-1' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + + test('should stale when set and the pr has all the same labels', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .onlyPrLabels('dummy-label-1,dummy-label-2') + .prs([ + { + labels: [ + { + name: 'dummy-label-1' + }, + { + name: 'dummy-label-2' + } + ] + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + }); +}); + +class IssuesProcessorBuilder { + private _options: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + daysBeforeStale: 0 + }; + private _issues: Issue[] = []; + + onlyLabels(labels: string): IssuesProcessorBuilder { + this._options.onlyLabels = labels; + + return this; + } + + onlyIssueLabels(labels: string): IssuesProcessorBuilder { + this._options.onlyIssueLabels = labels; + + return this; + } + + onlyPrLabels(labels: string): IssuesProcessorBuilder { + this._options.onlyPrLabels = labels; + + return this; + } + + emptyOnlyLabels(): IssuesProcessorBuilder { + return this.onlyLabels(''); + } + + emptyOnlyIssueLabels(): IssuesProcessorBuilder { + return this.onlyIssueLabels(''); + } + + emptyOnlyPrLabels(): IssuesProcessorBuilder { + return this.onlyPrLabels(''); + } + + 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; + } + + prs(issues: Partial[]): IssuesProcessorBuilder { + this.issuesOrPrs( + issues.map( + (issue: Readonly>): Partial => { + return { + ...issue, + pull_request: {key: 'value'} + }; + } + ) + ); + + return this; + } + + build(): IssuesProcessor { + return new IssuesProcessor( + this._options, + async () => 'abot', + async p => (p === 1 ? this._issues : []), + async () => [], + async () => new Date().toDateString() + ); + } +} diff --git a/action.yml b/action.yml index cbf8ade8..211470c4 100644 --- a/action.yml +++ b/action.yml @@ -88,6 +88,14 @@ inputs: description: 'Only issues or pull requests with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels.' default: '' required: false + only-issue-labels: + description: 'Only issues with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels. Override "only-labels" option regarding only the issues.' + default: '' + required: false + only-pr-labels: + description: 'Only pull requests with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels. Override "only-labels" option regarding only the pull requests.' + default: '' + required: false operations-per-run: description: 'The maximum number of operations per run, used to control rate limiting (GitHub API CRUD related).' default: '30' diff --git a/dist/index.js b/dist/index.js index ec5014e1..f1c1bdba 100644 --- a/dist/index.js +++ b/dist/index.js @@ -285,6 +285,23 @@ class IssuesProcessor { const daysBeforeStale = issue.isPullRequest ? this._getDaysBeforePrStale() : this._getDaysBeforeIssueStale(); + const onlyLabels = words_to_list_1.wordsToList(this._getOnlyLabels(issue)); + if (onlyLabels.length > 0) { + issueLogger.info(`The option "onlyLabels" was specified to only processed the issues and pull requests with all those labels (${onlyLabels.length})`); + const hasAllWhitelistedLabels = onlyLabels.every((label) => { + return is_labeled_1.isLabeled(issue, label); + }); + if (!hasAllWhitelistedLabels) { + issueLogger.info(`Skipping this $$type because it doesn't have all the required labels`); + continue; // Don't process issues without all of the required labels + } + else { + issueLogger.info(`All the required labels are present on this $$type. Continuing the process`); + } + } + else { + issueLogger.info(`The option "onlyLabels" was not specified. Continuing the process for this $$type`); + } issueLogger.info(`Days before $$type stale: ${daysBeforeStale}`); const shouldMarkAsStale = should_mark_when_stale_1.shouldMarkWhenStale(daysBeforeStale); if (!staleMessage && shouldMarkAsStale) { @@ -462,7 +479,6 @@ class IssuesProcessor { owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, state: 'open', - labels: this.options.onlyLabels, per_page: 100, direction: this.options.ascending ? 'asc' : 'desc', page @@ -676,6 +692,19 @@ class IssuesProcessor { ? this.options.daysBeforeClose : this.options.daysBeforePrClose; } + _getOnlyLabels(issue) { + if (issue.isPullRequest) { + if (this.options.onlyPrLabels !== '') { + return this.options.onlyPrLabels; + } + } + else { + if (this.options.onlyIssueLabels !== '') { + return this.options.onlyIssueLabels; + } + } + return this.options.onlyLabels; + } _removeStaleLabel(issue, staleLabel) { return __awaiter(this, void 0, void 0, function* () { const issueLogger = new issue_logger_1.IssueLogger(issue); @@ -1170,6 +1199,8 @@ function _getAndValidateArgs() { closePrLabel: core.getInput('close-pr-label'), exemptPrLabels: core.getInput('exempt-pr-labels'), onlyLabels: core.getInput('only-labels'), + onlyIssueLabels: core.getInput('only-issue-labels'), + onlyPrLabels: core.getInput('only-pr-labels'), operationsPerRun: parseInt(core.getInput('operations-per-run', { required: true })), removeStaleWhenUpdated: !(core.getInput('remove-stale-when-updated') === 'false'), debugOnly: core.getInput('debug-only') === 'true', @@ -1222,7 +1253,7 @@ function _toOptionalBoolean(argumentName) { } return undefined; } -_run(); +void _run(); /***/ }), diff --git a/src/classes/issue.spec.ts b/src/classes/issue.spec.ts index 8cbd3d91..8d852b23 100644 --- a/src/classes/issue.spec.ts +++ b/src/classes/issue.spec.ts @@ -28,6 +28,8 @@ describe('Issue', (): void => { exemptIssueLabels: '', exemptPrLabels: '', onlyLabels: '', + onlyIssueLabels: '', + onlyPrLabels: '', operationsPerRun: 0, removeStaleWhenUpdated: false, repoToken: '', diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts index cf7650fc..cbed3f5c 100644 --- a/src/classes/issues-processor.ts +++ b/src/classes/issues-processor.ts @@ -120,6 +120,34 @@ export class IssuesProcessor { const daysBeforeStale: number = issue.isPullRequest ? this._getDaysBeforePrStale() : this._getDaysBeforeIssueStale(); + const onlyLabels: string[] = wordsToList(this._getOnlyLabels(issue)); + + if (onlyLabels.length > 0) { + issueLogger.info( + `The option "onlyLabels" was specified to only processed the issues and pull requests with all those labels (${onlyLabels.length})` + ); + + const hasAllWhitelistedLabels: boolean = onlyLabels.every( + (label: Readonly): boolean => { + return isLabeled(issue, label); + } + ); + + if (!hasAllWhitelistedLabels) { + issueLogger.info( + `Skipping this $$type because it doesn't have all the required labels` + ); + continue; // Don't process issues without all of the required labels + } else { + issueLogger.info( + `All the required labels are present on this $$type. Continuing the process` + ); + } + } else { + issueLogger.info( + `The option "onlyLabels" was not specified. Continuing the process for this $$type` + ); + } issueLogger.info(`Days before $$type stale: ${daysBeforeStale}`); @@ -398,7 +426,6 @@ export class IssuesProcessor { owner: context.repo.owner, repo: context.repo.repo, state: 'open', - labels: this.options.onlyLabels, per_page: 100, direction: this.options.ascending ? 'asc' : 'desc', page @@ -660,6 +687,20 @@ export class IssuesProcessor { : this.options.daysBeforePrClose; } + private _getOnlyLabels(issue: Issue): string { + if (issue.isPullRequest) { + if (this.options.onlyPrLabels !== '') { + return this.options.onlyPrLabels; + } + } else { + if (this.options.onlyIssueLabels !== '') { + return this.options.onlyIssueLabels; + } + } + + return this.options.onlyLabels; + } + private async _removeStaleLabel( issue: Issue, staleLabel: Readonly diff --git a/src/interfaces/issues-processor-options.ts b/src/interfaces/issues-processor-options.ts index bb95ebac..28027e97 100644 --- a/src/interfaces/issues-processor-options.ts +++ b/src/interfaces/issues-processor-options.ts @@ -19,6 +19,8 @@ export interface IIssuesProcessorOptions { closePrLabel: string; exemptPrLabels: string; onlyLabels: string; + onlyIssueLabels: string; + onlyPrLabels: string; operationsPerRun: number; removeStaleWhenUpdated: boolean; debugOnly: boolean; diff --git a/src/main.ts b/src/main.ts index 14e949c2..f5dd9052 100644 --- a/src/main.ts +++ b/src/main.ts @@ -39,6 +39,8 @@ function _getAndValidateArgs(): IIssuesProcessorOptions { closePrLabel: core.getInput('close-pr-label'), exemptPrLabels: core.getInput('exempt-pr-labels'), onlyLabels: core.getInput('only-labels'), + onlyIssueLabels: core.getInput('only-issue-labels'), + onlyPrLabels: core.getInput('only-pr-labels'), operationsPerRun: parseInt( core.getInput('operations-per-run', {required: true}) ), @@ -106,4 +108,4 @@ function _toOptionalBoolean( return undefined; } -_run(); +void _run();