mirror of
https://github.com/actions/stale.git
synced 2025-12-10 20:21:20 +00:00
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:
committed by
GitHub
parent
7f340a46f3
commit
f698371c0d
@@ -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);
|
||||
|
||||
|
||||
33
src/functions/dates/get-humanized-date.spec.ts
Normal file
33
src/functions/dates/get-humanized-date.spec.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
17
src/functions/dates/get-humanized-date.ts
Normal file
17
src/functions/dates/get-humanized-date.ts
Normal 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('-');
|
||||
}
|
||||
51
src/functions/dates/is-date-more-recent-than.spec.ts
Normal file
51
src/functions/dates/is-date-more-recent-than.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
6
src/functions/dates/is-date-more-recent-than.ts
Normal file
6
src/functions/dates/is-date-more-recent-than.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function isDateMoreRecentThan(
|
||||
date: Readonly<Date>,
|
||||
comparedDate: Readonly<Date>
|
||||
): boolean {
|
||||
return date > comparedDate;
|
||||
}
|
||||
61
src/functions/dates/is-valid-date.spec.ts
Normal file
61
src/functions/dates/is-valid-date.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
18
src/functions/dates/is-valid-date.ts
Normal file
18
src/functions/dates/is-valid-date.ts
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import deburr from 'lodash.deburr';
|
||||
import {Issue, Label} from '../IssueProcessor';
|
||||
|
||||
type CleanLabel = string;
|
||||
import {CleanLabel} from '../types/clean-label';
|
||||
|
||||
/**
|
||||
* @description
|
||||
|
||||
20
src/main.ts
20
src/main.ts
@@ -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
1
src/types/clean-label.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type CleanLabel = string;
|
||||
1
src/types/humanized-date.ts
Normal file
1
src/types/humanized-date.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type HumanizedDate = string;
|
||||
1
src/types/iso-date-string.ts
Normal file
1
src/types/iso-date-string.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type IsoDateString = string;
|
||||
1
src/types/iso-or-rfc-date-string.ts
Normal file
1
src/types/iso-or-rfc-date-string.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type IsoOrRfcDateString = string;
|
||||
Reference in New Issue
Block a user