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
This commit is contained in:
Geoffrey Testelin 2021-03-01 21:34:35 +01:00 committed by GitHub
parent 63ae8ac024
commit 419a53bc05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1021 additions and 679 deletions

114
README.md
View File

@ -4,56 +4,51 @@ Warns and then closes issues and PRs that have had no activity for a specified a
### Arguments ### Arguments
| Input | Description | Usage | | Input | Description | Usage |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------- | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------- |
| `repo-token` | PAT(Personal Access Token) for authorizing repository. _Defaults to **${{ github.token }}**_ | Optional | | `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-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-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-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-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-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 | | `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-issue-message` | Message to post on the stale issue. | Optional |
| `stale-pr-message` | Message to post on the stale PR. | 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-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 | | `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 | | `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 | | `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 | | `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 | | `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-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-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 | | `only-labels` | Only issues and PRs with ALL these labels are checked. Separate multiple labels with commas (eg. "question,answered"). | Optional |
| `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale (override `exempt-milestones`). | Optional | | `only-labels` | Only labels checked for stale issue/PR. | Optional |
| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale (override `exempt-milestones`). | Optional | | `only-issue-labels` | Only labels checked for stale issue (override `only-labels`). | Optional |
| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale. (priority over `exempt-milestones` rules) | Optional | | `only-pr-labels` | Only labels checked for stale PR (override `only-labels`). | Optional |
| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale. (override `exempt-all-milestones`). | 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 |
| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional | | `operations-per-run` | Maximum number of operations per run (GitHub API CRUD related). _Defaults to **30**_ | Optional |
| `only-labels` | Only issues and PRs with ALL these labels are checked. Separate multiple labels with commas (eg. "question,answered"). | Optional | | `remove-stale-when-updated` | Remove stale label from issue/PR on updates or comments. _Defaults to **true**_ | Optional |
| `only-labels` | Only labels checked for stale issue/PR. | Optional | | `debug-only` | Dry-run on action. _Defaults to **false**_ | Optional |
| `only-issue-labels` | Only labels checked for stale issue (override `only-labels`). | Optional | | `ascending` | Order to get issues/PR. _Defaults to **false**_ | Optional |
| `only-pr-labels` | Only labels checked for stale PR (override `only-labels`). | Optional | | `skip-stale-issue-message` | Skip adding stale message on stale issue. _Defaults to **false**_ | 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 | | `skip-stale-pr-message` | Skip adding stale message on stale PR. _Defaults to **false**_ | Optional |
| `operations-per-run` | Maximum number of operations per run (GitHub API CRUD related). _Defaults to **30**_ | Optional | | `start-date` | The date used to skip the stale action on issue/PR created before it (ISO 8601 or RFC 2822). | Optional |
| `remove-stale-when-updated` | Remove stale label from issue/PR on updates or comments. _Defaults to **true**_ | Optional | | `delete-branch` | Delete the git branch after closing a stale pull request. _Defaults to **false**_ | Optional |
| `debug-only` | Dry-run on action. _Defaults to **false**_ | Optional | | `exempt-milestones` | Milestones on an issue or a PR exempted from being marked as stale. | Optional |
| `ascending` | Order to get issues/PR. _Defaults to **false**_ | Optional | | `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale (override `exempt-milestones`). | Optional |
| `skip-stale-issue-message` | Skip adding stale message on stale issue. _Defaults to **false**_ | Optional | | `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale (override `exempt-milestones`). | Optional |
| `skip-stale-pr-message` | Skip adding stale message on stale PR. _Defaults to **false**_ | Optional | | `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale. (priority over `exempt-milestones` rules) | Optional |
| `start-date` | The date used to skip the stale action on issue/PR created before it (ISO 8601 or RFC 2822). | Optional | | `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional |
| `delete-branch` | Delete the git branch after closing a stale pull request. _Defaults to **false**_ | Optional | | `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional |
| `exempt-milestones` | Milestones on an issue or a PR exempted from being marked as stale. | Optional | | `exempt-assignees` | Assignees 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-issue-assignees` | Assignees on an issue exempted from being marked as stale (override `exempt-assignees`). | Optional |
| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale (override `exempt-milestones`). | Optional | | `exempt-pr-assignees` | Assignees on the PR exempted from being marked as stale (override `exempt-assignees`). | Optional |
| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale. (priority over `exempt-milestones` rules) | 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-milestones` | Exempt all issues with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional | | `exempt-all-issue-assignees` | Exempt all issues with assignees from being marked as stale. (override `exempt-all-assignees`). | Optional |
| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional | | `exempt-all-pr-assignees` | Exempt all PRs with assignees from being marked as stale. (override `exempt-all-assignees`). | Optional |
| `exempt-assignees` | Assignees on an issue or a PR exempted from being marked as stale. | 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 |
| `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 |
### Usage ### Usage
@ -275,10 +270,25 @@ jobs:
### Debugging ### Debugging
**Logs:**
To see the debug output from this action, you must set the secret `ACTIONS_STEP_DEBUG` to `true` in your repository. To see the debug output from this action, you must set the secret `ACTIONS_STEP_DEBUG` to `true` in your repository.
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. 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. **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 ### Contributing

View File

@ -1,6 +1,6 @@
import {Issue} from '../src/classes/issue'; import {Issue} from '../src/classes/issue';
import {IssuesProcessor} from '../src/classes/issues-processor';
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
import {IssuesProcessorMock} from './classes/issues-processor-mock';
import {DefaultProcessorOptions} from './constants/default-processor-options'; import {DefaultProcessorOptions} from './constants/default-processor-options';
import {generateIssue} from './functions/generate-issue'; import {generateIssue} from './functions/generate-issue';
@ -95,8 +95,8 @@ class IssuesProcessorBuilder {
return this; return this;
} }
build(): IssuesProcessor { build(): IssuesProcessorMock {
return new IssuesProcessor( return new IssuesProcessorMock(
this._options, this._options,
async () => 'abot', async () => 'abot',
async p => (p === 1 ? this._issues : []), async p => (p === 1 ? this._issues : []),

View File

@ -1,6 +1,6 @@
import {Issue} from '../src/classes/issue'; import {Issue} from '../src/classes/issue';
import {IssuesProcessor} from '../src/classes/issues-processor';
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
import {IssuesProcessorMock} from './classes/issues-processor-mock';
import {DefaultProcessorOptions} from './constants/default-processor-options'; import {DefaultProcessorOptions} from './constants/default-processor-options';
import {generateIssue} from './functions/generate-issue'; import {generateIssue} from './functions/generate-issue';
@ -21,7 +21,7 @@ interface ITestData {
describe('assignees options', (): void => { describe('assignees options', (): void => {
let opts: IIssuesProcessorOptions; let opts: IIssuesProcessorOptions;
let testIssueList: Issue[]; let testIssueList: Issue[];
let processor: IssuesProcessor; let processor: IssuesProcessorMock;
const setTestIssueList = ( const setTestIssueList = (
isPullRequest: boolean, isPullRequest: boolean,
@ -46,7 +46,7 @@ describe('assignees options', (): void => {
}; };
const setProcessor = () => { const setProcessor = () => {
processor = new IssuesProcessor( processor = new IssuesProcessorMock(
opts, opts,
async () => 'abot', async () => 'abot',
async p => (p === 1 ? testIssueList : []), async p => (p === 1 ? testIssueList : []),

View File

@ -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<string>,
getIssues?: (page: number) => Promise<Issue[]>,
listIssueComments?: (
issueNumber: number,
sinceDate: string
) => Promise<IComment[]>,
getLabelCreationDate?: (
issue: Issue,
label: string
) => Promise<string | undefined>
) {
super(options);
if (getActor) {
this.getActor = getActor;
}
if (getIssues) {
this.getIssues = getIssues;
}
if (listIssueComments) {
this.listIssueComments = listIssueComments;
}
if (getLabelCreationDate) {
this.getLabelCreationDate = getLabelCreationDate;
}
}
}

View File

@ -41,5 +41,6 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
exemptPrAssignees: '', exemptPrAssignees: '',
exemptAllAssignees: false, exemptAllAssignees: false,
exemptAllIssueAssignees: undefined, exemptAllIssueAssignees: undefined,
exemptAllPrAssignees: undefined exemptAllPrAssignees: undefined,
enableStatistics: false
}); });

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
import {Issue} from '../src/classes/issue'; import {Issue} from '../src/classes/issue';
import {IssuesProcessor} from '../src/classes/issues-processor';
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
import {IssuesProcessorMock} from './classes/issues-processor-mock';
import {DefaultProcessorOptions} from './constants/default-processor-options'; import {DefaultProcessorOptions} from './constants/default-processor-options';
import {generateIssue} from './functions/generate-issue'; import {generateIssue} from './functions/generate-issue';
@ -14,7 +14,7 @@ interface ITestData {
describe('milestones options', (): void => { describe('milestones options', (): void => {
let opts: IIssuesProcessorOptions; let opts: IIssuesProcessorOptions;
let testIssueList: Issue[]; let testIssueList: Issue[];
let processor: IssuesProcessor; let processor: IssuesProcessorMock;
const setTestIssueList = ( const setTestIssueList = (
isPullRequest: boolean, isPullRequest: boolean,
@ -37,7 +37,7 @@ describe('milestones options', (): void => {
}; };
const setProcessor = () => { const setProcessor = () => {
processor = new IssuesProcessor( processor = new IssuesProcessorMock(
opts, opts,
async () => 'abot', async () => 'abot',
async p => (p === 1 ? testIssueList : []), async p => (p === 1 ? testIssueList : []),

View File

@ -1,12 +1,12 @@
import {Issue} from '../src/classes/issue'; import {Issue} from '../src/classes/issue';
import {IssuesProcessor} from '../src/classes/issues-processor';
import {IIssue} from '../src/interfaces/issue'; import {IIssue} from '../src/interfaces/issue';
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
import {IssuesProcessorMock} from './classes/issues-processor-mock';
import {DefaultProcessorOptions} from './constants/default-processor-options'; import {DefaultProcessorOptions} from './constants/default-processor-options';
import {generateIssue} from './functions/generate-issue'; import {generateIssue} from './functions/generate-issue';
let issuesProcessorBuilder: IssuesProcessorBuilder; let issuesProcessorBuilder: IssuesProcessorBuilder;
let issuesProcessor: IssuesProcessor; let issuesProcessor: IssuesProcessorMock;
describe('only-labels option', (): void => { describe('only-labels option', (): void => {
beforeEach((): void => { beforeEach((): void => {
@ -1140,8 +1140,8 @@ class IssuesProcessorBuilder {
return this; return this;
} }
build(): IssuesProcessor { build(): IssuesProcessorMock {
return new IssuesProcessor( return new IssuesProcessorMock(
this._options, this._options,
async () => 'abot', async () => 'abot',
async p => (p === 1 ? this._issues : []), async p => (p === 1 ? this._issues : []),

View File

@ -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.' description: 'Exempt all pull requests with assignees from being marked as stale. Override "exempt-all-assignees" option regarding only the pull requests.'
default: '' default: ''
required: false 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: runs:
using: 'node12' using: 'node12'
main: 'dist/index.js' main: 'dist/index.js'

425
dist/index.js vendored
View File

@ -206,7 +206,6 @@ const github_1 = __nccwpck_require__(5438);
const get_humanized_date_1 = __nccwpck_require__(965); const get_humanized_date_1 = __nccwpck_require__(965);
const is_date_more_recent_than_1 = __nccwpck_require__(1473); const is_date_more_recent_than_1 = __nccwpck_require__(1473);
const is_valid_date_1 = __nccwpck_require__(891); 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_labeled_1 = __nccwpck_require__(6792);
const is_pull_request_1 = __nccwpck_require__(5400); const is_pull_request_1 = __nccwpck_require__(5400);
const should_mark_when_stale_1 = __nccwpck_require__(2461); 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 issue_logger_1 = __nccwpck_require__(2984);
const logger_1 = __nccwpck_require__(6212); const logger_1 = __nccwpck_require__(6212);
const milestones_1 = __nccwpck_require__(4601); const milestones_1 = __nccwpck_require__(4601);
const statistics_1 = __nccwpck_require__(3334);
/*** /***
* Handle processing of issues for staleness/closure. * Handle processing of issues for staleness/closure.
*/ */
class IssuesProcessor { class IssuesProcessor {
constructor(options, getActor, getIssues, listIssueComments, getLabelCreationDate) { constructor(options) {
this._logger = new logger_1.Logger(); this._logger = new logger_1.Logger();
this._operationsLeft = 0; this._operationsLeft = 0;
this.staleIssues = []; this.staleIssues = [];
@ -228,23 +228,14 @@ class IssuesProcessor {
this.deletedBranchIssues = []; this.deletedBranchIssues = [];
this.removedLabelIssues = []; this.removedLabelIssues = [];
this.options = options; this.options = options;
this._operationsLeft = options.operationsPerRun; this._operationsLeft = this.options.operationsPerRun;
this.client = github_1.getOctokit(options.repoToken); this.client = github_1.getOctokit(this.options.repoToken);
if (getActor) {
this._getActor = getActor;
}
if (getIssues) {
this._getIssues = getIssues;
}
if (listIssueComments) {
this._listIssueComments = listIssueComments;
}
if (getLabelCreationDate) {
this._getLabelCreationDate = getLabelCreationDate;
}
if (this.options.debugOnly) { if (this.options.debugOnly) {
this._logger.warning('Executing in debug mode. Debug output will be written but no issues will be processed.'); 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) { static _updatedSince(timestamp, num_days) {
const daysInMillis = 1000 * 60 * 60 * 24 * num_days; const daysInMillis = 1000 * 60 * 60 * 24 * num_days;
@ -252,18 +243,20 @@ class IssuesProcessor {
return millisSinceLastUpdated <= daysInMillis; return millisSinceLastUpdated <= daysInMillis;
} }
processIssues(page = 1) { processIssues(page = 1) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
// get the next batch of issues // get the next batch of issues
const issues = yield this._getIssues(page); const issues = yield this.getIssues(page);
this._operationsLeft -= 1; const actor = yield this.getActor();
const actor = yield this._getActor();
if (issues.length <= 0) { if (issues.length <= 0) {
this._logger.info('---'); 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.'); this._logger.info('No more issues found to process. Exiting.');
return this._operationsLeft; return this._operationsLeft;
} }
for (const issue of issues.values()) { for (const issue of issues.values()) {
const issueLogger = new issue_logger_1.IssueLogger(issue); 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}`); issueLogger.info(`Found this $$type last updated ${issue.updated_at}`);
// calculate string based messages for this issue // calculate string based messages for this issue
const staleMessage = issue.isPullRequest const staleMessage = issue.isPullRequest
@ -281,7 +274,6 @@ class IssuesProcessor {
const skipMessage = issue.isPullRequest const skipMessage = issue.isPullRequest
? this.options.skipStalePrMessage ? this.options.skipStalePrMessage
: this.options.skipStaleIssueMessage; : this.options.skipStaleIssueMessage;
const issueType = get_issue_type_1.getIssueType(issue.isPullRequest);
const daysBeforeStale = issue.isPullRequest const daysBeforeStale = issue.isPullRequest
? this._getDaysBeforePrStale() ? this._getDaysBeforePrStale()
: this._getDaysBeforeIssueStale(); : this._getDaysBeforeIssueStale();
@ -353,7 +345,7 @@ class IssuesProcessor {
const anyOfLabels = words_to_list_1.wordsToList(this.options.anyOfLabels); const anyOfLabels = words_to_list_1.wordsToList(this.options.anyOfLabels);
if (anyOfLabels.length && if (anyOfLabels.length &&
!anyOfLabels.some((label) => is_labeled_1.isLabeled(issue, label))) { !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 continue; // don't process issues without any of the required labels
} }
const milestones = new milestones_1.Milestones(this.options, issue); const milestones = new milestones_1.Milestones(this.options, issue);
@ -379,7 +371,7 @@ class IssuesProcessor {
// process the issue if it was marked stale // process the issue if it was marked stale
if (issue.isStale) { if (issue.isStale) {
issueLogger.info(`Found a stale $$type`); 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) { if (this._operationsLeft <= 0) {
@ -390,11 +382,97 @@ class IssuesProcessor {
return this.processIssues(page + 1); return this.processIssues(page + 1);
}); });
} }
// handle all of the stale issue logic when we find a stale issue // grab comments for an issue since a given date
_processStaleIssue(issue, issueType, staleLabel, actor, closeMessage, closeLabel) { 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,
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
getActor() {
return __awaiter(this, void 0, void 0, function* () {
let actor;
try {
this._operationsLeft -= 1;
actor = yield this.client.users.getAuthenticated();
}
catch (error) {
return github_1.context.actor;
}
return actor.data.login;
});
}
// grab issues from github in batches of 100
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,
state: 'open',
per_page: 100,
direction: this.options.ascending ? 'asc' : 'desc',
page
});
return issueResult.data.map((issue) => new issue_1.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/
getLabelCreationDate(issue, label) {
var _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue); const issueLogger = new issue_logger_1.IssueLogger(issue);
const markedStaleOn = (yield this._getLabelCreationDate(issue, staleLabel)) || issue.updated_at; 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}`); issueLogger.info(`$$type marked stale on: ${markedStaleOn}`);
const issueHasComments = yield this._hasCommentsSince(issue, markedStaleOn, actor); const issueHasComments = yield this._hasCommentsSince(issue, markedStaleOn, actor);
issueLogger.info(`$$type has been commented on: ${issueHasComments}`); issueLogger.info(`$$type has been commented on: ${issueHasComments}`);
@ -436,74 +514,20 @@ class IssuesProcessor {
return true; return true;
} }
// find any comments since the date // find any comments since the date
const comments = yield this._listIssueComments(issue.number, sinceDate); const comments = yield this.listIssueComments(issue.number, sinceDate);
const filteredComments = comments.filter(comment => comment.user.type === 'User' && comment.user.login !== actor); 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}`); issueLogger.info(`Comments not made by actor or another bot: ${filteredComments.length}`);
// if there are any user comments returned // if there are any user comments returned
return filteredComments.length > 0; return filteredComments.length > 0;
}); });
} }
// grab comments for an issue since a given date
_listIssueComments(issueNumber, sinceDate) {
return __awaiter(this, void 0, void 0, function* () {
// find any comments since date on the given issue
try {
const comments = yield this.client.issues.listComments({
owner: github_1.context.repo.owner,
repo: github_1.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
_getActor() {
return __awaiter(this, void 0, void 0, function* () {
let actor;
try {
actor = yield this.client.users.getAuthenticated();
}
catch (error) {
return github_1.context.actor;
}
return actor.data.login;
});
}
// grab issues from github in batches of 100
_getIssues(page) {
return __awaiter(this, void 0, void 0, function* () {
// generate type for response
const endpoint = this.client.issues.listForRepo;
try {
const issueResult = yield this.client.issues.listForRepo({
owner: github_1.context.repo.owner,
repo: github_1.context.repo.repo,
state: 'open',
per_page: 100,
direction: this.options.ascending ? 'asc' : 'desc',
page
});
return issueResult.data.map((issue) => new issue_1.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 // Mark an issue as stale with a comment and a label
_markStale(issue, staleMessage, staleLabel, skipMessage) { _markStale(issue, staleMessage, staleLabel, skipMessage) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue); const issueLogger = new issue_logger_1.IssueLogger(issue);
issueLogger.info(`Marking $$type as stale`); issueLogger.info(`Marking $$type as stale`);
this.staleIssues.push(issue); this.staleIssues.push(issue);
this._operationsLeft -= 2;
// if the issue is being marked stale, the updated date should be changed to right now // if the issue is being marked stale, the updated date should be changed to right now
// so that close calculations work correctly // so that close calculations work correctly
const newUpdatedAtDate = new Date(); const newUpdatedAtDate = new Date();
@ -513,6 +537,8 @@ class IssuesProcessor {
} }
if (!skipMessage) { if (!skipMessage) {
try { try {
this._operationsLeft -= 1;
(_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementAddedComment();
yield this.client.issues.createComment({ yield this.client.issues.createComment({
owner: github_1.context.repo.owner, owner: github_1.context.repo.owner,
repo: github_1.context.repo.repo, repo: github_1.context.repo.repo,
@ -525,6 +551,9 @@ class IssuesProcessor {
} }
} }
try { 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({ yield this.client.issues.addLabels({
owner: github_1.context.repo.owner, owner: github_1.context.repo.owner,
repo: github_1.context.repo.repo, repo: github_1.context.repo.repo,
@ -539,16 +568,18 @@ class IssuesProcessor {
} }
// Close an issue based on staleness // Close an issue based on staleness
_closeIssue(issue, closeMessage, closeLabel) { _closeIssue(issue, closeMessage, closeLabel) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue); const issueLogger = new issue_logger_1.IssueLogger(issue);
issueLogger.info(`Closing $$type for being stale`); issueLogger.info(`Closing $$type for being stale`);
this.closedIssues.push(issue); this.closedIssues.push(issue);
this._operationsLeft -= 1;
if (this.options.debugOnly) { if (this.options.debugOnly) {
return; return;
} }
if (closeMessage) { if (closeMessage) {
try { try {
this._operationsLeft -= 1;
(_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementAddedComment();
yield this.client.issues.createComment({ yield this.client.issues.createComment({
owner: github_1.context.repo.owner, owner: github_1.context.repo.owner,
repo: github_1.context.repo.repo, repo: github_1.context.repo.repo,
@ -562,6 +593,8 @@ class IssuesProcessor {
} }
if (closeLabel) { if (closeLabel) {
try { try {
this._operationsLeft -= 1;
(_b = this._statistics) === null || _b === void 0 ? void 0 : _b.incrementAddedLabel();
yield this.client.issues.addLabels({ yield this.client.issues.addLabels({
owner: github_1.context.repo.owner, owner: github_1.context.repo.owner,
repo: github_1.context.repo.repo, repo: github_1.context.repo.repo,
@ -574,6 +607,8 @@ class IssuesProcessor {
} }
} }
try { try {
this._operationsLeft -= 1;
(_c = this._statistics) === null || _c === void 0 ? void 0 : _c.incrementClosedIssuesCount();
yield this.client.issues.update({ yield this.client.issues.update({
owner: github_1.context.repo.owner, owner: github_1.context.repo.owner,
repo: github_1.context.repo.repo, repo: github_1.context.repo.repo,
@ -587,10 +622,15 @@ class IssuesProcessor {
}); });
} }
_getPullRequest(issue) { _getPullRequest(issue) {
var _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue); const issueLogger = new issue_logger_1.IssueLogger(issue);
this._operationsLeft -= 1; if (this.options.debugOnly) {
return;
}
try { try {
this._operationsLeft -= 1;
(_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedPullRequestsCount();
const pullRequest = yield this.client.pulls.get({ const pullRequest = yield this.client.pulls.get({
owner: github_1.context.repo.owner, owner: github_1.context.repo.owner,
repo: github_1.context.repo.repo, repo: github_1.context.repo.repo,
@ -605,21 +645,23 @@ class IssuesProcessor {
} }
// Delete the branch on closed pull request // Delete the branch on closed pull request
_deleteBranch(issue) { _deleteBranch(issue) {
var _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue); const issueLogger = new issue_logger_1.IssueLogger(issue);
issueLogger.info(`Delete branch from closed $$type - ${issue.title}`); issueLogger.info(`Delete branch from closed $$type - ${issue.title}`);
if (this.options.debugOnly) {
return;
}
const pullRequest = yield this._getPullRequest(issue); const pullRequest = yield this._getPullRequest(issue);
if (!pullRequest) { if (!pullRequest) {
issueLogger.info(`Not deleting branch as pull request not found for this $$type`); issueLogger.info(`Not deleting branch as pull request not found for this $$type`);
return; return;
} }
if (this.options.debugOnly) {
return;
}
const branch = pullRequest.head.ref; const branch = pullRequest.head.ref;
issueLogger.info(`Deleting branch ${branch} from closed $$type`); issueLogger.info(`Deleting branch ${branch} from closed $$type`);
this._operationsLeft -= 1;
try { try {
this._operationsLeft -= 1;
(_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementDeletedBranchesCount();
yield this.client.git.deleteRef({ yield this.client.git.deleteRef({
owner: github_1.context.repo.owner, owner: github_1.context.repo.owner,
repo: github_1.context.repo.repo, repo: github_1.context.repo.repo,
@ -633,16 +675,18 @@ class IssuesProcessor {
} }
// Remove a label from an issue // Remove a label from an issue
_removeLabel(issue, label) { _removeLabel(issue, label) {
var _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue); const issueLogger = new issue_logger_1.IssueLogger(issue);
issueLogger.info(`Removing label "${label}" from $$type`); issueLogger.info(`Removing label "${label}" from $$type`);
this.removedLabelIssues.push(issue); this.removedLabelIssues.push(issue);
this._operationsLeft -= 1;
// @todo remove the debug only to be able to test the code below // @todo remove the debug only to be able to test the code below
if (this.options.debugOnly) { if (this.options.debugOnly) {
return; return;
} }
try { try {
this._operationsLeft -= 1;
(_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementDeletedLabelsCount();
yield this.client.issues.removeLabel({ yield this.client.issues.removeLabel({
owner: github_1.context.repo.owner, owner: github_1.context.repo.owner,
repo: github_1.context.repo.repo, 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() { _getDaysBeforeIssueStale() {
return isNaN(this.options.daysBeforeIssueStale) return isNaN(this.options.daysBeforeIssueStale)
? this.options.daysBeforeStale ? this.options.daysBeforeStale
@ -712,13 +733,16 @@ class IssuesProcessor {
return this.options.onlyLabels; return this.options.onlyLabels;
} }
_removeStaleLabel(issue, staleLabel) { _removeStaleLabel(issue, staleLabel) {
var _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue); const issueLogger = new issue_logger_1.IssueLogger(issue);
issueLogger.info(`The $$type is no longer stale. Removing the stale label...`); 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) { _removeCloseLabel(issue, closeLabel) {
var _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue); const issueLogger = new issue_logger_1.IssueLogger(issue);
issueLogger.info(`The $$type is not closed nor locked. Trying to remove the close label...`); 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)) { if (is_labeled_1.isLabeled(issue, closeLabel)) {
issueLogger.info(`The $$type has a close label "${closeLabel}". Removing the close label...`); 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: /***/ 3334:
/***/ ((__unused_webpack_module, exports) => { /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.IssueType = void 0; exports.Statistics = void 0;
var IssueType; const logger_1 = __nccwpck_require__(6212);
(function (IssueType) { class Statistics {
IssueType["Issue"] = "issue"; constructor(options) {
IssueType["PullRequest"] = "pr"; this._logger = new logger_1.Logger();
})(IssueType = exports.IssueType || (exports.IssueType = {})); 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; 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: /***/ 6792:
@ -1229,7 +1377,8 @@ function _getAndValidateArgs() {
exemptPrAssignees: core.getInput('exempt-pr-assignees'), exemptPrAssignees: core.getInput('exempt-pr-assignees'),
exemptAllAssignees: core.getInput('exempt-all-assignees') === 'true', exemptAllAssignees: core.getInput('exempt-all-assignees') === 'true',
exemptAllIssueAssignees: _toOptionalBoolean('exempt-all-issue-assignees'), 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 [ for (const numberInput of [
'days-before-stale', 'days-before-stale',

View File

@ -52,7 +52,8 @@ describe('Issue', (): void => {
exemptPrAssignees: '', exemptPrAssignees: '',
exemptAllAssignees: false, exemptAllAssignees: false,
exemptAllIssueAssignees: undefined, exemptAllIssueAssignees: undefined,
exemptAllPrAssignees: undefined exemptAllPrAssignees: undefined,
enableStatistics: false
}; };
issueInterface = { issueInterface = {
title: 'dummy-title', title: 'dummy-title',

View File

@ -1,11 +1,9 @@
import {context, getOctokit} from '@actions/github'; import {context, getOctokit} from '@actions/github';
import {GitHub} from '@actions/github/lib/utils'; import {GitHub} from '@actions/github/lib/utils';
import {GetResponseTypeFromEndpointMethod} from '@octokit/types'; import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
import {IssueType} from '../enums/issue-type';
import {getHumanizedDate} from '../functions/dates/get-humanized-date'; import {getHumanizedDate} from '../functions/dates/get-humanized-date';
import {isDateMoreRecentThan} from '../functions/dates/is-date-more-recent-than'; import {isDateMoreRecentThan} from '../functions/dates/is-date-more-recent-than';
import {isValidDate} from '../functions/dates/is-valid-date'; import {isValidDate} from '../functions/dates/is-valid-date';
import {getIssueType} from '../functions/get-issue-type';
import {isLabeled} from '../functions/is-labeled'; import {isLabeled} from '../functions/is-labeled';
import {isPullRequest} from '../functions/is-pull-request'; import {isPullRequest} from '../functions/is-pull-request';
import {shouldMarkWhenStale} from '../functions/should-mark-when-stale'; import {shouldMarkWhenStale} from '../functions/should-mark-when-stale';
@ -20,6 +18,7 @@ import {Issue} from './issue';
import {IssueLogger} from './loggers/issue-logger'; import {IssueLogger} from './loggers/issue-logger';
import {Logger} from './loggers/logger'; import {Logger} from './loggers/logger';
import {Milestones} from './milestones'; import {Milestones} from './milestones';
import {Statistics} from './statistics';
/*** /***
* Handle processing of issues for staleness/closure. * Handle processing of issues for staleness/closure.
@ -34,6 +33,7 @@ export class IssuesProcessor {
} }
private readonly _logger: Logger = new Logger(); private readonly _logger: Logger = new Logger();
private readonly _statistics: Statistics | undefined;
private _operationsLeft = 0; private _operationsLeft = 0;
readonly client: InstanceType<typeof GitHub>; readonly client: InstanceType<typeof GitHub>;
readonly options: IIssuesProcessorOptions; readonly options: IIssuesProcessorOptions;
@ -42,61 +42,38 @@ export class IssuesProcessor {
readonly deletedBranchIssues: Issue[] = []; readonly deletedBranchIssues: Issue[] = [];
readonly removedLabelIssues: Issue[] = []; readonly removedLabelIssues: Issue[] = [];
constructor( constructor(options: IIssuesProcessorOptions) {
options: IIssuesProcessorOptions,
getActor?: () => Promise<string>,
getIssues?: (page: number) => Promise<Issue[]>,
listIssueComments?: (
issueNumber: number,
sinceDate: string
) => Promise<IComment[]>,
getLabelCreationDate?: (
issue: Issue,
label: string
) => Promise<string | undefined>
) {
this.options = options; this.options = options;
this._operationsLeft = options.operationsPerRun; this._operationsLeft = this.options.operationsPerRun;
this.client = getOctokit(options.repoToken); this.client = getOctokit(this.options.repoToken);
if (getActor) {
this._getActor = getActor;
}
if (getIssues) {
this._getIssues = getIssues;
}
if (listIssueComments) {
this._listIssueComments = listIssueComments;
}
if (getLabelCreationDate) {
this._getLabelCreationDate = getLabelCreationDate;
}
if (this.options.debugOnly) { if (this.options.debugOnly) {
this._logger.warning( this._logger.warning(
'Executing in debug mode. Debug output will be written but no issues will be processed.' '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<number> { async processIssues(page = 1): Promise<number> {
// get the next batch of issues // get the next batch of issues
const issues: Issue[] = await this._getIssues(page); const issues: Issue[] = await this.getIssues(page);
this._operationsLeft -= 1; const actor: string = await this.getActor();
const actor: string = await this._getActor();
if (issues.length <= 0) { if (issues.length <= 0) {
this._logger.info('---'); this._logger.info('---');
this._statistics?.setOperationsLeft(this._operationsLeft).logStats();
this._logger.info('No more issues found to process. Exiting.'); this._logger.info('No more issues found to process. Exiting.');
return this._operationsLeft; return this._operationsLeft;
} }
for (const issue of issues.values()) { for (const issue of issues.values()) {
const issueLogger: IssueLogger = new IssueLogger(issue); const issueLogger: IssueLogger = new IssueLogger(issue);
this._statistics?.incrementProcessedIssuesCount();
issueLogger.info(`Found this $$type last updated ${issue.updated_at}`); issueLogger.info(`Found this $$type last updated ${issue.updated_at}`);
@ -116,7 +93,6 @@ export class IssuesProcessor {
const skipMessage = issue.isPullRequest const skipMessage = issue.isPullRequest
? this.options.skipStalePrMessage ? this.options.skipStalePrMessage
: this.options.skipStaleIssueMessage; : this.options.skipStaleIssueMessage;
const issueType: IssueType = getIssueType(issue.isPullRequest);
const daysBeforeStale: number = issue.isPullRequest const daysBeforeStale: number = issue.isPullRequest
? this._getDaysBeforePrStale() ? this._getDaysBeforePrStale()
: this._getDaysBeforeIssueStale(); : this._getDaysBeforeIssueStale();
@ -238,7 +214,7 @@ export class IssuesProcessor {
) )
) { ) {
issueLogger.info( 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 continue; // don't process issues without any of the required labels
} }
@ -282,7 +258,6 @@ export class IssuesProcessor {
issueLogger.info(`Found a stale $$type`); issueLogger.info(`Found a stale $$type`);
await this._processStaleIssue( await this._processStaleIssue(
issue, issue,
issueType,
staleLabel, staleLabel,
actor, actor,
closeMessage, closeMessage,
@ -302,10 +277,108 @@ export class IssuesProcessor {
return this.processIssues(page + 1); return this.processIssues(page + 1);
} }
// grab comments for an issue since a given date
async listIssueComments(
issueNumber: number,
sinceDate: string
): Promise<IComment[]> {
// 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<string> {
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<Issue[]> {
// generate type for response
const endpoint = this.client.issues.listForRepo;
type OctoKitIssueList = GetResponseTypeFromEndpointMethod<typeof endpoint>;
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<IIssue>): 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<string | undefined> {
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 // handle all of the stale issue logic when we find a stale issue
private async _processStaleIssue( private async _processStaleIssue(
issue: Issue, issue: Issue,
issueType: IssueType,
staleLabel: string, staleLabel: string,
actor: string, actor: string,
closeMessage?: string, closeMessage?: string,
@ -313,7 +386,7 @@ export class IssuesProcessor {
) { ) {
const issueLogger: IssueLogger = new IssueLogger(issue); const issueLogger: IssueLogger = new IssueLogger(issue);
const markedStaleOn: string = 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}`); issueLogger.info(`$$type marked stale on: ${markedStaleOn}`);
const issueHasComments: boolean = await this._hasCommentsSince( const issueHasComments: boolean = await this._hasCommentsSince(
@ -381,7 +454,7 @@ export class IssuesProcessor {
} }
// find any comments since the date // 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( const filteredComments = comments.filter(
comment => comment.user.type === 'User' && comment.user.login !== actor comment => comment.user.type === 'User' && comment.user.login !== actor
@ -395,65 +468,6 @@ export class IssuesProcessor {
return filteredComments.length > 0; return filteredComments.length > 0;
} }
// grab comments for an issue since a given date
private async _listIssueComments(
issueNumber: number,
sinceDate: string
): Promise<IComment[]> {
// 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<string> {
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<Issue[]> {
// generate type for response
const endpoint = this.client.issues.listForRepo;
type OctoKitIssueList = GetResponseTypeFromEndpointMethod<typeof endpoint>;
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<IIssue>): 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 // Mark an issue as stale with a comment and a label
private async _markStale( private async _markStale(
issue: Issue, issue: Issue,
@ -464,11 +478,8 @@ export class IssuesProcessor {
const issueLogger: IssueLogger = new IssueLogger(issue); const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Marking $$type as stale`); issueLogger.info(`Marking $$type as stale`);
this.staleIssues.push(issue); this.staleIssues.push(issue);
this._operationsLeft -= 2;
// if the issue is being marked stale, the updated date should be changed to right now // if the issue is being marked stale, the updated date should be changed to right now
// so that close calculations work correctly // so that close calculations work correctly
const newUpdatedAtDate: Date = new Date(); const newUpdatedAtDate: Date = new Date();
@ -480,6 +491,8 @@ export class IssuesProcessor {
if (!skipMessage) { if (!skipMessage) {
try { try {
this._operationsLeft -= 1;
this._statistics?.incrementAddedComment();
await this.client.issues.createComment({ await this.client.issues.createComment({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
@ -492,6 +505,9 @@ export class IssuesProcessor {
} }
try { try {
this._operationsLeft -= 1;
this._statistics?.incrementAddedLabel();
this._statistics?.incrementStaleIssuesCount();
await this.client.issues.addLabels({ await this.client.issues.addLabels({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
@ -512,17 +528,16 @@ export class IssuesProcessor {
const issueLogger: IssueLogger = new IssueLogger(issue); const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Closing $$type for being stale`); issueLogger.info(`Closing $$type for being stale`);
this.closedIssues.push(issue); this.closedIssues.push(issue);
this._operationsLeft -= 1;
if (this.options.debugOnly) { if (this.options.debugOnly) {
return; return;
} }
if (closeMessage) { if (closeMessage) {
try { try {
this._operationsLeft -= 1;
this._statistics?.incrementAddedComment();
await this.client.issues.createComment({ await this.client.issues.createComment({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
@ -536,6 +551,8 @@ export class IssuesProcessor {
if (closeLabel) { if (closeLabel) {
try { try {
this._operationsLeft -= 1;
this._statistics?.incrementAddedLabel();
await this.client.issues.addLabels({ await this.client.issues.addLabels({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
@ -548,6 +565,8 @@ export class IssuesProcessor {
} }
try { try {
this._operationsLeft -= 1;
this._statistics?.incrementClosedIssuesCount();
await this.client.issues.update({ await this.client.issues.update({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
@ -561,11 +580,16 @@ export class IssuesProcessor {
private async _getPullRequest( private async _getPullRequest(
issue: Issue issue: Issue
): Promise<IPullRequest | undefined> { ): Promise<IPullRequest | undefined | void> {
const issueLogger: IssueLogger = new IssueLogger(issue); const issueLogger: IssueLogger = new IssueLogger(issue);
this._operationsLeft -= 1;
if (this.options.debugOnly) {
return;
}
try { try {
this._operationsLeft -= 1;
this._statistics?.incrementFetchedPullRequestsCount();
const pullRequest = await this.client.pulls.get({ const pullRequest = await this.client.pulls.get({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
@ -584,10 +608,6 @@ export class IssuesProcessor {
issueLogger.info(`Delete branch from closed $$type - ${issue.title}`); issueLogger.info(`Delete branch from closed $$type - ${issue.title}`);
if (this.options.debugOnly) {
return;
}
const pullRequest = await this._getPullRequest(issue); const pullRequest = await this._getPullRequest(issue);
if (!pullRequest) { if (!pullRequest) {
@ -597,12 +617,16 @@ export class IssuesProcessor {
return; return;
} }
if (this.options.debugOnly) {
return;
}
const branch = pullRequest.head.ref; const branch = pullRequest.head.ref;
issueLogger.info(`Deleting branch ${branch} from closed $$type`); issueLogger.info(`Deleting branch ${branch} from closed $$type`);
this._operationsLeft -= 1;
try { try {
this._operationsLeft -= 1;
this._statistics?.incrementDeletedBranchesCount();
await this.client.git.deleteRef({ await this.client.git.deleteRef({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
@ -620,17 +644,16 @@ export class IssuesProcessor {
const issueLogger: IssueLogger = new IssueLogger(issue); const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Removing label "${label}" from $$type`); issueLogger.info(`Removing label "${label}" from $$type`);
this.removedLabelIssues.push(issue); this.removedLabelIssues.push(issue);
this._operationsLeft -= 1;
// @todo remove the debug only to be able to test the code below // @todo remove the debug only to be able to test the code below
if (this.options.debugOnly) { if (this.options.debugOnly) {
return; return;
} }
try { try {
this._operationsLeft -= 1;
this._statistics?.incrementDeletedLabelsCount();
await this.client.issues.removeLabel({ await this.client.issues.removeLabel({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, 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<string | undefined> {
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 { private _getDaysBeforeIssueStale(): number {
return isNaN(this.options.daysBeforeIssueStale) return isNaN(this.options.daysBeforeIssueStale)
? this.options.daysBeforeStale ? this.options.daysBeforeStale
@ -724,7 +713,8 @@ export class IssuesProcessor {
`The $$type is no longer stale. Removing the stale label...` `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( private async _removeCloseLabel(
@ -748,7 +738,8 @@ export class IssuesProcessor {
`The $$type has a close label "${closeLabel}". Removing the close label...` `The $$type has a close label "${closeLabel}". Removing the close label...`
); );
return this._removeLabel(issue, closeLabel); await this._removeLabel(issue, closeLabel);
this._statistics?.incrementDeletedCloseLabelsCount();
} }
} }
} }

200
src/classes/statistics.ts Normal file
View File

@ -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<number> = 1): Statistics {
this._processedIssuesCount += increment;
return this;
}
incrementStaleIssuesCount(increment: Readonly<number> = 1): Statistics {
this._staleIssuesCount += increment;
return this;
}
incrementUndoStaleIssuesCount(increment: Readonly<number> = 1): Statistics {
this._undoStaleIssuesCount += increment;
return this;
}
setOperationsLeft(operationsLeft: Readonly<number>): Statistics {
this._operationsCount = this._options.operationsPerRun - operationsLeft;
return this;
}
incrementClosedIssuesCount(increment: Readonly<number> = 1): Statistics {
this._closedIssuesCount += increment;
return this;
}
incrementDeletedLabelsCount(increment: Readonly<number> = 1): Statistics {
this._deletedLabelsCount += increment;
return this;
}
incrementDeletedCloseLabelsCount(
increment: Readonly<number> = 1
): Statistics {
this._deletedCloseLabelsCount += increment;
return this;
}
incrementDeletedBranchesCount(increment: Readonly<number> = 1): Statistics {
this._deletedBranchesCount += increment;
return this;
}
incrementAddedLabel(increment: Readonly<number> = 1): Statistics {
this._addedLabelsCount += increment;
return this;
}
incrementAddedComment(increment: Readonly<number> = 1): Statistics {
this._addedCommentsCount += increment;
return this;
}
incrementFetchedIssuesCount(increment: Readonly<number> = 1): Statistics {
this._fetchedIssuesCount += increment;
return this;
}
incrementFetchedIssuesEventsCount(
increment: Readonly<number> = 1
): Statistics {
this._fetchedIssuesEventsCount += increment;
return this;
}
incrementFetchedIssuesCommentsCount(
increment: Readonly<number> = 1
): Statistics {
this._fetchedIssuesCommentsCount += increment;
return this;
}
incrementFetchedPullRequestsCount(
increment: Readonly<number> = 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<string>, count: Readonly<number>): void {
if (count > 0) {
this._logger.info(`${name}: ${count}`);
}
}
}

View File

@ -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');
});
});
});

View File

@ -1,5 +0,0 @@
import {IssueType} from '../enums/issue-type';
export function getIssueType(isPullRequest: Readonly<boolean>): IssueType {
return isPullRequest ? IssueType.PullRequest : IssueType.Issue;
}

View File

@ -42,4 +42,5 @@ export interface IIssuesProcessorOptions {
exemptAllAssignees: boolean; exemptAllAssignees: boolean;
exemptAllIssueAssignees: boolean | undefined; exemptAllIssueAssignees: boolean | undefined;
exemptAllPrAssignees: boolean | undefined; exemptAllPrAssignees: boolean | undefined;
enableStatistics: boolean;
} }

View File

@ -68,7 +68,8 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
exemptPrAssignees: core.getInput('exempt-pr-assignees'), exemptPrAssignees: core.getInput('exempt-pr-assignees'),
exemptAllAssignees: core.getInput('exempt-all-assignees') === 'true', exemptAllAssignees: core.getInput('exempt-all-assignees') === 'true',
exemptAllIssueAssignees: _toOptionalBoolean('exempt-all-issue-assignees'), 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 [ for (const numberInput of [