1
0

feat(exempt): add new options to exempt the milestones (#279)

* feat(exempt): add new options to exempt the milestones

closes #270

* test(milestones): add coverage

* test(issue): add coverage

* chore(rebase): fix all errors due to the rebase

also made some changes regarding the change I made with the lint scripts and prettier. I did not saw that some scripts were already here and I created to more to keep the old ones as well

* test(milestone): add coverage

* chore(index): update index

* fix(checks): remove checks over optional number options

the code was actually handling the case where the values are NaN so it's fine
This commit is contained in:
Geoffrey Testelin
2021-01-19 11:54:16 +01:00
committed by GitHub
parent 1b9f13b607
commit f71123a6f7
25 changed files with 2184 additions and 596 deletions
+64 -58
View File
@@ -1,30 +1,21 @@
import {context, getOctokit} from '@actions/github';
import {GitHub} from '@actions/github/lib/utils';
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
import {Issue} from './classes/issue';
import {IssueLogger} from './classes/loggers/issue-logger';
import {Logger} from './classes/loggers/logger';
import {Milestones} from './classes/milestones';
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 {IssueLogger} from './classes/issue-logger';
import {Logger} from './classes/logger';
import {isLabeled} from './functions/is-labeled';
import {isPullRequest} from './functions/is-pull-request';
import {labelsToList} from './functions/labels-to-list';
import {shouldMarkWhenStale} from './functions/should-mark-when-stale';
import {IsoDateString} from './types/iso-date-string';
import {IsoOrRfcDateString} from './types/iso-or-rfc-date-string';
export interface Issue {
title: string;
number: number;
created_at: IsoDateString;
updated_at: IsoDateString;
labels: Label[];
pull_request: any;
state: string;
locked: boolean;
}
import {wordsToList} from './functions/words-to-list';
import {IIssue} from './interfaces/issue';
export interface PullRequest {
number: number;
@@ -79,10 +70,11 @@ export interface IssueProcessorOptions {
skipStalePrMessage: boolean;
deleteBranch: boolean;
startDate: IsoOrRfcDateString | undefined; // Should be ISO 8601 or RFC 2822
exemptMilestones: string;
exemptIssueMilestones: string;
exemptPrMilestones: string;
}
const logger: Logger = new Logger();
/***
* Handle processing of issues for staleness/closure.
*/
@@ -95,13 +87,14 @@ export class IssueProcessor {
return millisSinceLastUpdated <= daysInMillis;
}
private readonly _logger: Logger = new Logger();
private _operationsLeft = 0;
readonly client: InstanceType<typeof GitHub>;
readonly options: IssueProcessorOptions;
readonly staleIssues: Issue[] = [];
readonly closedIssues: Issue[] = [];
readonly deletedBranchIssues: Issue[] = [];
readonly removedLabelIssues: Issue[] = [];
private operationsLeft = 0;
constructor(
options: IssueProcessorOptions,
@@ -117,7 +110,7 @@ export class IssueProcessor {
) => Promise<string | undefined>
) {
this.options = options;
this.operationsLeft = options.operationsPerRun;
this._operationsLeft = options.operationsPerRun;
this.client = getOctokit(options.repoToken);
if (getActor) {
@@ -137,7 +130,7 @@ export class IssueProcessor {
}
if (this.options.debugOnly) {
logger.warning(
this._logger.warning(
'Executing in debug mode. Debug output will be written but no issues will be processed.'
);
}
@@ -146,49 +139,45 @@ export class IssueProcessor {
async processIssues(page = 1): Promise<number> {
// get the next batch of issues
const issues: Issue[] = await this._getIssues(page);
this.operationsLeft -= 1;
this._operationsLeft -= 1;
const actor: string = await this._getActor();
if (issues.length <= 0) {
logger.info('---');
logger.info('No more issues found to process. Exiting.');
return this.operationsLeft;
this._logger.info('---');
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);
const isPr = isPullRequest(issue);
issueLogger.info(
`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${isPr})`
`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${issue.isPullRequest})`
);
// calculate string based messages for this issue
const staleMessage: string = isPr
const staleMessage: string = issue.isPullRequest
? this.options.stalePrMessage
: this.options.staleIssueMessage;
const closeMessage: string = isPr
const closeMessage: string = issue.isPullRequest
? this.options.closePrMessage
: this.options.closeIssueMessage;
const staleLabel: string = isPr
const staleLabel: string = issue.isPullRequest
? this.options.stalePrLabel
: this.options.staleIssueLabel;
const closeLabel: string = isPr
const closeLabel: string = issue.isPullRequest
? this.options.closePrLabel
: this.options.closeIssueLabel;
const exemptLabels: string[] = labelsToList(
isPr ? this.options.exemptPrLabels : this.options.exemptIssueLabels
);
const skipMessage = isPr
const skipMessage = issue.isPullRequest
? this.options.skipStalePrMessage
: this.options.skipStaleIssueMessage;
const issueType: IssueType = getIssueType(isPr);
const daysBeforeStale: number = isPr
const issueType: IssueType = getIssueType(issue.isPullRequest);
const daysBeforeStale: number = issue.isPullRequest
? this._getDaysBeforePrStale()
: this._getDaysBeforeIssueStale();
if (isPr) {
if (issue.isPullRequest) {
issueLogger.info(`Days before pull request stale: ${daysBeforeStale}`);
} else {
issueLogger.info(`Days before issue stale: ${daysBeforeStale}`);
@@ -244,21 +233,24 @@ export class IssueProcessor {
}
}
// Does this issue have a stale label?
let isStale: boolean = isLabeled(issue, staleLabel);
if (isStale) {
if (issue.isStale) {
issueLogger.info(`This issue has a stale label`);
} else {
issueLogger.info(`This issue hasn't a stale label`);
}
const exemptLabels: string[] = wordsToList(
issue.isPullRequest
? this.options.exemptPrLabels
: this.options.exemptIssueLabels
);
if (
exemptLabels.some((exemptLabel: Readonly<string>): boolean =>
isLabeled(issue, exemptLabel)
)
) {
if (isStale) {
if (issue.isStale) {
issueLogger.info(`An exempt label was added after the stale label.`);
await this._removeStaleLabel(issue, staleLabel);
}
@@ -269,6 +261,15 @@ export class IssueProcessor {
continue; // don't process exempt issues
}
const milestones: Milestones = new Milestones(this.options, issue);
if (milestones.shouldExemptMilestones()) {
issueLogger.info(
`Skipping ${issueType} because it has an exempt milestone`
);
continue; // don't process exempt milestones
}
// should this issue be marked stale?
const shouldBeStale = !IssueProcessor._updatedSince(
issue.updated_at,
@@ -276,16 +277,16 @@ export class IssueProcessor {
);
// determine if this issue needs to be marked stale first
if (!isStale && shouldBeStale && shouldMarkAsStale) {
if (!issue.isStale && shouldBeStale && shouldMarkAsStale) {
issueLogger.info(
`Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label`
);
await this._markStale(issue, staleMessage, staleLabel, skipMessage);
isStale = true; // this issue is now considered stale
issue.isStale = true; // this issue is now considered stale
}
// process the issue if it was marked stale
if (isStale) {
if (issue.isStale) {
issueLogger.info(`Found a stale ${issueType}`);
await this._processStaleIssue(
issue,
@@ -298,8 +299,10 @@ export class IssueProcessor {
}
}
if (this.operationsLeft <= 0) {
logger.warning('Reached max number of operations to process. Exiting.');
if (this._operationsLeft <= 0) {
this._logger.warning(
'Reached max number of operations to process. Exiting.'
);
return 0;
}
@@ -427,7 +430,7 @@ export class IssueProcessor {
});
return comments.data;
} catch (error) {
logger.error(`List issue comments error: ${error.message}`);
this._logger.error(`List issue comments error: ${error.message}`);
return Promise.resolve([]);
}
}
@@ -444,7 +447,7 @@ export class IssueProcessor {
return actor.data.login;
}
// grab issues from github in baches of 100
// 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;
@@ -462,9 +465,12 @@ export class IssueProcessor {
page
}
);
return issueResult.data;
return issueResult.data.map(
(issue: Readonly<IIssue>): Issue => new Issue(this.options, issue)
);
} catch (error) {
logger.error(`Get issues for repo error: ${error.message}`);
this._logger.error(`Get issues for repo error: ${error.message}`);
return Promise.resolve([]);
}
}
@@ -482,7 +488,7 @@ export class IssueProcessor {
this.staleIssues.push(issue);
this.operationsLeft -= 2;
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
@@ -530,7 +536,7 @@ export class IssueProcessor {
this.closedIssues.push(issue);
this.operationsLeft -= 1;
this._operationsLeft -= 1;
if (this.options.debugOnly) {
return;
@@ -578,7 +584,7 @@ export class IssueProcessor {
issue: Issue
): Promise<PullRequest | undefined> {
const issueLogger: IssueLogger = new IssueLogger(issue);
this.operationsLeft -= 1;
this._operationsLeft -= 1;
try {
const pullRequest = await this.client.pulls.get({
@@ -621,7 +627,7 @@ export class IssueProcessor {
`Deleting branch ${branch} from closed issue #${issue.number}`
);
this.operationsLeft -= 1;
this._operationsLeft -= 1;
try {
await this.client.git.deleteRef({
@@ -644,7 +650,7 @@ export class IssueProcessor {
this.removedLabelIssues.push(issue);
this.operationsLeft -= 1;
this._operationsLeft -= 1;
// @todo remove the debug only to be able to test the code below
if (this.options.debugOnly) {
@@ -673,7 +679,7 @@ export class IssueProcessor {
issueLogger.info(`Checking for label on issue #${issue.number}`);
this.operationsLeft -= 1;
this._operationsLeft -= 1;
const options = this.client.issues.listEvents.endpoint.merge({
owner: context.repo.owner,
@@ -722,7 +728,7 @@ export class IssueProcessor {
}
private async _removeStaleLabel(
issue: Readonly<Issue>,
issue: Issue,
staleLabel: Readonly<string>
): Promise<void> {
const issueLogger: IssueLogger = new IssueLogger(issue);