From 419a53bc0562d12377202edbfd5a52ed4c391db1 Mon Sep 17 00:00:00 2001 From: Geoffrey Testelin Date: Mon, 1 Mar 2021 21:34:35 +0100 Subject: [PATCH] feat(statistics): display some stats in the logs (#337) * test: add more coverage * docs: reorder and enhance typo * docs(contributing): add more information about the npm scripts * feat(statistics): add simple statistics * feat(statistics): add more stats * refactor(issues-processor): remove some options from the constructor it should have been only useful for the tests * feat(statistics): add stats for new stale or undo stale issues * chore(rebase): handle rebase conflicts --- README.md | 116 ++-- __tests__/any-of-labels.spec.ts | 6 +- __tests__/assignees.spec.ts | 6 +- __tests__/classes/issues-processor-mock.ts | 38 ++ .../constants/default-processor-options.ts | 3 +- __tests__/main.spec.ts | 542 +++++++++--------- __tests__/milestones.spec.ts | 6 +- __tests__/only-labels.spec.ts | 8 +- action.yml | 4 + dist/index.js | 421 +++++++++----- src/classes/issue.spec.ts | 3 +- src/classes/issues-processor.ts | 301 +++++----- src/classes/statistics.ts | 200 +++++++ src/functions/get-issue-type.spec.ts | 33 -- src/functions/get-issue-type.ts | 5 - src/interfaces/issues-processor-options.ts | 1 + src/main.ts | 3 +- 17 files changed, 1019 insertions(+), 677 deletions(-) create mode 100644 __tests__/classes/issues-processor-mock.ts create mode 100644 src/classes/statistics.ts delete mode 100644 src/functions/get-issue-type.spec.ts delete mode 100644 src/functions/get-issue-type.ts diff --git a/README.md b/README.md index eda9bfe8a..fc9d55eb0 100644 --- a/README.md +++ b/README.md @@ -4,56 +4,51 @@ Warns and then closes issues and PRs that have had no activity for a specified a ### Arguments -| Input | Description | Usage | -| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------- | -| `repo-token` | PAT(Personal Access Token) for authorizing repository. _Defaults to **${{ github.token }}**_ | Optional | -| `days-before-stale` | Idle number of days before marking an issue/PR as stale. _Defaults to **60**_ | Optional | -| `days-before-issue-stale` | Idle number of days before marking an issue as stale (override `days-before-stale`). | Optional | -| `days-before-pr-stale` | Idle number of days before marking an PR as stale (override `days-before-stale`). | Optional | -| `days-before-close` | Idle number of days before closing an stale issue/PR. _Defaults to **7**_ | Optional | -| `days-before-issue-close` | Idle number of days before closing an stale issue (override `days-before-close`). | Optional | -| `days-before-pr-close` | Idle number of days before closing an stale PR (override `days-before-close`). | Optional | -| `stale-issue-message` | Message to post on the stale issue. | Optional | -| `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 (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 | -| `exempt-milestones` | Milestones on an issue or a PR exempted from being marked as stale. | Optional | -| `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale (override `exempt-milestones`). | Optional | -| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale (override `exempt-milestones`). | Optional | -| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale. (priority over `exempt-milestones` rules) | Optional | -| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional | -| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional | -| `only-labels` | Only issues and PRs with ALL these labels are checked. Separate multiple labels with commas (eg. "question,answered"). | 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 | -| `any-of-labels` | Only issues and PRs with ANY of these labels are checked. Separate multiple labels with commas (eg. "incomplete,waiting-feedback"). | 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 | -| `ascending` | Order to get issues/PR. _Defaults to **false**_ | Optional | -| `skip-stale-issue-message` | Skip adding stale message on stale issue. _Defaults to **false**_ | Optional | -| `skip-stale-pr-message` | Skip adding stale message on stale PR. _Defaults to **false**_ | Optional | -| `start-date` | The date used to skip the stale action on issue/PR created before it (ISO 8601 or RFC 2822). | Optional | -| `delete-branch` | Delete the git branch after closing a stale pull request. _Defaults to **false**_ | Optional | -| `exempt-milestones` | Milestones on an issue or a PR exempted from being marked as stale. | Optional | -| `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale (override `exempt-milestones`). | Optional | -| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale (override `exempt-milestones`). | Optional | -| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale. (priority over `exempt-milestones` rules) | Optional | -| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional | -| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional | -| `exempt-assignees` | Assignees on an issue or a PR exempted from being marked as stale. | Optional | -| `exempt-issue-assignees` | Assignees on an issue exempted from being marked as stale (override `exempt-assignees`). | Optional | -| `exempt-pr-assignees` | Assignees on the PR exempted from being marked as stale (override `exempt-assignees`). | Optional | -| `exempt-all-assignees` | Exempt all issues and PRs with assignees from being marked as stale. (priority over `exempt-assignees` rules) | Optional | -| `exempt-all-issue-assignees` | Exempt all issues with assignees from being marked as stale. (override `exempt-all-assignees`). | Optional | -| `exempt-all-pr-assignees` | Exempt all PRs with assignees from being marked as stale. (override `exempt-all-assignees`). | Optional | +| Input | Description | Usage | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------- | +| `repo-token` | PAT(Personal Access Token) for authorizing repository. _Defaults to **${{ github.token }}**_ | Optional | +| `days-before-stale` | Idle number of days before marking an issue/PR as stale. _Defaults to **60**_ | Optional | +| `days-before-issue-stale` | Idle number of days before marking an issue as stale (override `days-before-stale`). | Optional | +| `days-before-pr-stale` | Idle number of days before marking an PR as stale (override `days-before-stale`). | Optional | +| `days-before-close` | Idle number of days before closing an stale issue/PR. _Defaults to **7**_ | Optional | +| `days-before-issue-close` | Idle number of days before closing an stale issue (override `days-before-close`). | Optional | +| `days-before-pr-close` | Idle number of days before closing an stale PR (override `days-before-close`). | Optional | +| `stale-issue-message` | Message to post on the stale issue. | Optional | +| `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 (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 issues and PRs with ALL these labels are checked. Separate multiple labels with commas (eg. "question,answered"). | 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 | +| `any-of-labels` | Only issues and PRs with ANY of these labels are checked. Separate multiple labels with commas (eg. "incomplete,waiting-feedback"). | 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 | +| `ascending` | Order to get issues/PR. _Defaults to **false**_ | Optional | +| `skip-stale-issue-message` | Skip adding stale message on stale issue. _Defaults to **false**_ | Optional | +| `skip-stale-pr-message` | Skip adding stale message on stale PR. _Defaults to **false**_ | Optional | +| `start-date` | The date used to skip the stale action on issue/PR created before it (ISO 8601 or RFC 2822). | Optional | +| `delete-branch` | Delete the git branch after closing a stale pull request. _Defaults to **false**_ | Optional | +| `exempt-milestones` | Milestones on an issue or a PR exempted from being marked as stale. | Optional | +| `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale (override `exempt-milestones`). | Optional | +| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale (override `exempt-milestones`). | Optional | +| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale. (priority over `exempt-milestones` rules) | Optional | +| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional | +| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional | +| `exempt-assignees` | Assignees on an issue or a PR exempted from being marked as stale. | Optional | +| `exempt-issue-assignees` | Assignees on an issue exempted from being marked as stale (override `exempt-assignees`). | Optional | +| `exempt-pr-assignees` | Assignees on the PR exempted from being marked as stale (override `exempt-assignees`). | Optional | +| `exempt-all-assignees` | Exempt all issues and PRs with assignees from being marked as stale. (priority over `exempt-assignees` rules) | Optional | +| `exempt-all-issue-assignees` | Exempt all issues with assignees from being marked as stale. (override `exempt-all-assignees`). | Optional | +| `exempt-all-pr-assignees` | Exempt all PRs with assignees from being marked as stale. (override `exempt-all-assignees`). | Optional | +| `enable-statistics` | Display some statistics at the end of the logs regarding the stale workflow (only when the logs are enabled). _Defaults to **true**_ | Optional | ### Usage @@ -275,10 +270,25 @@ jobs: ### Debugging +**Logs:** To see the debug output from this action, you must set the secret `ACTIONS_STEP_DEBUG` to `true` in your repository. -You can run this action in debug only mode (no actions will be taken on your issues and pull requests) by passing `debug-only` to `true` as an argument to the action. -You can also increase the maximum number of operations per run by passing `operations-per-run` to `100` for example. -Finally, you could also change the cron job frequency in the stale workflow to run stale more often. +There is a lot of logs so this can be very helpful! + +**Statistics:** +If the logs are enabled, you can also enable the statistics log which will be visible at the end of the logs once all issues were processed. +This is very helpful to have a quick understanding of the whole stale workflow. +Set `enable-statistics` to `true` in your workflow configuration file. + +**Dry-run:** +You can run this action in debug only mode (no actions will be taken on your issues and pull requests) by passing `debug-only` to `true` as an argument to the action. + +**More operations:** +You can increase the maximum number of operations per run by passing `operations-per-run` to `1000` for example which will help you to handle more operations in a single stale workflow run. +If the `debug-only` option is enabled, this is very helpful because the workflow will (almost) never reach the GitHub API rate, and you will be able to deep-dive into the logs. + +**Job frequency:** +You could change the cron job frequency in the stale workflow to run the stale workflow more often. +Usually this is not very helpful though. ### Contributing diff --git a/__tests__/any-of-labels.spec.ts b/__tests__/any-of-labels.spec.ts index e3e3cfcb9..f69d1eed2 100644 --- a/__tests__/any-of-labels.spec.ts +++ b/__tests__/any-of-labels.spec.ts @@ -1,6 +1,6 @@ import {Issue} from '../src/classes/issue'; -import {IssuesProcessor} from '../src/classes/issues-processor'; import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; +import {IssuesProcessorMock} from './classes/issues-processor-mock'; import {DefaultProcessorOptions} from './constants/default-processor-options'; import {generateIssue} from './functions/generate-issue'; @@ -95,8 +95,8 @@ class IssuesProcessorBuilder { return this; } - build(): IssuesProcessor { - return new IssuesProcessor( + build(): IssuesProcessorMock { + return new IssuesProcessorMock( this._options, async () => 'abot', async p => (p === 1 ? this._issues : []), diff --git a/__tests__/assignees.spec.ts b/__tests__/assignees.spec.ts index c60dac7a2..073ce167c 100644 --- a/__tests__/assignees.spec.ts +++ b/__tests__/assignees.spec.ts @@ -1,6 +1,6 @@ import {Issue} from '../src/classes/issue'; -import {IssuesProcessor} from '../src/classes/issues-processor'; import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; +import {IssuesProcessorMock} from './classes/issues-processor-mock'; import {DefaultProcessorOptions} from './constants/default-processor-options'; import {generateIssue} from './functions/generate-issue'; @@ -21,7 +21,7 @@ interface ITestData { describe('assignees options', (): void => { let opts: IIssuesProcessorOptions; let testIssueList: Issue[]; - let processor: IssuesProcessor; + let processor: IssuesProcessorMock; const setTestIssueList = ( isPullRequest: boolean, @@ -46,7 +46,7 @@ describe('assignees options', (): void => { }; const setProcessor = () => { - processor = new IssuesProcessor( + processor = new IssuesProcessorMock( opts, async () => 'abot', async p => (p === 1 ? testIssueList : []), diff --git a/__tests__/classes/issues-processor-mock.ts b/__tests__/classes/issues-processor-mock.ts new file mode 100644 index 000000000..730235d35 --- /dev/null +++ b/__tests__/classes/issues-processor-mock.ts @@ -0,0 +1,38 @@ +import {Issue} from '../../src/classes/issue'; +import {IssuesProcessor} from '../../src/classes/issues-processor'; +import {IComment} from '../../src/interfaces/comment'; +import {IIssuesProcessorOptions} from '../../src/interfaces/issues-processor-options'; + +export class IssuesProcessorMock extends IssuesProcessor { + constructor( + options: IIssuesProcessorOptions, + getActor?: () => Promise, + getIssues?: (page: number) => Promise, + listIssueComments?: ( + issueNumber: number, + sinceDate: string + ) => Promise, + getLabelCreationDate?: ( + issue: Issue, + label: string + ) => Promise + ) { + super(options); + + if (getActor) { + this.getActor = getActor; + } + + if (getIssues) { + this.getIssues = getIssues; + } + + if (listIssueComments) { + this.listIssueComments = listIssueComments; + } + + if (getLabelCreationDate) { + this.getLabelCreationDate = getLabelCreationDate; + } + } +} diff --git a/__tests__/constants/default-processor-options.ts b/__tests__/constants/default-processor-options.ts index f1385b15f..4c8979388 100644 --- a/__tests__/constants/default-processor-options.ts +++ b/__tests__/constants/default-processor-options.ts @@ -41,5 +41,6 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({ exemptPrAssignees: '', exemptAllAssignees: false, exemptAllIssueAssignees: undefined, - exemptAllPrAssignees: undefined + exemptAllPrAssignees: undefined, + enableStatistics: false }); diff --git a/__tests__/main.spec.ts b/__tests__/main.spec.ts index fcb554400..b6efe3bc8 100644 --- a/__tests__/main.spec.ts +++ b/__tests__/main.spec.ts @@ -1,26 +1,10 @@ import * as github from '@actions/github'; import {Issue} from '../src/classes/issue'; -import {IssuesProcessor} from '../src/classes/issues-processor'; import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; +import {IssuesProcessorMock} from './classes/issues-processor-mock'; import {DefaultProcessorOptions} from './constants/default-processor-options'; import {generateIssue} from './functions/generate-issue'; -test('empty issue list results in 1 operation', async () => { - const processor = new IssuesProcessor( - DefaultProcessorOptions, - async () => 'abot', - async () => [], - async (num, dt) => [], - async (issue, label) => new Date().toDateString() - ); - - // process our fake issue list - const operationsLeft = await processor.processIssues(1); - - // processing an empty issue list should result in 1 operation - expect(operationsLeft).toEqual(99); -}); - test('processing an issue with no label will make it stale and close it, if it is old enough only if days-before-close is set to 0', async () => { const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, @@ -29,12 +13,12 @@ test('processing an issue with no label will make it stale and close it, if it i const TestIssueList: Issue[] = [ generateIssue(opts, 1, 'An issue with no label', '2020-01-01T17:00:00Z') ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -61,12 +45,12 @@ test('processing an issue with no label and a start date as ECMAScript epoch in '2020-01-01T17:00:00Z' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -93,12 +77,12 @@ test('processing an issue with no label and a start date as ECMAScript epoch in '2020-01-01T17:00:00Z' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -125,12 +109,12 @@ test('processing an issue with no label and a start date as ECMAScript epoch in '2020-01-01T17:00:00Z' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -157,12 +141,12 @@ test('processing an issue with no label and a start date as ECMAScript epoch in '2020-01-01T17:00:00Z' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -189,12 +173,12 @@ test('processing an issue with no label and a start date as ISO 8601 being befor '2020-01-01T17:00:00Z' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -221,12 +205,12 @@ test('processing an issue with no label and a start date as ISO 8601 being after '2020-01-01T17:00:00Z' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -253,12 +237,12 @@ test('processing an issue with no label and a start date as RFC 2822 being befor '2020-01-01T17:00:00Z' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -285,12 +269,12 @@ test('processing an issue with no label and a start date as RFC 2822 being after '2020-01-01T17:00:00Z' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -309,12 +293,12 @@ test('processing an issue with no label will make it stale and close it, if it i const TestIssueList: Issue[] = [ generateIssue(opts, 1, 'An issue with no label', '2020-01-01T17:00:00Z') ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -334,12 +318,12 @@ test('processing an issue with no label will make it stale and not close it, if const TestIssueList: Issue[] = [ generateIssue(opts, 1, 'An issue with no label', '2020-01-01T17:00:00Z') ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -357,12 +341,12 @@ test('processing an issue with no label will make it stale and not close it if d const TestIssueList: Issue[] = [ generateIssue(opts, 1, 'An issue with no label', '2020-01-01T17:00:00Z') ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -381,12 +365,12 @@ test('processing an issue with no label will make it stale and not close it if d const TestIssueList: Issue[] = [ generateIssue(opts, 1, 'An issue with no label', '2020-01-01T17:00:00Z') ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -405,12 +389,12 @@ test('processing an issue with no label will not make it stale if days-before-st const TestIssueList: Issue[] = [ generateIssue(opts, 1, 'An issue with no label', '2020-01-01T17:00:00Z') ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -430,12 +414,12 @@ test('processing an issue with no label will not make it stale if days-before-st const TestIssueList: Issue[] = [ generateIssue(opts, 1, 'An issue with no label', '2020-01-01T17:00:00Z') ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -458,12 +442,12 @@ test('processing an issue with no label will make it stale but not close it', as issueDate.toDateString() ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( DefaultProcessorOptions, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -489,12 +473,12 @@ test('processing a stale issue will close it', async () => { ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -520,12 +504,12 @@ test('processing a stale issue containing a space in the label will close it', a ['state: stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -551,12 +535,12 @@ test('processing a stale issue containing a slash in the label will close it', a ['lifecycle/stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -583,12 +567,12 @@ test('processing a stale issue will close it when days-before-issue-stale overri ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -614,12 +598,12 @@ test('processing a stale PR will close it', async () => { ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -646,12 +630,12 @@ test('processing a stale PR will close it when days-before-pr-stale override day ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -678,12 +662,12 @@ test('processing a stale issue will close it even if configured not to mark as s ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -711,12 +695,12 @@ test('processing a stale issue will close it even if configured not to mark as s ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -743,12 +727,12 @@ test('processing a stale PR will close it even if configured not to mark as stal ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -776,12 +760,12 @@ test('processing a stale PR will close it even if configured not to mark as stal ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -804,11 +788,11 @@ test('closed issues will not be marked stale', async () => { true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( DefaultProcessorOptions, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [] + async p => (p === 1 ? TestIssueList : []), + async () => [] ); // process our fake issue list @@ -831,12 +815,12 @@ test('stale closed issues will not be closed', async () => { true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( DefaultProcessorOptions, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -859,12 +843,12 @@ test('closed prs will not be marked stale', async () => { true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( DefaultProcessorOptions, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -887,12 +871,12 @@ test('stale closed prs will not be closed', async () => { true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( DefaultProcessorOptions, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -916,10 +900,10 @@ test('locked issues will not be marked stale', async () => { true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( DefaultProcessorOptions, async () => 'abot', - async p => (p == 1 ? TestIssueList : []) + async p => (p === 1 ? TestIssueList : []) ); // process our fake issue list @@ -943,12 +927,12 @@ test('stale locked issues will not be closed', async () => { true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( DefaultProcessorOptions, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -972,10 +956,10 @@ test('locked prs will not be marked stale', async () => { true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( DefaultProcessorOptions, async () => 'abot', - async p => (p == 1 ? TestIssueList : []) + async p => (p === 1 ? TestIssueList : []) ); // process our fake issue list @@ -999,12 +983,12 @@ test('stale locked prs will not be closed', async () => { true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( DefaultProcessorOptions, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1029,12 +1013,12 @@ test('exempt issue labels will not be marked stale', async () => { ['Exempt'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1059,12 +1043,12 @@ test('exempt issue labels will not be marked stale (multi issue label with space ['Cool'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1088,12 +1072,12 @@ test('exempt issue labels will not be marked stale (multi issue label)', async ( ['Cool'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1135,12 +1119,12 @@ test('exempt pr labels will not be marked stale', async () => { false ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1164,11 +1148,11 @@ test('exempt issue labels will not be marked stale and will remove the existing ['Exempt', 'Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [ + async p => (p === 1 ? TestIssueList : []), + async () => [ { user: { login: 'notme', @@ -1176,7 +1160,7 @@ test('exempt issue labels will not be marked stale and will remove the existing } } ], // return a fake comment to indicate there was an update - async (issue: Issue, label: string) => new Date().toDateString() + async () => new Date().toDateString() ); // process our fake issue list @@ -1219,12 +1203,12 @@ test('stale issues should not be closed if days is set to -1', async () => { ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1248,11 +1232,11 @@ test('stale label should be removed if a comment was added to a stale issue', as ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [ + async p => (p === 1 ? TestIssueList : []), + async () => [ { user: { login: 'notme', @@ -1260,7 +1244,7 @@ test('stale label should be removed if a comment was added to a stale issue', as } } ], // return a fake comment to indicate there was an update - async (issue: Issue, label: string) => new Date().toDateString() + async () => new Date().toDateString() ); // process our fake issue list @@ -1286,11 +1270,11 @@ test('stale label should not be removed if a comment was added by the bot (and t ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [ + async p => (p === 1 ? TestIssueList : []), + async () => [ { user: { login: 'abot', @@ -1298,7 +1282,7 @@ test('stale label should not be removed if a comment was added by the bot (and t } } ], // return a fake comment to indicate there was an update by the bot - async (issue: Issue, label: string) => new Date().toDateString() + async () => new Date().toDateString() ); // process our fake issue list @@ -1326,12 +1310,12 @@ test('stale label containing a space should be removed if a comment was added to ['stat: stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [{user: {login: 'notme', type: 'User'}}], // return a fake comment to indicate there was an update - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [{user: {login: 'notme', type: 'User'}}], // return a fake comment to indicate there was an update + async () => new Date().toDateString() ); // process our fake issue list @@ -1358,12 +1342,12 @@ test('stale issues should not be closed until after the closed number of days', false ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1391,12 +1375,12 @@ test('stale issues should be closed if the closed nubmer of days (additive) is a ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1423,12 +1407,12 @@ test('stale issues should not be closed until after the closed number of days (l false ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1456,12 +1440,12 @@ test('skips stale message on issues when skip-stale-issue-message is set', async false ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // for sake of testing, mocking private function @@ -1501,12 +1485,12 @@ test('skips stale message on prs when skip-stale-pr-message is set', async () => true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // for sake of testing, mocking private function @@ -1547,12 +1531,12 @@ test('not providing state takes precedence over skipStaleIssueMessage', async () false ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); await processor.processIssues(1); @@ -1581,12 +1565,12 @@ test('not providing stalePrMessage takes precedence over skipStalePrMessage', as true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); await processor.processIssues(1); @@ -1611,12 +1595,12 @@ test('git branch is deleted when option is enabled', async () => { ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); await processor.processIssues(1); @@ -1641,12 +1625,12 @@ test('git branch is not deleted when issue is not pull request', async () => { ['Stale'] ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); await processor.processIssues(1); @@ -1673,12 +1657,12 @@ test('an issue without a milestone will be marked as stale', async () => { '' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( DefaultProcessorOptions, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1707,12 +1691,12 @@ test('an issue without an exempted milestone will be marked as stale', async () 'Milestone' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1741,12 +1725,12 @@ test('an issue with an exempted milestone will not be marked as stale', async () 'Milestone1' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1775,12 +1759,12 @@ test('an issue with an exempted milestone will not be marked as stale (multi mil 'Milestone2' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1809,12 +1793,12 @@ test('an issue with an exempted milestone will not be marked as stale (multi mil 'Milestone2' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1844,12 +1828,12 @@ test('an issue with an exempted milestone but without an exempted issue mileston 'Milestone1' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1879,12 +1863,12 @@ test('an issue with an exempted milestone but with another exempted issue milest 'Milestone1' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1914,12 +1898,12 @@ test('an issue with an exempted milestone and with an exempted issue milestone w 'Milestone1' ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num: number, dt: string) => [], - async (issue: Issue, label: string) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1942,12 +1926,12 @@ test('processing an issue opened since 2 days and with the option "daysBeforeIss const TestIssueList: Issue[] = [ generateIssue(opts, 1, 'An issue with no label', issueDate.toDateString()) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1969,12 +1953,12 @@ test('processing an issue opened since 2 days and with the option "daysBeforeIss const TestIssueList: Issue[] = [ generateIssue(opts, 1, 'An issue with no label', issueDate.toDateString()) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -1996,12 +1980,12 @@ test('processing an issue opened since 2 days and with the option "daysBeforeIss const TestIssueList: Issue[] = [ generateIssue(opts, 1, 'An issue with no label', issueDate.toDateString()) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -2030,12 +2014,12 @@ test('processing a pull request opened since 2 days and with the option "daysBef true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -2064,12 +2048,12 @@ test('processing a pull request opened since 2 days and with the option "daysBef true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -2098,12 +2082,12 @@ test('processing a pull request opened since 2 days and with the option "daysBef true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', - async p => (p == 1 ? TestIssueList : []), - async (num, dt) => [], - async (issue, label) => new Date().toDateString() + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() ); // process our fake issue list @@ -2135,7 +2119,7 @@ test('processing a previously closed issue with a close label will remove the cl false ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', async p => (p === 1 ? TestIssueList : []), @@ -2171,7 +2155,7 @@ test('processing a closed issue with a close label will not remove the close lab false ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', async p => (p === 1 ? TestIssueList : []), @@ -2207,7 +2191,7 @@ test('processing a locked issue with a close label will not remove the close lab true ) ]; - const processor = new IssuesProcessor( + const processor = new IssuesProcessorMock( opts, async () => 'abot', async p => (p === 1 ? TestIssueList : []), diff --git a/__tests__/milestones.spec.ts b/__tests__/milestones.spec.ts index faa7c9b3f..5f2b7edbe 100644 --- a/__tests__/milestones.spec.ts +++ b/__tests__/milestones.spec.ts @@ -1,6 +1,6 @@ import {Issue} from '../src/classes/issue'; -import {IssuesProcessor} from '../src/classes/issues-processor'; import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; +import {IssuesProcessorMock} from './classes/issues-processor-mock'; import {DefaultProcessorOptions} from './constants/default-processor-options'; import {generateIssue} from './functions/generate-issue'; @@ -14,7 +14,7 @@ interface ITestData { describe('milestones options', (): void => { let opts: IIssuesProcessorOptions; let testIssueList: Issue[]; - let processor: IssuesProcessor; + let processor: IssuesProcessorMock; const setTestIssueList = ( isPullRequest: boolean, @@ -37,7 +37,7 @@ describe('milestones options', (): void => { }; const setProcessor = () => { - processor = new IssuesProcessor( + processor = new IssuesProcessorMock( opts, async () => 'abot', async p => (p === 1 ? testIssueList : []), diff --git a/__tests__/only-labels.spec.ts b/__tests__/only-labels.spec.ts index 2ca5167c0..d8ceddc9d 100644 --- a/__tests__/only-labels.spec.ts +++ b/__tests__/only-labels.spec.ts @@ -1,12 +1,12 @@ 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 {IssuesProcessorMock} from './classes/issues-processor-mock'; import {DefaultProcessorOptions} from './constants/default-processor-options'; import {generateIssue} from './functions/generate-issue'; let issuesProcessorBuilder: IssuesProcessorBuilder; -let issuesProcessor: IssuesProcessor; +let issuesProcessor: IssuesProcessorMock; describe('only-labels option', (): void => { beforeEach((): void => { @@ -1140,8 +1140,8 @@ class IssuesProcessorBuilder { return this; } - build(): IssuesProcessor { - return new IssuesProcessor( + build(): IssuesProcessorMock { + return new IssuesProcessorMock( this._options, async () => 'abot', async p => (p === 1 ? this._issues : []), diff --git a/action.yml b/action.yml index 44a4cb5f4..1bb6375cd 100644 --- a/action.yml +++ b/action.yml @@ -156,6 +156,10 @@ inputs: description: 'Exempt all pull requests with assignees from being marked as stale. Override "exempt-all-assignees" option regarding only the pull requests.' default: '' required: false + enable-statistics: + description: 'Display some statistics at the end regarding the stale workflow (only when the logs are enabled).' + default: 'true' + required: false runs: using: 'node12' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index 4e18b46e1..cbe4c6af4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -206,7 +206,6 @@ const github_1 = __nccwpck_require__(5438); const get_humanized_date_1 = __nccwpck_require__(965); const is_date_more_recent_than_1 = __nccwpck_require__(1473); const is_valid_date_1 = __nccwpck_require__(891); -const get_issue_type_1 = __nccwpck_require__(5153); const is_labeled_1 = __nccwpck_require__(6792); const is_pull_request_1 = __nccwpck_require__(5400); const should_mark_when_stale_1 = __nccwpck_require__(2461); @@ -216,11 +215,12 @@ const issue_1 = __nccwpck_require__(4783); const issue_logger_1 = __nccwpck_require__(2984); const logger_1 = __nccwpck_require__(6212); const milestones_1 = __nccwpck_require__(4601); +const statistics_1 = __nccwpck_require__(3334); /*** * Handle processing of issues for staleness/closure. */ class IssuesProcessor { - constructor(options, getActor, getIssues, listIssueComments, getLabelCreationDate) { + constructor(options) { this._logger = new logger_1.Logger(); this._operationsLeft = 0; this.staleIssues = []; @@ -228,23 +228,14 @@ class IssuesProcessor { this.deletedBranchIssues = []; this.removedLabelIssues = []; this.options = options; - this._operationsLeft = options.operationsPerRun; - this.client = github_1.getOctokit(options.repoToken); - if (getActor) { - this._getActor = getActor; - } - if (getIssues) { - this._getIssues = getIssues; - } - if (listIssueComments) { - this._listIssueComments = listIssueComments; - } - if (getLabelCreationDate) { - this._getLabelCreationDate = getLabelCreationDate; - } + this._operationsLeft = this.options.operationsPerRun; + this.client = github_1.getOctokit(this.options.repoToken); if (this.options.debugOnly) { this._logger.warning('Executing in debug mode. Debug output will be written but no issues will be processed.'); } + if (this.options.enableStatistics) { + this._statistics = new statistics_1.Statistics(this.options); + } } static _updatedSince(timestamp, num_days) { const daysInMillis = 1000 * 60 * 60 * 24 * num_days; @@ -252,18 +243,20 @@ class IssuesProcessor { return millisSinceLastUpdated <= daysInMillis; } processIssues(page = 1) { + var _a, _b; return __awaiter(this, void 0, void 0, function* () { // get the next batch of issues - const issues = yield this._getIssues(page); - this._operationsLeft -= 1; - const actor = yield this._getActor(); + const issues = yield this.getIssues(page); + const actor = yield this.getActor(); if (issues.length <= 0) { this._logger.info('---'); + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.setOperationsLeft(this._operationsLeft).logStats(); this._logger.info('No more issues found to process. Exiting.'); return this._operationsLeft; } for (const issue of issues.values()) { const issueLogger = new issue_logger_1.IssueLogger(issue); + (_b = this._statistics) === null || _b === void 0 ? void 0 : _b.incrementProcessedIssuesCount(); issueLogger.info(`Found this $$type last updated ${issue.updated_at}`); // calculate string based messages for this issue const staleMessage = issue.isPullRequest @@ -281,7 +274,6 @@ class IssuesProcessor { const skipMessage = issue.isPullRequest ? this.options.skipStalePrMessage : this.options.skipStaleIssueMessage; - const issueType = get_issue_type_1.getIssueType(issue.isPullRequest); const daysBeforeStale = issue.isPullRequest ? this._getDaysBeforePrStale() : this._getDaysBeforeIssueStale(); @@ -353,7 +345,7 @@ class IssuesProcessor { const anyOfLabels = words_to_list_1.wordsToList(this.options.anyOfLabels); if (anyOfLabels.length && !anyOfLabels.some((label) => is_labeled_1.isLabeled(issue, label))) { - issueLogger.info(`Skipping ${issueType} because it does not have any of the required labels`); + issueLogger.info(`Skipping $$type because it does not have any of the required labels`); continue; // don't process issues without any of the required labels } const milestones = new milestones_1.Milestones(this.options, issue); @@ -379,7 +371,7 @@ class IssuesProcessor { // process the issue if it was marked stale if (issue.isStale) { issueLogger.info(`Found a stale $$type`); - yield this._processStaleIssue(issue, issueType, staleLabel, actor, closeMessage, closeLabel); + yield this._processStaleIssue(issue, staleLabel, actor, closeMessage, closeLabel); } } if (this._operationsLeft <= 0) { @@ -390,64 +382,14 @@ class IssuesProcessor { return this.processIssues(page + 1); }); } - // handle all of the stale issue logic when we find a stale issue - _processStaleIssue(issue, issueType, staleLabel, actor, closeMessage, closeLabel) { - return __awaiter(this, void 0, void 0, function* () { - const issueLogger = new issue_logger_1.IssueLogger(issue); - const markedStaleOn = (yield this._getLabelCreationDate(issue, staleLabel)) || issue.updated_at; - issueLogger.info(`$$type marked stale on: ${markedStaleOn}`); - const issueHasComments = yield this._hasCommentsSince(issue, markedStaleOn, actor); - issueLogger.info(`$$type has been commented on: ${issueHasComments}`); - const isPr = is_pull_request_1.isPullRequest(issue); - const daysBeforeClose = isPr - ? this._getDaysBeforePrClose() - : this._getDaysBeforeIssueClose(); - issueLogger.info(`Days before $$type close: ${daysBeforeClose}`); - const issueHasUpdate = IssuesProcessor._updatedSince(issue.updated_at, daysBeforeClose); - issueLogger.info(`$$type has been updated: ${issueHasUpdate}`); - // should we un-stale this issue? - if (this.options.removeStaleWhenUpdated && issueHasComments) { - yield this._removeStaleLabel(issue, staleLabel); - } - // now start closing logic - if (daysBeforeClose < 0) { - return; // nothing to do because we aren't closing stale issues - } - if (!issueHasComments && !issueHasUpdate) { - issueLogger.info(`Closing $$type because it was last updated on ${issue.updated_at}`); - yield this._closeIssue(issue, closeMessage, closeLabel); - if (this.options.deleteBranch && issue.pull_request) { - issueLogger.info(`Deleting branch for as delete-branch option was specified`); - yield this._deleteBranch(issue); - this.deletedBranchIssues.push(issue); - } - } - else { - issueLogger.info(`Stale $$type is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})`); - } - }); - } - // checks to see if a given issue is still stale (has had activity on it) - _hasCommentsSince(issue, sinceDate, actor) { - return __awaiter(this, void 0, void 0, function* () { - const issueLogger = new issue_logger_1.IssueLogger(issue); - issueLogger.info(`Checking for comments on $$type since ${sinceDate}`); - if (!sinceDate) { - return true; - } - // find any comments since the date - const comments = yield this._listIssueComments(issue.number, sinceDate); - const filteredComments = comments.filter(comment => comment.user.type === 'User' && comment.user.login !== actor); - issueLogger.info(`Comments not made by actor or another bot: ${filteredComments.length}`); - // if there are any user comments returned - return filteredComments.length > 0; - }); - } // grab comments for an issue since a given date - _listIssueComments(issueNumber, sinceDate) { + listIssueComments(issueNumber, sinceDate) { + var _a; return __awaiter(this, void 0, void 0, function* () { // find any comments since date on the given issue try { + this._operationsLeft -= 1; + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedIssuesCommentsCount(); const comments = yield this.client.issues.listComments({ owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, @@ -463,10 +405,11 @@ class IssuesProcessor { }); } // get the actor from the GitHub token or context - _getActor() { + getActor() { return __awaiter(this, void 0, void 0, function* () { let actor; try { + this._operationsLeft -= 1; actor = yield this.client.users.getAuthenticated(); } catch (error) { @@ -476,11 +419,14 @@ class IssuesProcessor { }); } // grab issues from github in batches of 100 - _getIssues(page) { + getIssues(page) { + var _a; return __awaiter(this, void 0, void 0, function* () { // generate type for response const endpoint = this.client.issues.listForRepo; try { + this._operationsLeft -= 1; + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedIssuesCount(); const issueResult = yield this.client.issues.listForRepo({ owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, @@ -497,13 +443,91 @@ class IssuesProcessor { } }); } + // returns the creation date of a given label on an issue (or nothing if no label existed) + ///see https://developer.github.com/v3/activity/events/ + getLabelCreationDate(issue, label) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const issueLogger = new issue_logger_1.IssueLogger(issue); + issueLogger.info(`Checking for label on $$type`); + this._operationsLeft -= 1; + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedIssuesEventsCount(); + const options = this.client.issues.listEvents.endpoint.merge({ + owner: github_1.context.repo.owner, + repo: github_1.context.repo.repo, + per_page: 100, + issue_number: issue.number + }); + const events = yield this.client.paginate(options); + const reversedEvents = events.reverse(); + const staleLabeledEvent = reversedEvents.find(event => event.event === 'labeled' && event.label.name === label); + if (!staleLabeledEvent) { + // Must be old rather than labeled + return undefined; + } + return staleLabeledEvent.created_at; + }); + } + // handle all of the stale issue logic when we find a stale issue + _processStaleIssue(issue, staleLabel, actor, closeMessage, closeLabel) { + return __awaiter(this, void 0, void 0, function* () { + const issueLogger = new issue_logger_1.IssueLogger(issue); + const markedStaleOn = (yield this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at; + issueLogger.info(`$$type marked stale on: ${markedStaleOn}`); + const issueHasComments = yield this._hasCommentsSince(issue, markedStaleOn, actor); + issueLogger.info(`$$type has been commented on: ${issueHasComments}`); + const isPr = is_pull_request_1.isPullRequest(issue); + const daysBeforeClose = isPr + ? this._getDaysBeforePrClose() + : this._getDaysBeforeIssueClose(); + issueLogger.info(`Days before $$type close: ${daysBeforeClose}`); + const issueHasUpdate = IssuesProcessor._updatedSince(issue.updated_at, daysBeforeClose); + issueLogger.info(`$$type has been updated: ${issueHasUpdate}`); + // should we un-stale this issue? + if (this.options.removeStaleWhenUpdated && issueHasComments) { + yield this._removeStaleLabel(issue, staleLabel); + } + // now start closing logic + if (daysBeforeClose < 0) { + return; // nothing to do because we aren't closing stale issues + } + if (!issueHasComments && !issueHasUpdate) { + issueLogger.info(`Closing $$type because it was last updated on ${issue.updated_at}`); + yield this._closeIssue(issue, closeMessage, closeLabel); + if (this.options.deleteBranch && issue.pull_request) { + issueLogger.info(`Deleting branch for as delete-branch option was specified`); + yield this._deleteBranch(issue); + this.deletedBranchIssues.push(issue); + } + } + else { + issueLogger.info(`Stale $$type is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})`); + } + }); + } + // checks to see if a given issue is still stale (has had activity on it) + _hasCommentsSince(issue, sinceDate, actor) { + return __awaiter(this, void 0, void 0, function* () { + const issueLogger = new issue_logger_1.IssueLogger(issue); + issueLogger.info(`Checking for comments on $$type since ${sinceDate}`); + if (!sinceDate) { + return true; + } + // find any comments since the date + const comments = yield this.listIssueComments(issue.number, sinceDate); + const filteredComments = comments.filter(comment => comment.user.type === 'User' && comment.user.login !== actor); + issueLogger.info(`Comments not made by actor or another bot: ${filteredComments.length}`); + // if there are any user comments returned + return filteredComments.length > 0; + }); + } // Mark an issue as stale with a comment and a label _markStale(issue, staleMessage, staleLabel, skipMessage) { + var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { const issueLogger = new issue_logger_1.IssueLogger(issue); issueLogger.info(`Marking $$type as stale`); this.staleIssues.push(issue); - this._operationsLeft -= 2; // if the issue is being marked stale, the updated date should be changed to right now // so that close calculations work correctly const newUpdatedAtDate = new Date(); @@ -513,6 +537,8 @@ class IssuesProcessor { } if (!skipMessage) { try { + this._operationsLeft -= 1; + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementAddedComment(); yield this.client.issues.createComment({ owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, @@ -525,6 +551,9 @@ class IssuesProcessor { } } try { + this._operationsLeft -= 1; + (_b = this._statistics) === null || _b === void 0 ? void 0 : _b.incrementAddedLabel(); + (_c = this._statistics) === null || _c === void 0 ? void 0 : _c.incrementStaleIssuesCount(); yield this.client.issues.addLabels({ owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, @@ -539,16 +568,18 @@ class IssuesProcessor { } // Close an issue based on staleness _closeIssue(issue, closeMessage, closeLabel) { + var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { const issueLogger = new issue_logger_1.IssueLogger(issue); issueLogger.info(`Closing $$type for being stale`); this.closedIssues.push(issue); - this._operationsLeft -= 1; if (this.options.debugOnly) { return; } if (closeMessage) { try { + this._operationsLeft -= 1; + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementAddedComment(); yield this.client.issues.createComment({ owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, @@ -562,6 +593,8 @@ class IssuesProcessor { } if (closeLabel) { try { + this._operationsLeft -= 1; + (_b = this._statistics) === null || _b === void 0 ? void 0 : _b.incrementAddedLabel(); yield this.client.issues.addLabels({ owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, @@ -574,6 +607,8 @@ class IssuesProcessor { } } try { + this._operationsLeft -= 1; + (_c = this._statistics) === null || _c === void 0 ? void 0 : _c.incrementClosedIssuesCount(); yield this.client.issues.update({ owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, @@ -587,10 +622,15 @@ class IssuesProcessor { }); } _getPullRequest(issue) { + var _a; return __awaiter(this, void 0, void 0, function* () { const issueLogger = new issue_logger_1.IssueLogger(issue); - this._operationsLeft -= 1; + if (this.options.debugOnly) { + return; + } try { + this._operationsLeft -= 1; + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedPullRequestsCount(); const pullRequest = yield this.client.pulls.get({ owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, @@ -605,21 +645,23 @@ class IssuesProcessor { } // Delete the branch on closed pull request _deleteBranch(issue) { + var _a; return __awaiter(this, void 0, void 0, function* () { const issueLogger = new issue_logger_1.IssueLogger(issue); issueLogger.info(`Delete branch from closed $$type - ${issue.title}`); - if (this.options.debugOnly) { - return; - } const pullRequest = yield this._getPullRequest(issue); if (!pullRequest) { issueLogger.info(`Not deleting branch as pull request not found for this $$type`); return; } + if (this.options.debugOnly) { + return; + } const branch = pullRequest.head.ref; issueLogger.info(`Deleting branch ${branch} from closed $$type`); - this._operationsLeft -= 1; try { + this._operationsLeft -= 1; + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementDeletedBranchesCount(); yield this.client.git.deleteRef({ owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, @@ -633,16 +675,18 @@ class IssuesProcessor { } // Remove a label from an issue _removeLabel(issue, label) { + var _a; return __awaiter(this, void 0, void 0, function* () { const issueLogger = new issue_logger_1.IssueLogger(issue); issueLogger.info(`Removing label "${label}" from $$type`); this.removedLabelIssues.push(issue); - this._operationsLeft -= 1; // @todo remove the debug only to be able to test the code below if (this.options.debugOnly) { return; } try { + this._operationsLeft -= 1; + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementDeletedLabelsCount(); yield this.client.issues.removeLabel({ owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, @@ -655,29 +699,6 @@ class IssuesProcessor { } }); } - // returns the creation date of a given label on an issue (or nothing if no label existed) - ///see https://developer.github.com/v3/activity/events/ - _getLabelCreationDate(issue, label) { - return __awaiter(this, void 0, void 0, function* () { - const issueLogger = new issue_logger_1.IssueLogger(issue); - issueLogger.info(`Checking for label on $$type`); - this._operationsLeft -= 1; - const options = this.client.issues.listEvents.endpoint.merge({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, - per_page: 100, - issue_number: issue.number - }); - const events = yield this.client.paginate(options); - const reversedEvents = events.reverse(); - const staleLabeledEvent = reversedEvents.find(event => event.event === 'labeled' && event.label.name === label); - if (!staleLabeledEvent) { - // Must be old rather than labeled - return undefined; - } - return staleLabeledEvent.created_at; - }); - } _getDaysBeforeIssueStale() { return isNaN(this.options.daysBeforeIssueStale) ? this.options.daysBeforeStale @@ -712,13 +733,16 @@ class IssuesProcessor { return this.options.onlyLabels; } _removeStaleLabel(issue, staleLabel) { + var _a; return __awaiter(this, void 0, void 0, function* () { const issueLogger = new issue_logger_1.IssueLogger(issue); issueLogger.info(`The $$type is no longer stale. Removing the stale label...`); - return this._removeLabel(issue, staleLabel); + yield this._removeLabel(issue, staleLabel); + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementUndoStaleIssuesCount(); }); } _removeCloseLabel(issue, closeLabel) { + var _a; 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...`); @@ -728,7 +752,8 @@ class IssuesProcessor { } 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); + yield this._removeLabel(issue, closeLabel); + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementDeletedCloseLabelsCount(); } }); } @@ -938,18 +963,157 @@ exports.Milestones = Milestones; /***/ }), -/***/ 9639: -/***/ ((__unused_webpack_module, exports) => { +/***/ 3334: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.IssueType = void 0; -var IssueType; -(function (IssueType) { - IssueType["Issue"] = "issue"; - IssueType["PullRequest"] = "pr"; -})(IssueType = exports.IssueType || (exports.IssueType = {})); +exports.Statistics = void 0; +const logger_1 = __nccwpck_require__(6212); +class Statistics { + constructor(options) { + this._logger = new logger_1.Logger(); + this._processedIssuesCount = 0; + this._staleIssuesCount = 0; + this._undoStaleIssuesCount = 0; + this._operationsCount = 0; + this._closedIssuesCount = 0; + this._deletedLabelsCount = 0; + this._deletedCloseLabelsCount = 0; + this._deletedBranchesCount = 0; + this._addedLabelsCount = 0; + this._addedCommentsCount = 0; + this._fetchedIssuesCount = 0; + this._fetchedIssuesEventsCount = 0; + this._fetchedIssuesCommentsCount = 0; + this._fetchedPullRequestsCount = 0; + this._options = options; + } + incrementProcessedIssuesCount(increment = 1) { + this._processedIssuesCount += increment; + return this; + } + incrementStaleIssuesCount(increment = 1) { + this._staleIssuesCount += increment; + return this; + } + incrementUndoStaleIssuesCount(increment = 1) { + this._undoStaleIssuesCount += increment; + return this; + } + setOperationsLeft(operationsLeft) { + this._operationsCount = this._options.operationsPerRun - operationsLeft; + return this; + } + incrementClosedIssuesCount(increment = 1) { + this._closedIssuesCount += increment; + return this; + } + incrementDeletedLabelsCount(increment = 1) { + this._deletedLabelsCount += increment; + return this; + } + incrementDeletedCloseLabelsCount(increment = 1) { + this._deletedCloseLabelsCount += increment; + return this; + } + incrementDeletedBranchesCount(increment = 1) { + this._deletedBranchesCount += increment; + return this; + } + incrementAddedLabel(increment = 1) { + this._addedLabelsCount += increment; + return this; + } + incrementAddedComment(increment = 1) { + this._addedCommentsCount += increment; + return this; + } + incrementFetchedIssuesCount(increment = 1) { + this._fetchedIssuesCount += increment; + return this; + } + incrementFetchedIssuesEventsCount(increment = 1) { + this._fetchedIssuesEventsCount += increment; + return this; + } + incrementFetchedIssuesCommentsCount(increment = 1) { + this._fetchedIssuesCommentsCount += increment; + return this; + } + incrementFetchedPullRequestsCount(increment = 1) { + this._fetchedPullRequestsCount += increment; + return this; + } + logStats() { + this._logger.info('Statistics'); + this._logProcessedIssuesCount(); + this._logStaleIssuesCount(); + this._logUndoStaleIssuesCount(); + this._logOperationsCount(); + this._logClosedIssuesCount(); + this._logDeletedLabelsCount(); + this._logDeletedCloseLabelsCount(); + this._logDeletedBranchesCount(); + this._logAddedLabelsCount(); + this._logAddedCommentsCount(); + this._logFetchedIssuesCount(); + this._logFetchedIssuesEventsCount(); + this._logFetchedIssuesCommentsCount(); + this._logFetchedPullRequestsCount(); + this._logger.info('---'); + return this; + } + _logProcessedIssuesCount() { + this._logCount('Processed issues/PRs', this._processedIssuesCount); + } + _logStaleIssuesCount() { + this._logCount('New stale issues/PRs', this._staleIssuesCount); + } + _logUndoStaleIssuesCount() { + this._logCount('No longer stale issues/PRs', this._undoStaleIssuesCount); + } + _logOperationsCount() { + this._logCount('Operations performed', this._operationsCount); + } + _logClosedIssuesCount() { + this._logCount('Closed issues', this._closedIssuesCount); + } + _logDeletedLabelsCount() { + this._logCount('Deleted labels', this._deletedLabelsCount); + } + _logDeletedCloseLabelsCount() { + this._logCount('Deleted close labels', this._deletedCloseLabelsCount); + } + _logDeletedBranchesCount() { + this._logCount('Deleted branches', this._deletedBranchesCount); + } + _logAddedLabelsCount() { + this._logCount('Added labels', this._addedLabelsCount); + } + _logAddedCommentsCount() { + this._logCount('Added comments', this._addedCommentsCount); + } + _logFetchedIssuesCount() { + this._logCount('Fetched issues', this._fetchedIssuesCount); + } + _logFetchedIssuesEventsCount() { + this._logCount('Fetched issues events', this._fetchedIssuesEventsCount); + } + _logFetchedIssuesCommentsCount() { + this._logCount('Fetched issues comments', this._fetchedIssuesCommentsCount); + } + _logFetchedPullRequestsCount() { + this._logCount('Fetched pull requests', this._fetchedPullRequestsCount); + } + _logCount(name, count) { + if (count > 0) { + this._logger.info(`${name}: ${count}`); + } + } +} +exports.Statistics = Statistics; /***/ }), @@ -1020,22 +1184,6 @@ function isValidDate(date) { exports.isValidDate = isValidDate; -/***/ }), - -/***/ 5153: -/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getIssueType = void 0; -const issue_type_1 = __nccwpck_require__(9639); -function getIssueType(isPullRequest) { - return isPullRequest ? issue_type_1.IssueType.PullRequest : issue_type_1.IssueType.Issue; -} -exports.getIssueType = getIssueType; - - /***/ }), /***/ 6792: @@ -1229,7 +1377,8 @@ function _getAndValidateArgs() { exemptPrAssignees: core.getInput('exempt-pr-assignees'), exemptAllAssignees: core.getInput('exempt-all-assignees') === 'true', exemptAllIssueAssignees: _toOptionalBoolean('exempt-all-issue-assignees'), - exemptAllPrAssignees: _toOptionalBoolean('exempt-all-pr-assignees') + exemptAllPrAssignees: _toOptionalBoolean('exempt-all-pr-assignees'), + enableStatistics: core.getInput('enable-statistics') === 'true' }; for (const numberInput of [ 'days-before-stale', diff --git a/src/classes/issue.spec.ts b/src/classes/issue.spec.ts index a79ab5dec..8e7afff67 100644 --- a/src/classes/issue.spec.ts +++ b/src/classes/issue.spec.ts @@ -52,7 +52,8 @@ describe('Issue', (): void => { exemptPrAssignees: '', exemptAllAssignees: false, exemptAllIssueAssignees: undefined, - exemptAllPrAssignees: undefined + exemptAllPrAssignees: undefined, + enableStatistics: false }; issueInterface = { title: 'dummy-title', diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts index b82cb5275..70a704a1c 100644 --- a/src/classes/issues-processor.ts +++ b/src/classes/issues-processor.ts @@ -1,11 +1,9 @@ import {context, getOctokit} from '@actions/github'; import {GitHub} from '@actions/github/lib/utils'; import {GetResponseTypeFromEndpointMethod} from '@octokit/types'; -import {IssueType} from '../enums/issue-type'; import {getHumanizedDate} from '../functions/dates/get-humanized-date'; import {isDateMoreRecentThan} from '../functions/dates/is-date-more-recent-than'; import {isValidDate} from '../functions/dates/is-valid-date'; -import {getIssueType} from '../functions/get-issue-type'; import {isLabeled} from '../functions/is-labeled'; import {isPullRequest} from '../functions/is-pull-request'; import {shouldMarkWhenStale} from '../functions/should-mark-when-stale'; @@ -20,6 +18,7 @@ import {Issue} from './issue'; import {IssueLogger} from './loggers/issue-logger'; import {Logger} from './loggers/logger'; import {Milestones} from './milestones'; +import {Statistics} from './statistics'; /*** * Handle processing of issues for staleness/closure. @@ -34,6 +33,7 @@ export class IssuesProcessor { } private readonly _logger: Logger = new Logger(); + private readonly _statistics: Statistics | undefined; private _operationsLeft = 0; readonly client: InstanceType; readonly options: IIssuesProcessorOptions; @@ -42,61 +42,38 @@ export class IssuesProcessor { readonly deletedBranchIssues: Issue[] = []; readonly removedLabelIssues: Issue[] = []; - constructor( - options: IIssuesProcessorOptions, - getActor?: () => Promise, - getIssues?: (page: number) => Promise, - listIssueComments?: ( - issueNumber: number, - sinceDate: string - ) => Promise, - getLabelCreationDate?: ( - issue: Issue, - label: string - ) => Promise - ) { + constructor(options: IIssuesProcessorOptions) { this.options = options; - this._operationsLeft = options.operationsPerRun; - this.client = getOctokit(options.repoToken); - - if (getActor) { - this._getActor = getActor; - } - - if (getIssues) { - this._getIssues = getIssues; - } - - if (listIssueComments) { - this._listIssueComments = listIssueComments; - } - - if (getLabelCreationDate) { - this._getLabelCreationDate = getLabelCreationDate; - } + this._operationsLeft = this.options.operationsPerRun; + this.client = getOctokit(this.options.repoToken); if (this.options.debugOnly) { this._logger.warning( 'Executing in debug mode. Debug output will be written but no issues will be processed.' ); } + + if (this.options.enableStatistics) { + this._statistics = new Statistics(this.options); + } } async processIssues(page = 1): Promise { // get the next batch of issues - const issues: Issue[] = await this._getIssues(page); - this._operationsLeft -= 1; - - const actor: string = await this._getActor(); + const issues: Issue[] = await this.getIssues(page); + const actor: string = await this.getActor(); if (issues.length <= 0) { this._logger.info('---'); + this._statistics?.setOperationsLeft(this._operationsLeft).logStats(); this._logger.info('No more issues found to process. Exiting.'); + return this._operationsLeft; } for (const issue of issues.values()) { const issueLogger: IssueLogger = new IssueLogger(issue); + this._statistics?.incrementProcessedIssuesCount(); issueLogger.info(`Found this $$type last updated ${issue.updated_at}`); @@ -116,7 +93,6 @@ export class IssuesProcessor { const skipMessage = issue.isPullRequest ? this.options.skipStalePrMessage : this.options.skipStaleIssueMessage; - const issueType: IssueType = getIssueType(issue.isPullRequest); const daysBeforeStale: number = issue.isPullRequest ? this._getDaysBeforePrStale() : this._getDaysBeforeIssueStale(); @@ -238,7 +214,7 @@ export class IssuesProcessor { ) ) { issueLogger.info( - `Skipping ${issueType} because it does not have any of the required labels` + `Skipping $$type because it does not have any of the required labels` ); continue; // don't process issues without any of the required labels } @@ -282,7 +258,6 @@ export class IssuesProcessor { issueLogger.info(`Found a stale $$type`); await this._processStaleIssue( issue, - issueType, staleLabel, actor, closeMessage, @@ -302,10 +277,108 @@ export class IssuesProcessor { return this.processIssues(page + 1); } + // grab comments for an issue since a given date + async listIssueComments( + issueNumber: number, + sinceDate: string + ): Promise { + // find any comments since date on the given issue + try { + this._operationsLeft -= 1; + this._statistics?.incrementFetchedIssuesCommentsCount(); + const comments = await this.client.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + since: sinceDate + }); + return comments.data; + } catch (error) { + this._logger.error(`List issue comments error: ${error.message}`); + return Promise.resolve([]); + } + } + + // get the actor from the GitHub token or context + async getActor(): Promise { + let actor; + + try { + this._operationsLeft -= 1; + actor = await this.client.users.getAuthenticated(); + } catch (error) { + return context.actor; + } + + return actor.data.login; + } + + // grab issues from github in batches of 100 + async getIssues(page: number): Promise { + // generate type for response + const endpoint = this.client.issues.listForRepo; + type OctoKitIssueList = GetResponseTypeFromEndpointMethod; + + try { + this._operationsLeft -= 1; + this._statistics?.incrementFetchedIssuesCount(); + 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 + } + ); + + return issueResult.data.map( + (issue: Readonly): Issue => new Issue(this.options, issue) + ); + } catch (error) { + this._logger.error(`Get issues for repo error: ${error.message}`); + return Promise.resolve([]); + } + } + + // returns the creation date of a given label on an issue (or nothing if no label existed) + ///see https://developer.github.com/v3/activity/events/ + async getLabelCreationDate( + issue: Issue, + label: string + ): Promise { + const issueLogger: IssueLogger = new IssueLogger(issue); + + issueLogger.info(`Checking for label on $$type`); + + this._operationsLeft -= 1; + this._statistics?.incrementFetchedIssuesEventsCount(); + const options = this.client.issues.listEvents.endpoint.merge({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100, + issue_number: issue.number + }); + + const events: IIssueEvent[] = await this.client.paginate(options); + const reversedEvents = events.reverse(); + + const staleLabeledEvent = reversedEvents.find( + event => event.event === 'labeled' && event.label.name === label + ); + + if (!staleLabeledEvent) { + // Must be old rather than labeled + return undefined; + } + + return staleLabeledEvent.created_at; + } + // handle all of the stale issue logic when we find a stale issue private async _processStaleIssue( issue: Issue, - issueType: IssueType, staleLabel: string, actor: string, closeMessage?: string, @@ -313,7 +386,7 @@ export class IssuesProcessor { ) { const issueLogger: IssueLogger = new IssueLogger(issue); const markedStaleOn: string = - (await this._getLabelCreationDate(issue, staleLabel)) || issue.updated_at; + (await this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at; issueLogger.info(`$$type marked stale on: ${markedStaleOn}`); const issueHasComments: boolean = await this._hasCommentsSince( @@ -381,7 +454,7 @@ export class IssuesProcessor { } // find any comments since the date - const comments = await this._listIssueComments(issue.number, sinceDate); + const comments = await this.listIssueComments(issue.number, sinceDate); const filteredComments = comments.filter( comment => comment.user.type === 'User' && comment.user.login !== actor @@ -395,65 +468,6 @@ export class IssuesProcessor { return filteredComments.length > 0; } - // grab comments for an issue since a given date - private async _listIssueComments( - issueNumber: number, - sinceDate: string - ): Promise { - // find any comments since date on the given issue - try { - const comments = await this.client.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - since: sinceDate - }); - return comments.data; - } catch (error) { - this._logger.error(`List issue comments error: ${error.message}`); - return Promise.resolve([]); - } - } - - // get the actor from the GitHub token or context - private async _getActor(): Promise { - let actor; - try { - actor = await this.client.users.getAuthenticated(); - } catch (error) { - return context.actor; - } - - return actor.data.login; - } - - // grab issues from github in batches of 100 - private async _getIssues(page: number): Promise { - // generate type for response - const endpoint = this.client.issues.listForRepo; - type OctoKitIssueList = GetResponseTypeFromEndpointMethod; - - try { - 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 - } - ); - - return issueResult.data.map( - (issue: Readonly): Issue => new Issue(this.options, issue) - ); - } catch (error) { - this._logger.error(`Get issues for repo error: ${error.message}`); - return Promise.resolve([]); - } - } - // Mark an issue as stale with a comment and a label private async _markStale( issue: Issue, @@ -464,11 +478,8 @@ export class IssuesProcessor { const issueLogger: IssueLogger = new IssueLogger(issue); issueLogger.info(`Marking $$type as stale`); - this.staleIssues.push(issue); - this._operationsLeft -= 2; - // if the issue is being marked stale, the updated date should be changed to right now // so that close calculations work correctly const newUpdatedAtDate: Date = new Date(); @@ -480,6 +491,8 @@ export class IssuesProcessor { if (!skipMessage) { try { + this._operationsLeft -= 1; + this._statistics?.incrementAddedComment(); await this.client.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, @@ -492,6 +505,9 @@ export class IssuesProcessor { } try { + this._operationsLeft -= 1; + this._statistics?.incrementAddedLabel(); + this._statistics?.incrementStaleIssuesCount(); await this.client.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, @@ -512,17 +528,16 @@ export class IssuesProcessor { const issueLogger: IssueLogger = new IssueLogger(issue); issueLogger.info(`Closing $$type for being stale`); - this.closedIssues.push(issue); - this._operationsLeft -= 1; - if (this.options.debugOnly) { return; } if (closeMessage) { try { + this._operationsLeft -= 1; + this._statistics?.incrementAddedComment(); await this.client.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, @@ -536,6 +551,8 @@ export class IssuesProcessor { if (closeLabel) { try { + this._operationsLeft -= 1; + this._statistics?.incrementAddedLabel(); await this.client.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, @@ -548,6 +565,8 @@ export class IssuesProcessor { } try { + this._operationsLeft -= 1; + this._statistics?.incrementClosedIssuesCount(); await this.client.issues.update({ owner: context.repo.owner, repo: context.repo.repo, @@ -561,11 +580,16 @@ export class IssuesProcessor { private async _getPullRequest( issue: Issue - ): Promise { + ): Promise { const issueLogger: IssueLogger = new IssueLogger(issue); - this._operationsLeft -= 1; + + if (this.options.debugOnly) { + return; + } try { + this._operationsLeft -= 1; + this._statistics?.incrementFetchedPullRequestsCount(); const pullRequest = await this.client.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, @@ -584,10 +608,6 @@ export class IssuesProcessor { issueLogger.info(`Delete branch from closed $$type - ${issue.title}`); - if (this.options.debugOnly) { - return; - } - const pullRequest = await this._getPullRequest(issue); if (!pullRequest) { @@ -597,12 +617,16 @@ export class IssuesProcessor { return; } + if (this.options.debugOnly) { + return; + } + const branch = pullRequest.head.ref; issueLogger.info(`Deleting branch ${branch} from closed $$type`); - this._operationsLeft -= 1; - try { + this._operationsLeft -= 1; + this._statistics?.incrementDeletedBranchesCount(); await this.client.git.deleteRef({ owner: context.repo.owner, repo: context.repo.repo, @@ -620,17 +644,16 @@ export class IssuesProcessor { const issueLogger: IssueLogger = new IssueLogger(issue); issueLogger.info(`Removing label "${label}" from $$type`); - this.removedLabelIssues.push(issue); - this._operationsLeft -= 1; - // @todo remove the debug only to be able to test the code below if (this.options.debugOnly) { return; } try { + this._operationsLeft -= 1; + this._statistics?.incrementDeletedLabelsCount(); await this.client.issues.removeLabel({ owner: context.repo.owner, repo: context.repo.repo, @@ -642,40 +665,6 @@ export class IssuesProcessor { } } - // returns the creation date of a given label on an issue (or nothing if no label existed) - ///see https://developer.github.com/v3/activity/events/ - private async _getLabelCreationDate( - issue: Issue, - label: string - ): Promise { - const issueLogger: IssueLogger = new IssueLogger(issue); - - issueLogger.info(`Checking for label on $$type`); - - this._operationsLeft -= 1; - - const options = this.client.issues.listEvents.endpoint.merge({ - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 100, - issue_number: issue.number - }); - - const events: IIssueEvent[] = await this.client.paginate(options); - const reversedEvents = events.reverse(); - - const staleLabeledEvent = reversedEvents.find( - event => event.event === 'labeled' && event.label.name === label - ); - - if (!staleLabeledEvent) { - // Must be old rather than labeled - return undefined; - } - - return staleLabeledEvent.created_at; - } - private _getDaysBeforeIssueStale(): number { return isNaN(this.options.daysBeforeIssueStale) ? this.options.daysBeforeStale @@ -724,7 +713,8 @@ export class IssuesProcessor { `The $$type is no longer stale. Removing the stale label...` ); - return this._removeLabel(issue, staleLabel); + await this._removeLabel(issue, staleLabel); + this._statistics?.incrementUndoStaleIssuesCount(); } private async _removeCloseLabel( @@ -748,7 +738,8 @@ export class IssuesProcessor { `The $$type has a close label "${closeLabel}". Removing the close label...` ); - return this._removeLabel(issue, closeLabel); + await this._removeLabel(issue, closeLabel); + this._statistics?.incrementDeletedCloseLabelsCount(); } } } diff --git a/src/classes/statistics.ts b/src/classes/statistics.ts new file mode 100644 index 000000000..a5fefe4ca --- /dev/null +++ b/src/classes/statistics.ts @@ -0,0 +1,200 @@ +import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options'; +import {Logger} from './loggers/logger'; + +export class Statistics { + private readonly _logger: Logger = new Logger(); + private readonly _options: IIssuesProcessorOptions; + private _processedIssuesCount = 0; + private _staleIssuesCount = 0; + private _undoStaleIssuesCount = 0; + private _operationsCount = 0; + private _closedIssuesCount = 0; + private _deletedLabelsCount = 0; + private _deletedCloseLabelsCount = 0; + private _deletedBranchesCount = 0; + private _addedLabelsCount = 0; + private _addedCommentsCount = 0; + private _fetchedIssuesCount = 0; + private _fetchedIssuesEventsCount = 0; + private _fetchedIssuesCommentsCount = 0; + private _fetchedPullRequestsCount = 0; + + constructor(options: IIssuesProcessorOptions) { + this._options = options; + } + + incrementProcessedIssuesCount(increment: Readonly = 1): Statistics { + this._processedIssuesCount += increment; + + return this; + } + + incrementStaleIssuesCount(increment: Readonly = 1): Statistics { + this._staleIssuesCount += increment; + + return this; + } + + incrementUndoStaleIssuesCount(increment: Readonly = 1): Statistics { + this._undoStaleIssuesCount += increment; + + return this; + } + + setOperationsLeft(operationsLeft: Readonly): Statistics { + this._operationsCount = this._options.operationsPerRun - operationsLeft; + + return this; + } + + incrementClosedIssuesCount(increment: Readonly = 1): Statistics { + this._closedIssuesCount += increment; + + return this; + } + + incrementDeletedLabelsCount(increment: Readonly = 1): Statistics { + this._deletedLabelsCount += increment; + + return this; + } + + incrementDeletedCloseLabelsCount( + increment: Readonly = 1 + ): Statistics { + this._deletedCloseLabelsCount += increment; + + return this; + } + + incrementDeletedBranchesCount(increment: Readonly = 1): Statistics { + this._deletedBranchesCount += increment; + + return this; + } + + incrementAddedLabel(increment: Readonly = 1): Statistics { + this._addedLabelsCount += increment; + + return this; + } + + incrementAddedComment(increment: Readonly = 1): Statistics { + this._addedCommentsCount += increment; + + return this; + } + + incrementFetchedIssuesCount(increment: Readonly = 1): Statistics { + this._fetchedIssuesCount += increment; + + return this; + } + + incrementFetchedIssuesEventsCount( + increment: Readonly = 1 + ): Statistics { + this._fetchedIssuesEventsCount += increment; + + return this; + } + + incrementFetchedIssuesCommentsCount( + increment: Readonly = 1 + ): Statistics { + this._fetchedIssuesCommentsCount += increment; + + return this; + } + + incrementFetchedPullRequestsCount( + increment: Readonly = 1 + ): Statistics { + this._fetchedPullRequestsCount += increment; + + return this; + } + + logStats(): Statistics { + this._logger.info('Statistics'); + this._logProcessedIssuesCount(); + this._logStaleIssuesCount(); + this._logUndoStaleIssuesCount(); + this._logOperationsCount(); + this._logClosedIssuesCount(); + this._logDeletedLabelsCount(); + this._logDeletedCloseLabelsCount(); + this._logDeletedBranchesCount(); + this._logAddedLabelsCount(); + this._logAddedCommentsCount(); + this._logFetchedIssuesCount(); + this._logFetchedIssuesEventsCount(); + this._logFetchedIssuesCommentsCount(); + this._logFetchedPullRequestsCount(); + this._logger.info('---'); + + return this; + } + + private _logProcessedIssuesCount(): void { + this._logCount('Processed issues/PRs', this._processedIssuesCount); + } + + private _logStaleIssuesCount(): void { + this._logCount('New stale issues/PRs', this._staleIssuesCount); + } + + private _logUndoStaleIssuesCount(): void { + this._logCount('No longer stale issues/PRs', this._undoStaleIssuesCount); + } + + private _logOperationsCount(): void { + this._logCount('Operations performed', this._operationsCount); + } + + private _logClosedIssuesCount(): void { + this._logCount('Closed issues', this._closedIssuesCount); + } + + private _logDeletedLabelsCount(): void { + this._logCount('Deleted labels', this._deletedLabelsCount); + } + + private _logDeletedCloseLabelsCount(): void { + this._logCount('Deleted close labels', this._deletedCloseLabelsCount); + } + + private _logDeletedBranchesCount(): void { + this._logCount('Deleted branches', this._deletedBranchesCount); + } + + private _logAddedLabelsCount(): void { + this._logCount('Added labels', this._addedLabelsCount); + } + + private _logAddedCommentsCount(): void { + this._logCount('Added comments', this._addedCommentsCount); + } + + private _logFetchedIssuesCount(): void { + this._logCount('Fetched issues', this._fetchedIssuesCount); + } + + private _logFetchedIssuesEventsCount(): void { + this._logCount('Fetched issues events', this._fetchedIssuesEventsCount); + } + + private _logFetchedIssuesCommentsCount(): void { + this._logCount('Fetched issues comments', this._fetchedIssuesCommentsCount); + } + + private _logFetchedPullRequestsCount(): void { + this._logCount('Fetched pull requests', this._fetchedPullRequestsCount); + } + + private _logCount(name: Readonly, count: Readonly): void { + if (count > 0) { + this._logger.info(`${name}: ${count}`); + } + } +} diff --git a/src/functions/get-issue-type.spec.ts b/src/functions/get-issue-type.spec.ts deleted file mode 100644 index 993cb725c..000000000 --- a/src/functions/get-issue-type.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {getIssueType} from './get-issue-type'; - -describe('getIssueType()', (): void => { - let isPullRequest: boolean; - - describe('when the issue is a not pull request', (): void => { - beforeEach((): void => { - isPullRequest = false; - }); - - it('should return that the issue is really an issue', (): void => { - expect.assertions(1); - - const result = getIssueType(isPullRequest); - - expect(result).toStrictEqual('issue'); - }); - }); - - describe('when the issue is a pull request', (): void => { - beforeEach((): void => { - isPullRequest = true; - }); - - it('should return that the issue is a pull request', (): void => { - expect.assertions(1); - - const result = getIssueType(isPullRequest); - - expect(result).toStrictEqual('pr'); - }); - }); -}); diff --git a/src/functions/get-issue-type.ts b/src/functions/get-issue-type.ts deleted file mode 100644 index d3603286d..000000000 --- a/src/functions/get-issue-type.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {IssueType} from '../enums/issue-type'; - -export function getIssueType(isPullRequest: Readonly): IssueType { - return isPullRequest ? IssueType.PullRequest : IssueType.Issue; -} diff --git a/src/interfaces/issues-processor-options.ts b/src/interfaces/issues-processor-options.ts index 155e94f2c..3c62d95b8 100644 --- a/src/interfaces/issues-processor-options.ts +++ b/src/interfaces/issues-processor-options.ts @@ -42,4 +42,5 @@ export interface IIssuesProcessorOptions { exemptAllAssignees: boolean; exemptAllIssueAssignees: boolean | undefined; exemptAllPrAssignees: boolean | undefined; + enableStatistics: boolean; } diff --git a/src/main.ts b/src/main.ts index 255a57d72..33910815f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -68,7 +68,8 @@ function _getAndValidateArgs(): IIssuesProcessorOptions { exemptPrAssignees: core.getInput('exempt-pr-assignees'), exemptAllAssignees: core.getInput('exempt-all-assignees') === 'true', exemptAllIssueAssignees: _toOptionalBoolean('exempt-all-issue-assignees'), - exemptAllPrAssignees: _toOptionalBoolean('exempt-all-pr-assignees') + exemptAllPrAssignees: _toOptionalBoolean('exempt-all-pr-assignees'), + enableStatistics: core.getInput('enable-statistics') === 'true' }; for (const numberInput of [