Skip to content

Commit

Permalink
feat(stale-and-close): add new options to change the days before close
Browse files Browse the repository at this point in the history
to avoid a breaking change and simplify the configuration the old options 'daysBeforeStale' and 'daysBeforePrClose' are kept and new options are available to override them with 'daysBeforeIssueStale', 'daysBeforePrStale', 'daysBeforeIssueClose' and 'daysBeforePrClose'
  • Loading branch information
C0ZEN committed Nov 22, 2020
1 parent 6f872ce commit c6e82d1
Show file tree
Hide file tree
Showing 9 changed files with 507 additions and 46 deletions.
310 changes: 279 additions & 31 deletions __tests__/main.test.ts

Large diffs are not rendered by default.

84 changes: 69 additions & 15 deletions src/IssueProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import * as core from '@actions/core';
import {context, getOctokit} from '@actions/github';
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
import {IssueTypeEnum} from './enums/issue-type.enum';
import {getIssueType} from './functions/get-issue-type';
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';

export interface Issue {
title: string;
Expand Down Expand Up @@ -40,7 +44,11 @@ export interface IssueProcessorOptions {
closeIssueMessage: string;
closePrMessage: string;
daysBeforeStale: number;
daysBeforeIssueStale: number; // Could be NaN
daysBeforePrStale: number; // Could be NaN
daysBeforeClose: number;
daysBeforeIssueClose: number; // Could be NaN
daysBeforePrClose: number; // Could be NaN
staleIssueLabel: string;
closeIssueLabel: string;
exemptIssueLabels: string;
Expand All @@ -60,13 +68,20 @@ export interface IssueProcessorOptions {
* Handle processing of issues for staleness/closure.
*/
export class IssueProcessor {
private static updatedSince(timestamp: string, num_days: number): boolean {
const daysInMillis = 1000 * 60 * 60 * 24 * num_days;
const millisSinceLastUpdated =
new Date().getTime() - new Date(timestamp).getTime();

return millisSinceLastUpdated <= daysInMillis;
}

readonly client: any; // need to make this the correct type
readonly options: IssueProcessorOptions;
private operationsLeft = 0;

readonly staleIssues: Issue[] = [];
readonly closedIssues: Issue[] = [];
readonly removedLabelIssues: Issue[] = [];
private operationsLeft = 0;

constructor(
options: IssueProcessorOptions,
Expand Down Expand Up @@ -114,7 +129,7 @@ export class IssueProcessor {
}

for (const issue of issues.values()) {
const isPr = !!issue.pull_request;
const isPr = isPullRequest(issue);

core.info(
`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${isPr})`
Expand All @@ -139,10 +154,22 @@ export class IssueProcessor {
const skipMessage = isPr
? this.options.skipStalePrMessage
: this.options.skipStaleIssueMessage;
const issueType: string = isPr ? 'pr' : 'issue';
const shouldMarkWhenStale = this.options.daysBeforeStale > -1;
const issueType: IssueTypeEnum = getIssueType(isPr);
const DAYS_BEFORE_STALE: number = isPr
? this._getDaysBeforePrStale()
: this._getDaysBeforeIssueStale();

if (isPr) {
core.info(`Days before pull request stale: ${DAYS_BEFORE_STALE}`);
} else {
core.info(`Days before issue stale: ${DAYS_BEFORE_STALE}`);
}

const SHOULD_MARK_WHEN_STALE: boolean = shouldMarkWhenStale(
DAYS_BEFORE_STALE
);

if (!staleMessage && shouldMarkWhenStale) {
if (!staleMessage && SHOULD_MARK_WHEN_STALE) {
core.info(`Skipping ${issueType} due to empty stale message`);
continue;
}
Expand Down Expand Up @@ -176,7 +203,7 @@ export class IssueProcessor {
);

// determine if this issue needs to be marked stale first
if (!isStale && shouldBeStale && shouldMarkWhenStale) {
if (!isStale && shouldBeStale && SHOULD_MARK_WHEN_STALE) {
core.info(
`Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label`
);
Expand Down Expand Up @@ -209,7 +236,7 @@ export class IssueProcessor {
// handle all of the stale issue logic when we find a stale issue
private async processStaleIssue(
issue: Issue,
issueType: string,
issueType: IssueTypeEnum,
staleLabel: string,
closeMessage?: string,
closeLabel?: string
Expand All @@ -226,9 +253,20 @@ export class IssueProcessor {
`Issue #${issue.number} has been commented on: ${issueHasComments}`
);

const IS_PR: boolean = isPullRequest(issue);
const daysBeforeClose: number = IS_PR
? this._getDaysBeforePrClose()
: this._getDaysBeforeIssueClose();

if (IS_PR) {
core.info(`Days before pull request close: ${daysBeforeClose}`);
} else {
core.info(`Days before issue close: ${daysBeforeClose}`);
}

const issueHasUpdate: boolean = IssueProcessor.updatedSince(
issue.updated_at,
this.options.daysBeforeClose
daysBeforeClose
);
core.info(`Issue #${issue.number} has been updated: ${issueHasUpdate}`);

Expand All @@ -241,7 +279,7 @@ export class IssueProcessor {
}

// now start closing logic
if (this.options.daysBeforeClose < 0) {
if (daysBeforeClose < 0) {
return; // nothing to do because we aren't closing stale issues
}

Expand Down Expand Up @@ -488,11 +526,27 @@ export class IssueProcessor {
return staleLabeledEvent.created_at;
}

private static updatedSince(timestamp: string, num_days: number): boolean {
const daysInMillis = 1000 * 60 * 60 * 24 * num_days;
const millisSinceLastUpdated =
new Date().getTime() - new Date(timestamp).getTime();
private _getDaysBeforeIssueStale(): number {
return isNaN(this.options.daysBeforeIssueStale)
? this.options.daysBeforeStale
: this.options.daysBeforeIssueStale;
}

return millisSinceLastUpdated <= daysInMillis;
private _getDaysBeforePrStale(): number {
return isNaN(this.options.daysBeforePrStale)
? this.options.daysBeforeStale
: this.options.daysBeforePrStale;
}

private _getDaysBeforeIssueClose(): number {
return isNaN(this.options.daysBeforeIssueClose)
? this.options.daysBeforeClose
: this.options.daysBeforeIssueClose;
}

private _getDaysBeforePrClose(): number {
return isNaN(this.options.daysBeforePrClose)
? this.options.daysBeforeClose
: this.options.daysBeforePrClose;
}
}
7 changes: 7 additions & 0 deletions src/enums/issue-type.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @todo fix this "false" report from ESLint
// Or I just missed something IDK
// eslint-disable-next-line no-shadow
export enum IssueTypeEnum {
ISSUE = 'issue',
PULL_REQUEST = 'pr'
}
33 changes: 33 additions & 0 deletions src/functions/get-issue-type.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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');
});
});
});
5 changes: 5 additions & 0 deletions src/functions/get-issue-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {IssueTypeEnum} from '../enums/issue-type.enum';

export function getIssueType(isPullRequest: Readonly<boolean>): IssueTypeEnum {
return isPullRequest ? IssueTypeEnum.PULL_REQUEST : IssueTypeEnum.ISSUE;
}
57 changes: 57 additions & 0 deletions src/functions/is-pull-request.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {Issue} from '../IssueProcessor';
import {isPullRequest} from './is-pull-request';

describe('isPullRequest()', (): void => {
let issue: Issue;

describe('when the given issue has an undefined pull request', (): void => {
beforeEach((): void => {
issue = {
pull_request: undefined
} as Issue;
});

it('should return false', (): void => {
expect.assertions(1);

const result = isPullRequest(issue);

expect(result).toStrictEqual(false);
});
});

describe('when the given issue has a null pull request', (): void => {
beforeEach((): void => {
issue = {
pull_request: null
} as Issue;
});

it('should return false', (): void => {
expect.assertions(1);

const result = isPullRequest(issue);

expect(result).toStrictEqual(false);
});
});

describe.each([{}, true])(
'when the given issue has pull request',
(value): void => {
beforeEach((): void => {
issue = {
pull_request: value
} as Issue;
});

it('should return true', (): void => {
expect.assertions(1);

const result = isPullRequest(issue);

expect(result).toStrictEqual(true);
});
}
);
});
5 changes: 5 additions & 0 deletions src/functions/is-pull-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {Issue} from '../IssueProcessor';

export function isPullRequest(issue: Readonly<Issue>): boolean {
return !!issue.pull_request;
}
47 changes: 47 additions & 0 deletions src/functions/should-mark-when-stale.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {shouldMarkWhenStale} from './should-mark-when-stale';

describe('shouldMarkWhenStale()', (): void => {
let daysBeforeStale: number;

describe('when the given number of days indicate that it should be stalled', (): void => {
beforeEach((): void => {
daysBeforeStale = -1;
});

it('should return false', (): void => {
expect.assertions(1);

const result = shouldMarkWhenStale(daysBeforeStale);

expect(result).toStrictEqual(false);
});
});

describe('when the given number of days indicate that it should be stalled today', (): void => {
beforeEach((): void => {
daysBeforeStale = 0;
});

it('should return true', (): void => {
expect.assertions(1);

const result = shouldMarkWhenStale(daysBeforeStale);

expect(result).toStrictEqual(true);
});
});

describe('when the given number of days indicate that it should be stalled tomorrow', (): void => {
beforeEach((): void => {
daysBeforeStale = 1;
});

it('should return true', (): void => {
expect.assertions(1);

const result = shouldMarkWhenStale(daysBeforeStale);

expect(result).toStrictEqual(true);
});
});
});
5 changes: 5 additions & 0 deletions src/functions/should-mark-when-stale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function shouldMarkWhenStale(
daysBeforeStale: Readonly<number>
): boolean {
return daysBeforeStale >= 0;
}

0 comments on commit c6e82d1

Please sign in to comment.