mirror of
https://github.com/actions/stale.git
synced 2025-12-11 04:32:53 +00:00
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:
committed by
GitHub
parent
1b9f13b607
commit
f71123a6f7
@@ -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);
|
||||
|
||||
208
src/classes/issue.spec.ts
Normal file
208
src/classes/issue.spec.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import {IIssue} from '../interfaces/issue';
|
||||
import {IMilestone} from '../interfaces/milestone';
|
||||
import {IssueProcessorOptions, Label} from '../IssueProcessor';
|
||||
import {Issue} from './issue';
|
||||
|
||||
describe('Issue', (): void => {
|
||||
let issue: Issue;
|
||||
let optionsInterface: IssueProcessorOptions;
|
||||
let issueInterface: IIssue;
|
||||
|
||||
beforeEach((): void => {
|
||||
optionsInterface = {
|
||||
ascending: false,
|
||||
closeIssueLabel: '',
|
||||
closeIssueMessage: '',
|
||||
closePrLabel: '',
|
||||
closePrMessage: '',
|
||||
daysBeforeClose: 0,
|
||||
daysBeforeIssueClose: 0,
|
||||
daysBeforeIssueStale: 0,
|
||||
daysBeforePrClose: 0,
|
||||
daysBeforePrStale: 0,
|
||||
daysBeforeStale: 0,
|
||||
debugOnly: false,
|
||||
deleteBranch: false,
|
||||
exemptIssueLabels: '',
|
||||
exemptIssueMilestones: '',
|
||||
exemptMilestones: '',
|
||||
exemptPrLabels: '',
|
||||
exemptPrMilestones: '',
|
||||
onlyLabels: '',
|
||||
operationsPerRun: 0,
|
||||
removeStaleWhenUpdated: false,
|
||||
repoToken: '',
|
||||
skipStaleIssueMessage: false,
|
||||
skipStalePrMessage: false,
|
||||
staleIssueMessage: '',
|
||||
stalePrMessage: '',
|
||||
startDate: undefined,
|
||||
stalePrLabel: 'dummy-stale-pr-label',
|
||||
staleIssueLabel: 'dummy-stale-issue-label'
|
||||
};
|
||||
issueInterface = {
|
||||
title: 'dummy-title',
|
||||
number: 8,
|
||||
created_at: 'dummy-created-at',
|
||||
updated_at: 'dummy-updated-at',
|
||||
labels: [
|
||||
{
|
||||
name: 'dummy-name'
|
||||
}
|
||||
],
|
||||
pull_request: {},
|
||||
state: 'dummy-state',
|
||||
locked: false,
|
||||
milestone: {
|
||||
title: 'dummy-milestone'
|
||||
}
|
||||
};
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
describe('constructor()', (): void => {
|
||||
it('should set the title with the given issue title', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.title).toStrictEqual('dummy-title');
|
||||
});
|
||||
|
||||
it('should set the number with the given issue number', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.number).toStrictEqual(8);
|
||||
});
|
||||
|
||||
it('should set the created_at with the given issue created_at', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.created_at).toStrictEqual('dummy-created-at');
|
||||
});
|
||||
|
||||
it('should set the updated_at with the given issue updated_at', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.updated_at).toStrictEqual('dummy-updated-at');
|
||||
});
|
||||
|
||||
it('should set the labels with the given issue labels', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.labels).toStrictEqual([
|
||||
{
|
||||
name: 'dummy-name'
|
||||
} as Label
|
||||
]);
|
||||
});
|
||||
|
||||
it('should set the pull_request with the given issue pull_request', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.pull_request).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('should set the state with the given issue state', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.state).toStrictEqual('dummy-state');
|
||||
});
|
||||
|
||||
it('should set the locked with the given issue locked', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.locked).toStrictEqual(false);
|
||||
});
|
||||
|
||||
it('should set the milestone with the given issue milestone', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.milestone).toStrictEqual({
|
||||
title: 'dummy-milestone'
|
||||
} as IMilestone);
|
||||
});
|
||||
|
||||
describe('when the given issue pull_request is not set', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.pull_request = undefined;
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
it('should set the isPullRequest to false', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.isPullRequest).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue pull_request is set', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.pull_request = {};
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
it('should set the isPullRequest to true', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.isPullRequest).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue is not a pull request', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.pull_request = undefined;
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
it('should set the staleLabel with the given option staleIssueLabel', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.staleLabel).toStrictEqual('dummy-stale-issue-label');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue is a pull request', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.pull_request = {};
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
it('should set the staleLabel with the given option stalePrLabel', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.staleLabel).toStrictEqual('dummy-stale-pr-label');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does not contains the stale label', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.pull_request = undefined;
|
||||
issueInterface.labels = [];
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
it('should set the isStale to false', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.isStale).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue contains the stale label', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.pull_request = undefined;
|
||||
issueInterface.labels = [
|
||||
{
|
||||
name: 'dummy-stale-issue-label'
|
||||
} as Label
|
||||
];
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
it('should set the isStale to true', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
expect(issue.isStale).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
48
src/classes/issue.ts
Normal file
48
src/classes/issue.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import {isLabeled} from '../functions/is-labeled';
|
||||
import {isPullRequest} from '../functions/is-pull-request';
|
||||
import {IIssue} from '../interfaces/issue';
|
||||
import {IMilestone} from '../interfaces/milestone';
|
||||
import {IssueProcessorOptions, Label} from '../IssueProcessor';
|
||||
import {IsoDateString} from '../types/iso-date-string';
|
||||
|
||||
export class Issue implements IIssue {
|
||||
private readonly _options: IssueProcessorOptions;
|
||||
readonly title: string;
|
||||
readonly number: number;
|
||||
created_at: IsoDateString;
|
||||
updated_at: IsoDateString;
|
||||
readonly labels: Label[];
|
||||
readonly pull_request: Object | null | undefined;
|
||||
readonly state: string;
|
||||
readonly locked: boolean;
|
||||
readonly milestone: IMilestone | undefined;
|
||||
readonly isPullRequest: boolean;
|
||||
readonly staleLabel: string;
|
||||
isStale: boolean;
|
||||
|
||||
constructor(
|
||||
options: Readonly<IssueProcessorOptions>,
|
||||
issue: Readonly<IIssue>
|
||||
) {
|
||||
this._options = options;
|
||||
this.title = issue.title;
|
||||
this.number = issue.number;
|
||||
this.created_at = issue.created_at;
|
||||
this.updated_at = issue.updated_at;
|
||||
this.labels = issue.labels;
|
||||
this.pull_request = issue.pull_request;
|
||||
this.state = issue.state;
|
||||
this.locked = issue.locked;
|
||||
this.milestone = issue.milestone;
|
||||
|
||||
this.isPullRequest = isPullRequest(this);
|
||||
this.staleLabel = this._getStaleLabel();
|
||||
this.isStale = isLabeled(this, this.staleLabel);
|
||||
}
|
||||
|
||||
private _getStaleLabel(): string {
|
||||
return this.isPullRequest
|
||||
? this._options.stalePrLabel
|
||||
: this._options.staleIssueLabel;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Issue} from '../IssueProcessor';
|
||||
import {Issue} from '../issue';
|
||||
import {IssueLogger} from './issue-logger';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as core from '@actions/core';
|
||||
import {Issue} from '../IssueProcessor';
|
||||
import {Issue} from '../issue';
|
||||
import {Logger} from './logger';
|
||||
|
||||
export class IssueLogger implements Logger {
|
||||
private readonly _issue: Issue;
|
||||
|
||||
constructor(issue: Readonly<Issue>) {
|
||||
constructor(issue: Issue) {
|
||||
this._issue = issue;
|
||||
}
|
||||
|
||||
596
src/classes/milestones.spec.ts
Normal file
596
src/classes/milestones.spec.ts
Normal file
@@ -0,0 +1,596 @@
|
||||
import {IIssue} from '../interfaces/issue';
|
||||
import {IssueProcessorOptions} from '../IssueProcessor';
|
||||
import {Issue} from './issue';
|
||||
import {Milestones} from './milestones';
|
||||
|
||||
describe('Milestones', (): void => {
|
||||
let milestones: Milestones;
|
||||
let optionsInterface: IssueProcessorOptions;
|
||||
let issue: Issue;
|
||||
let issueInterface: IIssue;
|
||||
|
||||
beforeEach((): void => {
|
||||
optionsInterface = {
|
||||
ascending: false,
|
||||
closeIssueLabel: '',
|
||||
closeIssueMessage: '',
|
||||
closePrLabel: '',
|
||||
closePrMessage: '',
|
||||
daysBeforeClose: 0,
|
||||
daysBeforeIssueClose: 0,
|
||||
daysBeforeIssueStale: 0,
|
||||
daysBeforePrClose: 0,
|
||||
daysBeforePrStale: 0,
|
||||
daysBeforeStale: 0,
|
||||
debugOnly: false,
|
||||
deleteBranch: false,
|
||||
exemptIssueLabels: '',
|
||||
exemptPrLabels: '',
|
||||
onlyLabels: '',
|
||||
operationsPerRun: 0,
|
||||
removeStaleWhenUpdated: false,
|
||||
repoToken: '',
|
||||
skipStaleIssueMessage: false,
|
||||
skipStalePrMessage: false,
|
||||
staleIssueLabel: '',
|
||||
staleIssueMessage: '',
|
||||
stalePrLabel: '',
|
||||
stalePrMessage: '',
|
||||
startDate: undefined,
|
||||
exemptIssueMilestones: '',
|
||||
exemptPrMilestones: '',
|
||||
exemptMilestones: ''
|
||||
};
|
||||
issueInterface = {
|
||||
created_at: '',
|
||||
locked: false,
|
||||
milestone: undefined,
|
||||
number: 0,
|
||||
pull_request: undefined,
|
||||
state: '',
|
||||
title: '',
|
||||
updated_at: '',
|
||||
labels: []
|
||||
};
|
||||
});
|
||||
|
||||
describe('shouldExemptMilestones()', (): void => {
|
||||
describe('when the given issue is not a pull request', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.pull_request = undefined;
|
||||
});
|
||||
|
||||
describe('when the given options are not configured to exempt a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptMilestones = '';
|
||||
});
|
||||
|
||||
describe('when the given options are not configured to exempt an issue milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptIssueMilestones = '';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = undefined;
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-title'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt an issue milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptIssueMilestones =
|
||||
'dummy-exempt-issue-milestone';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = undefined;
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone different than the exempt issue milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-title'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone equaling the exempt issue milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-exempt-issue-milestone'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptMilestones = 'dummy-exempt-milestone';
|
||||
});
|
||||
|
||||
describe('when the given options are not configured to exempt an issue milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptIssueMilestones = '';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = undefined;
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-title'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-exempt-milestone'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt an issue milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptIssueMilestones =
|
||||
'dummy-exempt-issue-milestone';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = undefined;
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone different than the exempt issue milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-title'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone equaling the exempt issue milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-exempt-issue-milestone'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-title'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-exempt-milestone'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue is a pull request', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.pull_request = {};
|
||||
});
|
||||
|
||||
describe('when the given options are not configured to exempt a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptMilestones = '';
|
||||
});
|
||||
|
||||
describe('when the given options are not configured to exempt a pull request milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptPrMilestones = '';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = undefined;
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-title'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt a pull request milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptPrMilestones = 'dummy-exempt-pr-milestone';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = undefined;
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone different than the exempt pull request milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-title'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone equaling the exempt pull request milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-exempt-pr-milestone'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptMilestones = 'dummy-exempt-milestone';
|
||||
});
|
||||
|
||||
describe('when the given options are not configured to exempt a pull request milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptPrMilestones = '';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = undefined;
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-title'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-exempt-milestone'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt a pull request milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptPrMilestones = 'dummy-exempt-pr-milestone';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have a milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = undefined;
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone different than the exempt pull request milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-title'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone equaling the exempt pull request milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-exempt-pr-milestone'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-title'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.milestone = {
|
||||
title: 'dummy-exempt-milestone'
|
||||
};
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
milestones = new Milestones(optionsInterface, issue);
|
||||
|
||||
const result = milestones.shouldExemptMilestones();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
59
src/classes/milestones.ts
Normal file
59
src/classes/milestones.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import deburr from 'lodash.deburr';
|
||||
import {wordsToList} from '../functions/words-to-list';
|
||||
import {IssueProcessorOptions} from '../IssueProcessor';
|
||||
import {Issue} from './issue';
|
||||
|
||||
type CleanMilestone = string;
|
||||
|
||||
export class Milestones {
|
||||
private static _cleanMilestone(label: Readonly<string>): CleanMilestone {
|
||||
return deburr(label.toLowerCase());
|
||||
}
|
||||
|
||||
private readonly _options: IssueProcessorOptions;
|
||||
private readonly _issue: Issue;
|
||||
|
||||
constructor(options: Readonly<IssueProcessorOptions>, issue: Issue) {
|
||||
this._options = options;
|
||||
this._issue = issue;
|
||||
}
|
||||
|
||||
shouldExemptMilestones(): boolean {
|
||||
const exemptMilestones: string[] = this._getExemptMilestones();
|
||||
|
||||
return exemptMilestones.some((exemptMilestone: Readonly<string>): boolean =>
|
||||
this._hasMilestone(exemptMilestone)
|
||||
);
|
||||
}
|
||||
|
||||
private _getExemptMilestones(): string[] {
|
||||
return wordsToList(
|
||||
this._issue.isPullRequest
|
||||
? this._getExemptPullRequestMilestones()
|
||||
: this._getExemptIssueMilestones()
|
||||
);
|
||||
}
|
||||
|
||||
private _getExemptIssueMilestones(): string {
|
||||
return this._options.exemptIssueMilestones !== ''
|
||||
? this._options.exemptIssueMilestones
|
||||
: this._options.exemptMilestones;
|
||||
}
|
||||
|
||||
private _getExemptPullRequestMilestones(): string {
|
||||
return this._options.exemptPrMilestones !== ''
|
||||
? this._options.exemptPrMilestones
|
||||
: this._options.exemptMilestones;
|
||||
}
|
||||
|
||||
private _hasMilestone(milestone: Readonly<string>): boolean {
|
||||
if (!this._issue.milestone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
Milestones._cleanMilestone(milestone) ===
|
||||
Milestones._cleanMilestone(this._issue.milestone.title)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Issue} from '../IssueProcessor';
|
||||
import {Issue} from '../classes/issue';
|
||||
import {isLabeled} from './is-labeled';
|
||||
|
||||
describe('isLabeled()', (): void => {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import deburr from 'lodash.deburr';
|
||||
import {Issue, Label} from '../IssueProcessor';
|
||||
import {Issue} from '../classes/issue';
|
||||
import {Label} from '../IssueProcessor';
|
||||
import {CleanLabel} from '../types/clean-label';
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Check if the label is listed as a label of the issue
|
||||
* Check if the given label is listed as a label of the given issue
|
||||
*
|
||||
* @param {Readonly<Issue>} issue A GitHub issue containing some labels
|
||||
* @param {Readonly<string>} label The label to check the presence with
|
||||
*
|
||||
* @return {boolean} Return true when the given label is also in the issue labels
|
||||
* @return {boolean} Return true when the given label is also in the given issue labels
|
||||
*/
|
||||
export function isLabeled(
|
||||
issue: Readonly<Issue>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Issue} from '../IssueProcessor';
|
||||
import {Issue} from '../classes/issue';
|
||||
import {isPullRequest} from './is-pull-request';
|
||||
|
||||
describe('isPullRequest()', (): void => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Issue} from '../IssueProcessor';
|
||||
import {Issue} from '../classes/issue';
|
||||
|
||||
export function isPullRequest(issue: Readonly<Issue>): boolean {
|
||||
return !!issue.pull_request;
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
import {labelsToList} from './labels-to-list';
|
||||
|
||||
describe('labelsToList()', (): void => {
|
||||
let labels: string;
|
||||
|
||||
describe('when the given labels is empty', (): void => {
|
||||
beforeEach((): void => {
|
||||
labels = '';
|
||||
});
|
||||
|
||||
it('should return an empty list of labels', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = labelsToList(labels);
|
||||
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given labels is a simple label', (): void => {
|
||||
beforeEach((): void => {
|
||||
labels = 'label';
|
||||
});
|
||||
|
||||
it('should return a list of one label', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = labelsToList(labels);
|
||||
|
||||
expect(result).toStrictEqual(['label']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given labels is a label with extra spaces before and after', (): void => {
|
||||
beforeEach((): void => {
|
||||
labels = ' label ';
|
||||
});
|
||||
|
||||
it('should return a list of one label and remove all spaces before and after', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = labelsToList(labels);
|
||||
|
||||
expect(result).toStrictEqual(['label']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given labels is a kebab case label', (): void => {
|
||||
beforeEach((): void => {
|
||||
labels = 'kebab-case-label';
|
||||
});
|
||||
|
||||
it('should return a list of one label', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = labelsToList(labels);
|
||||
|
||||
expect(result).toStrictEqual(['kebab-case-label']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given labels is two kebab case labels separated with a comma', (): void => {
|
||||
beforeEach((): void => {
|
||||
labels = 'kebab-case-label-1,kebab-case-label-2';
|
||||
});
|
||||
|
||||
it('should return a list of two labels', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = labelsToList(labels);
|
||||
|
||||
expect(result).toStrictEqual([
|
||||
'kebab-case-label-1',
|
||||
'kebab-case-label-2'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given labels is a multiple word label', (): void => {
|
||||
beforeEach((): void => {
|
||||
labels = 'label like a sentence';
|
||||
});
|
||||
|
||||
it('should return a list of one label', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = labelsToList(labels);
|
||||
|
||||
expect(result).toStrictEqual(['label like a sentence']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given labels is two multiple word labels separated with a comma', (): void => {
|
||||
beforeEach((): void => {
|
||||
labels = 'label like a sentence, another label like a sentence';
|
||||
});
|
||||
|
||||
it('should return a list of two labels', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = labelsToList(labels);
|
||||
|
||||
expect(result).toStrictEqual([
|
||||
'label like a sentence',
|
||||
'another label like a sentence'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given labels is a multiple word label with %20 spaces', (): void => {
|
||||
beforeEach((): void => {
|
||||
labels = 'label%20like%20a%20sentence';
|
||||
});
|
||||
|
||||
it('should return a list of one label', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = labelsToList(labels);
|
||||
|
||||
expect(result).toStrictEqual(['label%20like%20a%20sentence']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given labels is two multiple word labels with %20 spaces separated with a comma', (): void => {
|
||||
beforeEach((): void => {
|
||||
labels =
|
||||
'label%20like%20a%20sentence,another%20label%20like%20a%20sentence';
|
||||
});
|
||||
|
||||
it('should return a list of two labels', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = labelsToList(labels);
|
||||
|
||||
expect(result).toStrictEqual([
|
||||
'label%20like%20a%20sentence',
|
||||
'another%20label%20like%20a%20sentence'
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* @description
|
||||
* Transform a string of comma separated labels
|
||||
* to an array of labels
|
||||
*
|
||||
* @example
|
||||
* labelsToList('label') => ['label']
|
||||
* labelsToList('label,label') => ['label', 'label']
|
||||
* labelsToList('kebab-label') => ['kebab-label']
|
||||
* labelsToList('kebab%20label') => ['kebab%20label']
|
||||
* labelsToList('label with words') => ['label with words']
|
||||
*
|
||||
* @param {Readonly<string>} labels A string of comma separated labels
|
||||
*
|
||||
* @return {string[]} A list of labels
|
||||
*/
|
||||
export function labelsToList(labels: Readonly<string>): string[] {
|
||||
if (!labels.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return labels.split(',').map(l => l.trim());
|
||||
}
|
||||
137
src/functions/words-to-list.spec.ts
Normal file
137
src/functions/words-to-list.spec.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import {wordsToList} from './words-to-list';
|
||||
|
||||
describe('wordsToList()', (): void => {
|
||||
let words: string;
|
||||
|
||||
describe('when the given words is empty', (): void => {
|
||||
beforeEach((): void => {
|
||||
words = '';
|
||||
});
|
||||
|
||||
it('should return an empty list of words', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = wordsToList(words);
|
||||
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given words is a simple word', (): void => {
|
||||
beforeEach((): void => {
|
||||
words = 'word';
|
||||
});
|
||||
|
||||
it('should return a list of one word', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = wordsToList(words);
|
||||
|
||||
expect(result).toStrictEqual(['word']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given words is a word with extra spaces before and after', (): void => {
|
||||
beforeEach((): void => {
|
||||
words = ' word ';
|
||||
});
|
||||
|
||||
it('should return a list of one word and remove all spaces before and after', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = wordsToList(words);
|
||||
|
||||
expect(result).toStrictEqual(['word']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given words is a kebab case word', (): void => {
|
||||
beforeEach((): void => {
|
||||
words = 'kebab-case-word';
|
||||
});
|
||||
|
||||
it('should return a list of one word', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = wordsToList(words);
|
||||
|
||||
expect(result).toStrictEqual(['kebab-case-word']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given words is two kebab case words separated with a comma', (): void => {
|
||||
beforeEach((): void => {
|
||||
words = 'kebab-case-word-1,kebab-case-word-2';
|
||||
});
|
||||
|
||||
it('should return a list of two words', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = wordsToList(words);
|
||||
|
||||
expect(result).toStrictEqual(['kebab-case-word-1', 'kebab-case-word-2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given words is a multiple word word', (): void => {
|
||||
beforeEach((): void => {
|
||||
words = 'word like a sentence';
|
||||
});
|
||||
|
||||
it('should return a list of one word', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = wordsToList(words);
|
||||
|
||||
expect(result).toStrictEqual(['word like a sentence']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given words is two multiple word words separated with a comma', (): void => {
|
||||
beforeEach((): void => {
|
||||
words = 'word like a sentence, another word like a sentence';
|
||||
});
|
||||
|
||||
it('should return a list of two words', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = wordsToList(words);
|
||||
|
||||
expect(result).toStrictEqual([
|
||||
'word like a sentence',
|
||||
'another word like a sentence'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given words is a multiple word word with %20 spaces', (): void => {
|
||||
beforeEach((): void => {
|
||||
words = 'word%20like%20a%20sentence';
|
||||
});
|
||||
|
||||
it('should return a list of one word', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = wordsToList(words);
|
||||
|
||||
expect(result).toStrictEqual(['word%20like%20a%20sentence']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given words is two multiple word words with %20 spaces separated with a comma', (): void => {
|
||||
beforeEach((): void => {
|
||||
words = 'word%20like%20a%20sentence,another%20word%20like%20a%20sentence';
|
||||
});
|
||||
|
||||
it('should return a list of two words', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = wordsToList(words);
|
||||
|
||||
expect(result).toStrictEqual([
|
||||
'word%20like%20a%20sentence',
|
||||
'another%20word%20like%20a%20sentence'
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
23
src/functions/words-to-list.ts
Normal file
23
src/functions/words-to-list.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @description
|
||||
* Transform a string of comma separated words
|
||||
* to an array of words
|
||||
*
|
||||
* @example
|
||||
* wordsToList('label') => ['label']
|
||||
* wordsToList('label,label') => ['label', 'label']
|
||||
* wordsToList('kebab-label') => ['kebab-label']
|
||||
* wordsToList('kebab%20label') => ['kebab%20label']
|
||||
* wordsToList('label with words') => ['label with words']
|
||||
*
|
||||
* @param {Readonly<string>} words A string of comma separated words
|
||||
*
|
||||
* @return {string[]} A list of words
|
||||
*/
|
||||
export function wordsToList(words: Readonly<string>): string[] {
|
||||
if (!words.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return words.split(',').map((word: Readonly<string>): string => word.trim());
|
||||
}
|
||||
15
src/interfaces/issue.ts
Normal file
15
src/interfaces/issue.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {Label} from '../IssueProcessor';
|
||||
import {IsoDateString} from '../types/iso-date-string';
|
||||
import {IMilestone} from './milestone';
|
||||
|
||||
export interface IIssue {
|
||||
title: string;
|
||||
number: number;
|
||||
created_at: IsoDateString;
|
||||
updated_at: IsoDateString;
|
||||
labels: Label[];
|
||||
pull_request: Object | null | undefined;
|
||||
state: string;
|
||||
locked: boolean;
|
||||
milestone: IMilestone | undefined;
|
||||
}
|
||||
3
src/interfaces/milestone.ts
Normal file
3
src/interfaces/milestone.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface IMilestone {
|
||||
title: string;
|
||||
}
|
||||
@@ -52,16 +52,15 @@ function getAndValidateArgs(): IssueProcessorOptions {
|
||||
startDate:
|
||||
core.getInput('start-date') !== ''
|
||||
? core.getInput('start-date')
|
||||
: undefined
|
||||
: undefined,
|
||||
exemptMilestones: core.getInput('exempt-milestones'),
|
||||
exemptIssueMilestones: core.getInput('exempt-issue-milestones'),
|
||||
exemptPrMilestones: core.getInput('exempt-pr-milestones')
|
||||
};
|
||||
|
||||
for (const numberInput of [
|
||||
'days-before-stale',
|
||||
'days-before-issue-stale',
|
||||
'days-before-pr-stale',
|
||||
'days-before-close',
|
||||
'days-before-issue-close',
|
||||
'days-before-pr-close',
|
||||
'operations-per-run'
|
||||
]) {
|
||||
if (isNaN(parseInt(core.getInput(numberInput)))) {
|
||||
|
||||
Reference in New Issue
Block a user