Add a start date option to ignore old issues and PRs (#269)

* docs(readme): add a small precision about the operations-per-run

closes #230

* chore(lint): ignore the lib folder for prettier

* chore(date): add a function to check if a date is valid

* chore(date): add a function to get a humanized date

* chore(date): add a function to check if the date is more recent than

* feat(date): add a start date to ignore old issues and PRs

closes #174

* docs(readme): change the date to match the description

* chore(date): add a better type for the date

* docs(date): add missing JSDoc about the return type

* chore(rebase): fix issues due to rebase

* docs(readme): fix table formatting issues
This commit is contained in:
Geoffrey Testelin
2021-01-18 02:22:36 +01:00
committed by GitHub
parent 7f340a46f3
commit f698371c0d
19 changed files with 694 additions and 69 deletions

View File

@@ -1,7 +1,10 @@
import {context, getOctokit} from '@actions/github';
import {GitHub} from '@actions/github/lib/utils';
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
import {IssueType} from './enums/issue-type.enum';
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';
@@ -9,11 +12,14 @@ 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;
updated_at: string;
created_at: IsoDateString;
updated_at: IsoDateString;
labels: Label[];
pull_request: any;
state: string;
@@ -72,6 +78,7 @@ export interface IssueProcessorOptions {
skipStaleIssueMessage: boolean;
skipStalePrMessage: boolean;
deleteBranch: boolean;
startDate: IsoOrRfcDateString | undefined; // Should be ISO 8601 or RFC 2822
}
const logger: Logger = new Logger();
@@ -204,6 +211,39 @@ export class IssueProcessor {
continue; // don't process locked issues
}
if (this.options.startDate) {
const startDate: Date = new Date(this.options.startDate);
const createdAt: Date = new Date(issue.created_at);
issueLogger.info(
`A start date was specified for the ${getHumanizedDate(startDate)} (${
this.options.startDate
})`
);
// Expecting that GitHub will always set a creation date on the issues and PRs
// But you never know!
if (!isValidDate(createdAt)) {
throw new Error(
`Invalid issue field: "created_at". Expected a valid date`
);
}
issueLogger.info(
`Issue created the ${getHumanizedDate(createdAt)} (${
issue.created_at
})`
);
if (!isDateMoreRecentThan(createdAt, startDate)) {
issueLogger.info(
`Skipping ${issueType} because it was created before the specified start date`
);
continue; // don't process issues which were created before the start date
}
}
// Does this issue have a stale label?
let isStale: boolean = isLabeled(issue, staleLabel);

View File

@@ -0,0 +1,33 @@
import {getHumanizedDate} from './get-humanized-date';
describe('getHumanizedDate()', (): void => {
let date: Date;
describe('when the given date is the 1st of april 2020', (): void => {
beforeEach((): void => {
date = new Date(2020, 3, 1);
});
it('should return the date formatted as DD-MM-YYYY', (): void => {
expect.assertions(1);
const result = getHumanizedDate(date);
expect(result).toStrictEqual('01-04-2020');
});
});
describe('when the given date is the 18st of december 2020', (): void => {
beforeEach((): void => {
date = new Date(2020, 11, 18);
});
it('should return the date formatted as DD-MM-YYYY', (): void => {
expect.assertions(1);
const result = getHumanizedDate(date);
expect(result).toStrictEqual('18-12-2020');
});
});
});

View File

@@ -0,0 +1,17 @@
import {HumanizedDate} from '../../types/humanized-date';
export function getHumanizedDate(date: Readonly<Date>): HumanizedDate {
const year: number = date.getFullYear();
let month = `${date.getMonth() + 1}`;
let day = `${date.getDate()}`;
if (month.length < 2) {
month = `0${month}`;
}
if (day.length < 2) {
day = `0${day}`;
}
return [day, month, year].join('-');
}

View File

@@ -0,0 +1,51 @@
import {isDateMoreRecentThan} from './is-date-more-recent-than';
describe('isDateMoreRecentThan()', (): void => {
let date: Date;
let comparedDate: Date;
describe('when the given date is older than the compared date', (): void => {
beforeEach((): void => {
date = new Date(2020, 0, 20);
comparedDate = new Date(2021, 0, 20);
});
it('should return false', (): void => {
expect.assertions(1);
const result = isDateMoreRecentThan(date, comparedDate);
expect(result).toStrictEqual(false);
});
});
describe('when the given date is equal to the compared date', (): void => {
beforeEach((): void => {
date = new Date(2020, 0, 20);
comparedDate = new Date(2020, 0, 20);
});
it('should return false', (): void => {
expect.assertions(1);
const result = isDateMoreRecentThan(date, comparedDate);
expect(result).toStrictEqual(false);
});
});
describe('when the given date is more recent than the compared date', (): void => {
beforeEach((): void => {
date = new Date(2021, 0, 20);
comparedDate = new Date(2020, 0, 20);
});
it('should return true', (): void => {
expect.assertions(1);
const result = isDateMoreRecentThan(date, comparedDate);
expect(result).toStrictEqual(true);
});
});
});

View File

@@ -0,0 +1,6 @@
export function isDateMoreRecentThan(
date: Readonly<Date>,
comparedDate: Readonly<Date>
): boolean {
return date > comparedDate;
}

View File

@@ -0,0 +1,61 @@
import {isValidDate} from './is-valid-date';
describe('isValidDate()', (): void => {
let date: Date;
describe('when the given date is an invalid date', (): void => {
beforeEach((): void => {
date = new Date('16-04-1994');
});
it('should return false', (): void => {
expect.assertions(1);
const result = isValidDate(date);
expect(result).toStrictEqual(false);
});
});
describe('when the given date is a new date', (): void => {
beforeEach((): void => {
date = new Date();
});
it('should return true', (): void => {
expect.assertions(1);
const result = isValidDate(date);
expect(result).toStrictEqual(true);
});
});
describe('when the given date is an ISO and valid date', (): void => {
beforeEach((): void => {
date = new Date('2011-04-22T13:33:48Z');
});
it('should return true', (): void => {
expect.assertions(1);
const result = isValidDate(date);
expect(result).toStrictEqual(true);
});
});
describe('when the given date is an ISO with ms and valid date', (): void => {
beforeEach((): void => {
date = new Date('2011-10-05T14:48:00.000Z');
});
it('should return true', (): void => {
expect.assertions(1);
const result = isValidDate(date);
expect(result).toStrictEqual(true);
});
});
});

View File

@@ -0,0 +1,18 @@
/**
* @description
* Check if a date is valid
*
* @see
* https://stackoverflow.com/a/1353711/4440414
*
* @param {Readonly<Date>} date The date to check
*
* @returns {boolean} true when the given date is valid
*/
export function isValidDate(date: Readonly<Date>): boolean {
if (Object.prototype.toString.call(date) === '[object Date]') {
return !isNaN(date.getTime());
}
return false;
}

View File

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

View File

@@ -1,7 +1,6 @@
import deburr from 'lodash.deburr';
import {Issue, Label} from '../IssueProcessor';
type CleanLabel = string;
import {CleanLabel} from '../types/clean-label';
/**
* @description

View File

@@ -1,4 +1,5 @@
import * as core from '@actions/core';
import {isValidDate} from './functions/dates/is-valid-date';
import {IssueProcessor, IssueProcessorOptions} from './IssueProcessor';
async function run(): Promise<void> {
@@ -14,7 +15,7 @@ async function run(): Promise<void> {
}
function getAndValidateArgs(): IssueProcessorOptions {
const args = {
const args: IssueProcessorOptions = {
repoToken: core.getInput('repo-token'),
staleIssueMessage: core.getInput('stale-issue-message'),
stalePrMessage: core.getInput('stale-pr-message'),
@@ -47,7 +48,11 @@ function getAndValidateArgs(): IssueProcessorOptions {
ascending: core.getInput('ascending') === 'true',
skipStalePrMessage: core.getInput('skip-stale-pr-message') === 'true',
skipStaleIssueMessage: core.getInput('skip-stale-issue-message') === 'true',
deleteBranch: core.getInput('delete-branch') === 'true'
deleteBranch: core.getInput('delete-branch') === 'true',
startDate:
core.getInput('start-date') !== ''
? core.getInput('start-date')
: undefined
};
for (const numberInput of [
@@ -64,6 +69,17 @@ function getAndValidateArgs(): IssueProcessorOptions {
}
}
for (const optionalDateInput of ['start-date']) {
// Ignore empty dates because it is considered as the right type for a default value (so a valid one)
if (core.getInput(optionalDateInput) !== '') {
if (!isValidDate(new Date(core.getInput(optionalDateInput)))) {
throw new Error(
`input ${optionalDateInput} did not parse to a valid date`
);
}
}
}
return args;
}

1
src/types/clean-label.ts Normal file
View File

@@ -0,0 +1 @@
export type CleanLabel = string;

View File

@@ -0,0 +1 @@
export type HumanizedDate = string;

View File

@@ -0,0 +1 @@
export type IsoDateString = string;

View File

@@ -0,0 +1 @@
export type IsoOrRfcDateString = string;