mirror of
https://github.com/actions/stale.git
synced 2025-12-11 12:37:27 +00:00
feat(assignees): add 6 new options to avoid stale for assignees (#327)
* feat(assignees): add new option to avoid stale for assignees closes #271 * test: add more coverage * docs: fix readme format issue * docs: reorder and enhance typo * docs(contributing): add more information about the npm scripts
This commit is contained in:
committed by
GitHub
parent
996798eb71
commit
ec96ff65b0
594
src/classes/assignees.spec.ts
Normal file
594
src/classes/assignees.spec.ts
Normal file
@@ -0,0 +1,594 @@
|
||||
import {DefaultProcessorOptions} from '../../__tests__/constants/default-processor-options';
|
||||
import {generateIIssue} from '../../__tests__/functions/generate-iissue';
|
||||
import {IIssue} from '../interfaces/issue';
|
||||
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||
import {Issue} from './issue';
|
||||
import {Assignees} from './assignees';
|
||||
|
||||
describe('Assignees', (): void => {
|
||||
let assignees: Assignees;
|
||||
let optionsInterface: IIssuesProcessorOptions;
|
||||
let issue: Issue;
|
||||
let issueInterface: IIssue;
|
||||
|
||||
beforeEach((): void => {
|
||||
optionsInterface = {...DefaultProcessorOptions};
|
||||
issueInterface = generateIIssue();
|
||||
});
|
||||
|
||||
describe('shouldExemptAssignees()', (): 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 an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptAssignees = '';
|
||||
});
|
||||
|
||||
describe('when the given options are not configured to exempt an issue with an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptIssueAssignees = '';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt an issue with an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptIssueAssignees =
|
||||
'dummy-exempt-issue-assignee';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee different than the exempt issue assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee equaling the exempt issue assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-exempt-issue-assignee'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptAssignees = 'dummy-exempt-assignee';
|
||||
});
|
||||
|
||||
describe('when the given options are not configured to exempt an issue with an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptIssueAssignees = '';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee different than the exempt assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee equaling the exempt assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-exempt-assignee'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt an issue with an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptIssueAssignees =
|
||||
'dummy-exempt-issue-assignee';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee different than the exempt issue assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee equaling the exempt issue assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-exempt-issue-assignee'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee different than the exempt assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee equaling the exempt assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-exempt-assignee'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
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 an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptAssignees = '';
|
||||
});
|
||||
|
||||
describe('when the given options are not configured to exempt a pull request with an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptPrAssignees = '';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt a pull request with an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptPrAssignees = 'dummy-exempt-pr-assignee';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee different than the exempt pull request assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee equaling the exempt pull request assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-exempt-pr-assignee'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptAssignees = 'dummy-exempt-assignee';
|
||||
});
|
||||
|
||||
describe('when the given options are not configured to exempt a pull request with an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptPrAssignees = '';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee different than the exempt assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee equaling the exempt assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-exempt-assignee'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given options are configured to exempt a pull request with an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
optionsInterface.exemptPrAssignees = 'dummy-exempt-pr-assignee';
|
||||
});
|
||||
|
||||
describe('when the given issue does not have an assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee different than the exempt pull request assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee equaling the exempt pull request assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-exempt-pr-assignee'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee different than the exempt assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue does have an assignee equaling the exempt assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-exempt-assignee'
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
assignees = new Assignees(optionsInterface, issue);
|
||||
|
||||
const result = assignees.shouldExemptAssignees();
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
251
src/classes/assignees.ts
Normal file
251
src/classes/assignees.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
import deburr from 'lodash.deburr';
|
||||
import {wordsToList} from '../functions/words-to-list';
|
||||
import {IAssignee} from '../interfaces/assignee';
|
||||
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||
import {Issue} from './issue';
|
||||
import {IssueLogger} from './loggers/issue-logger';
|
||||
|
||||
type CleanAssignee = string;
|
||||
|
||||
export class Assignees {
|
||||
private static _cleanAssignee(assignee: Readonly<string>): CleanAssignee {
|
||||
return deburr(assignee.toLowerCase());
|
||||
}
|
||||
|
||||
private readonly _options: IIssuesProcessorOptions;
|
||||
private readonly _issue: Issue;
|
||||
private readonly _issueLogger: IssueLogger;
|
||||
|
||||
constructor(options: Readonly<IIssuesProcessorOptions>, issue: Issue) {
|
||||
this._options = options;
|
||||
this._issue = issue;
|
||||
this._issueLogger = new IssueLogger(issue);
|
||||
}
|
||||
|
||||
shouldExemptAssignees(): boolean {
|
||||
if (!this._issue.hasAssignees) {
|
||||
this._issueLogger.info('This $$type has no assignee');
|
||||
this._logSkip();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._shouldExemptAllAssignees()) {
|
||||
this._issueLogger.info(
|
||||
'Skipping $$type because it has an exempt assignee'
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const exemptAssignees: string[] = this._getExemptAssignees();
|
||||
|
||||
if (exemptAssignees.length === 0) {
|
||||
this._issueLogger.info(
|
||||
`No option was specified to skip the stale process for this $$type`
|
||||
);
|
||||
this._logSkip();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this._issueLogger.info(
|
||||
`Found ${exemptAssignees.length} assignee${
|
||||
exemptAssignees.length > 1 ? 's' : ''
|
||||
} on this $$type`
|
||||
);
|
||||
|
||||
const hasExemptAssignee: boolean = exemptAssignees.some(
|
||||
(exemptAssignee: Readonly<string>): boolean =>
|
||||
this._hasAssignee(exemptAssignee)
|
||||
);
|
||||
|
||||
if (!hasExemptAssignee) {
|
||||
this._issueLogger.info(
|
||||
'No assignee on this $$type can exempt the stale process'
|
||||
);
|
||||
this._logSkip();
|
||||
} else {
|
||||
this._issueLogger.info(
|
||||
'Skipping this $$type because it has an exempt assignee'
|
||||
);
|
||||
}
|
||||
|
||||
return hasExemptAssignee;
|
||||
}
|
||||
|
||||
private _getExemptAssignees(): string[] {
|
||||
return this._issue.isPullRequest
|
||||
? this._getExemptPullRequestAssignees()
|
||||
: this._getExemptIssueAssignees();
|
||||
}
|
||||
|
||||
private _getExemptIssueAssignees(): string[] {
|
||||
if (this._options.exemptIssueAssignees === '') {
|
||||
this._issueLogger.info(
|
||||
'The option "exemptIssueAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
|
||||
);
|
||||
|
||||
if (this._options.exemptAssignees === '') {
|
||||
this._issueLogger.info(
|
||||
'The option "exemptAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
const exemptAssignees: string[] = wordsToList(
|
||||
this._options.exemptAssignees
|
||||
);
|
||||
|
||||
this._issueLogger.info(
|
||||
`The option "exemptAssignees" is set. ${
|
||||
exemptAssignees.length
|
||||
} assignee${
|
||||
exemptAssignees.length === 1 ? '' : 's'
|
||||
} can skip the stale process for this $$type`
|
||||
);
|
||||
|
||||
return exemptAssignees;
|
||||
}
|
||||
|
||||
const exemptAssignees: string[] = wordsToList(
|
||||
this._options.exemptIssueAssignees
|
||||
);
|
||||
|
||||
this._issueLogger.info(
|
||||
`The option "exemptIssueAssignees" is set. ${
|
||||
exemptAssignees.length
|
||||
} assignee${
|
||||
exemptAssignees.length === 1 ? '' : 's'
|
||||
} can skip the stale process for this $$type`
|
||||
);
|
||||
|
||||
return exemptAssignees;
|
||||
}
|
||||
|
||||
private _getExemptPullRequestAssignees(): string[] {
|
||||
if (this._options.exemptPrAssignees === '') {
|
||||
this._issueLogger.info(
|
||||
'The option "exemptPrAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
|
||||
);
|
||||
|
||||
if (this._options.exemptAssignees === '') {
|
||||
this._issueLogger.info(
|
||||
'The option "exemptAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
const exemptAssignees: string[] = wordsToList(
|
||||
this._options.exemptAssignees
|
||||
);
|
||||
|
||||
this._issueLogger.info(
|
||||
`The option "exemptAssignees" is set. ${
|
||||
exemptAssignees.length
|
||||
} assignee${
|
||||
exemptAssignees.length === 1 ? '' : 's'
|
||||
} can skip the stale process for this $$type`
|
||||
);
|
||||
|
||||
return exemptAssignees;
|
||||
}
|
||||
|
||||
const exemptAssignees: string[] = wordsToList(
|
||||
this._options.exemptPrAssignees
|
||||
);
|
||||
|
||||
this._issueLogger.info(
|
||||
`The option "exemptPrAssignees" is set. ${
|
||||
exemptAssignees.length
|
||||
} assignee${
|
||||
exemptAssignees.length === 1 ? '' : 's'
|
||||
} can skip the stale process for this $$type`
|
||||
);
|
||||
|
||||
return exemptAssignees;
|
||||
}
|
||||
|
||||
private _hasAssignee(assignee: Readonly<string>): boolean {
|
||||
const cleanAssignee: CleanAssignee = Assignees._cleanAssignee(assignee);
|
||||
|
||||
return this._issue.assignees.some(
|
||||
(issueAssignee: Readonly<IAssignee>): boolean => {
|
||||
const isSameAssignee: boolean =
|
||||
cleanAssignee === Assignees._cleanAssignee(issueAssignee.login);
|
||||
|
||||
if (isSameAssignee) {
|
||||
this._issueLogger.info(
|
||||
`@${issueAssignee.login} is assigned on this $$type and is an exempt assignee`
|
||||
);
|
||||
}
|
||||
|
||||
return isSameAssignee;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _shouldExemptAllAssignees(): boolean {
|
||||
return this._issue.isPullRequest
|
||||
? this._shouldExemptAllPullRequestAssignees()
|
||||
: this._shouldExemptAllIssueAssignees();
|
||||
}
|
||||
|
||||
private _shouldExemptAllIssueAssignees(): boolean {
|
||||
if (this._options.exemptAllIssueAssignees === true) {
|
||||
this._issueLogger.info(
|
||||
'The option "exemptAllIssueAssignees" is enabled. Any assignee on this $$type will skip the stale process'
|
||||
);
|
||||
|
||||
return true;
|
||||
} else if (this._options.exemptAllIssueAssignees === false) {
|
||||
this._issueLogger.info(
|
||||
'The option "exemptAllIssueAssignees" is disabled. Only some specific assignees on this $$type will skip the stale process'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this._logExemptAllAssigneesOption();
|
||||
|
||||
return this._options.exemptAllAssignees;
|
||||
}
|
||||
|
||||
private _shouldExemptAllPullRequestAssignees(): boolean {
|
||||
if (this._options.exemptAllPrAssignees === true) {
|
||||
this._issueLogger.info(
|
||||
'The option "exemptAllPrAssignees" is enabled. Any assignee on this $$type will skip the stale process'
|
||||
);
|
||||
|
||||
return true;
|
||||
} else if (this._options.exemptAllPrAssignees === false) {
|
||||
this._issueLogger.info(
|
||||
'The option "exemptAllPrAssignees" is disabled. Only some specific assignees on this $$type will skip the stale process'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this._logExemptAllAssigneesOption();
|
||||
|
||||
return this._options.exemptAllAssignees;
|
||||
}
|
||||
|
||||
private _logExemptAllAssigneesOption(): void {
|
||||
if (this._options.exemptAllAssignees) {
|
||||
this._issueLogger.info(
|
||||
'The option "exemptAllAssignees" is enabled. Any assignee on this $$type will skip the stale process'
|
||||
);
|
||||
} else {
|
||||
this._issueLogger.info(
|
||||
'The option "exemptAllAssignees" is disabled. Only some specific assignees on this $$type will skip the stale process'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _logSkip(): void {
|
||||
this._issueLogger.info('Skip the assignees checks');
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import {IAssignee} from '../interfaces/assignee';
|
||||
import {IIssue} from '../interfaces/issue';
|
||||
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||
import {ILabel} from '../interfaces/label';
|
||||
@@ -25,10 +26,7 @@ describe('Issue', (): void => {
|
||||
debugOnly: false,
|
||||
deleteBranch: false,
|
||||
exemptIssueLabels: '',
|
||||
exemptIssueMilestones: '',
|
||||
exemptMilestones: '',
|
||||
exemptPrLabels: '',
|
||||
exemptPrMilestones: '',
|
||||
onlyLabels: '',
|
||||
operationsPerRun: 0,
|
||||
removeStaleWhenUpdated: false,
|
||||
@@ -40,9 +38,18 @@ describe('Issue', (): void => {
|
||||
startDate: undefined,
|
||||
stalePrLabel: 'dummy-stale-pr-label',
|
||||
staleIssueLabel: 'dummy-stale-issue-label',
|
||||
exemptMilestones: '',
|
||||
exemptIssueMilestones: '',
|
||||
exemptPrMilestones: '',
|
||||
exemptAllMilestones: false,
|
||||
exemptAllIssueMilestones: undefined,
|
||||
exemptAllPrMilestones: undefined
|
||||
exemptAllPrMilestones: undefined,
|
||||
exemptAssignees: '',
|
||||
exemptIssueAssignees: '',
|
||||
exemptPrAssignees: '',
|
||||
exemptAllAssignees: false,
|
||||
exemptAllIssueAssignees: undefined,
|
||||
exemptAllPrAssignees: undefined
|
||||
};
|
||||
issueInterface = {
|
||||
title: 'dummy-title',
|
||||
@@ -59,7 +66,12 @@ describe('Issue', (): void => {
|
||||
locked: false,
|
||||
milestone: {
|
||||
title: 'dummy-milestone'
|
||||
}
|
||||
},
|
||||
assignees: [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
]
|
||||
};
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
@@ -125,56 +137,14 @@ describe('Issue', (): void => {
|
||||
} 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 assignees with the given issue assignees', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
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');
|
||||
});
|
||||
expect(issue.assignees).toStrictEqual([
|
||||
{
|
||||
login: 'dummy-login'
|
||||
} as IAssignee
|
||||
]);
|
||||
});
|
||||
|
||||
describe('when the given issue does not contains the stale label', (): void => {
|
||||
@@ -209,4 +179,104 @@ describe('Issue', (): void => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get isPullRequest', (): void => {
|
||||
describe('when the issue pull_request is not set', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.pull_request = undefined;
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = issue.isPullRequest;
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the issue pull_request is set', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.pull_request = {};
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = issue.isPullRequest;
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get staleLabel', (): void => {
|
||||
describe('when the issue is not a pull request', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.pull_request = undefined;
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
it('should return the issue stale label', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = issue.staleLabel;
|
||||
|
||||
expect(result).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 return the pull request stale label', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = issue.staleLabel;
|
||||
|
||||
expect(result).toStrictEqual('dummy-stale-pr-label');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get hasAssignees', (): void => {
|
||||
describe('when the issue has no assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [];
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = issue.hasAssignees;
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the issue has at least one assignee', (): void => {
|
||||
beforeEach((): void => {
|
||||
issueInterface.assignees = [
|
||||
{
|
||||
login: 'dummy-login'
|
||||
}
|
||||
];
|
||||
issue = new Issue(optionsInterface, issueInterface);
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = issue.hasAssignees;
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {isLabeled} from '../functions/is-labeled';
|
||||
import {isPullRequest} from '../functions/is-pull-request';
|
||||
import {IAssignee} from '../interfaces/assignee';
|
||||
import {IIssue} from '../interfaces/issue';
|
||||
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||
import {ILabel} from '../interfaces/label';
|
||||
@@ -17,8 +18,7 @@ export class Issue implements IIssue {
|
||||
readonly state: string;
|
||||
readonly locked: boolean;
|
||||
readonly milestone: IMilestone | undefined;
|
||||
readonly isPullRequest: boolean;
|
||||
readonly staleLabel: string;
|
||||
readonly assignees: IAssignee[];
|
||||
isStale: boolean;
|
||||
|
||||
constructor(
|
||||
@@ -35,12 +35,23 @@ export class Issue implements IIssue {
|
||||
this.state = issue.state;
|
||||
this.locked = issue.locked;
|
||||
this.milestone = issue.milestone;
|
||||
this.assignees = issue.assignees;
|
||||
|
||||
this.isPullRequest = isPullRequest(this);
|
||||
this.staleLabel = this._getStaleLabel();
|
||||
this.isStale = isLabeled(this, this.staleLabel);
|
||||
}
|
||||
|
||||
get isPullRequest(): boolean {
|
||||
return isPullRequest(this);
|
||||
}
|
||||
|
||||
get staleLabel(): string {
|
||||
return this._getStaleLabel();
|
||||
}
|
||||
|
||||
get hasAssignees(): boolean {
|
||||
return this.assignees.length > 0;
|
||||
}
|
||||
|
||||
private _getStaleLabel(): string {
|
||||
return this.isPullRequest
|
||||
? this._options.stalePrLabel
|
||||
|
||||
@@ -15,6 +15,7 @@ import {IIssue} from '../interfaces/issue';
|
||||
import {IIssueEvent} from '../interfaces/issue-event';
|
||||
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||
import {IPullRequest} from '../interfaces/pull-request';
|
||||
import {Assignees} from './assignees';
|
||||
import {Issue} from './issue';
|
||||
import {IssueLogger} from './loggers/issue-logger';
|
||||
import {Logger} from './loggers/logger';
|
||||
@@ -97,9 +98,7 @@ export class IssuesProcessor {
|
||||
for (const issue of issues.values()) {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(
|
||||
`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${issue.isPullRequest})`
|
||||
);
|
||||
issueLogger.info(`Found this $$type last updated ${issue.updated_at}`);
|
||||
|
||||
// calculate string based messages for this issue
|
||||
const staleMessage: string = issue.isPullRequest
|
||||
@@ -122,26 +121,22 @@ export class IssuesProcessor {
|
||||
? this._getDaysBeforePrStale()
|
||||
: this._getDaysBeforeIssueStale();
|
||||
|
||||
if (issue.isPullRequest) {
|
||||
issueLogger.info(`Days before pull request stale: ${daysBeforeStale}`);
|
||||
} else {
|
||||
issueLogger.info(`Days before issue stale: ${daysBeforeStale}`);
|
||||
}
|
||||
issueLogger.info(`Days before $$type stale: ${daysBeforeStale}`);
|
||||
|
||||
const shouldMarkAsStale: boolean = shouldMarkWhenStale(daysBeforeStale);
|
||||
|
||||
if (!staleMessage && shouldMarkAsStale) {
|
||||
issueLogger.info(`Skipping ${issueType} due to empty stale message`);
|
||||
issueLogger.info(`Skipping $$type due to empty stale message`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (issue.state === 'closed') {
|
||||
issueLogger.info(`Skipping ${issueType} because it is closed`);
|
||||
issueLogger.info(`Skipping $$type because it is closed`);
|
||||
continue; // don't process closed issues
|
||||
}
|
||||
|
||||
if (issue.locked) {
|
||||
issueLogger.info(`Skipping ${issueType} because it is locked`);
|
||||
issueLogger.info(`Skipping $$type because it is locked`);
|
||||
continue; // don't process locked issues
|
||||
}
|
||||
|
||||
@@ -164,14 +159,14 @@ export class IssuesProcessor {
|
||||
}
|
||||
|
||||
issueLogger.info(
|
||||
`Issue created the ${getHumanizedDate(createdAt)} (${
|
||||
`$$type created the ${getHumanizedDate(createdAt)} (${
|
||||
issue.created_at
|
||||
})`
|
||||
);
|
||||
|
||||
if (!isDateMoreRecentThan(createdAt, startDate)) {
|
||||
issueLogger.info(
|
||||
`Skipping ${issueType} because it was created before the specified start date`
|
||||
`Skipping $$type because it was created before the specified start date`
|
||||
);
|
||||
|
||||
continue; // don't process issues which were created before the start date
|
||||
@@ -179,9 +174,9 @@ export class IssuesProcessor {
|
||||
}
|
||||
|
||||
if (issue.isStale) {
|
||||
issueLogger.info(`This issue has a stale label`);
|
||||
issueLogger.info(`This $$type has a stale label`);
|
||||
} else {
|
||||
issueLogger.info(`This issue hasn't a stale label`);
|
||||
issueLogger.info(`This $$type hasn't a stale label`);
|
||||
}
|
||||
|
||||
const exemptLabels: string[] = wordsToList(
|
||||
@@ -200,9 +195,7 @@ export class IssuesProcessor {
|
||||
await this._removeStaleLabel(issue, staleLabel);
|
||||
}
|
||||
|
||||
issueLogger.info(
|
||||
`Skipping ${issueType} because it has an exempt label`
|
||||
);
|
||||
issueLogger.info(`Skipping $$type because it has an exempt label`);
|
||||
continue; // don't process exempt issues
|
||||
}
|
||||
|
||||
@@ -210,11 +203,17 @@ export class IssuesProcessor {
|
||||
|
||||
if (milestones.shouldExemptMilestones()) {
|
||||
issueLogger.info(
|
||||
`Skipping ${issueType} because it has an exempt milestone`
|
||||
`Skipping $$type because it has an exempted milestone`
|
||||
);
|
||||
continue; // don't process exempt milestones
|
||||
}
|
||||
|
||||
const assignees: Assignees = new Assignees(this.options, issue);
|
||||
|
||||
if (assignees.shouldExemptAssignees()) {
|
||||
continue; // don't process exempt assignees
|
||||
}
|
||||
|
||||
// should this issue be marked stale?
|
||||
const shouldBeStale = !IssuesProcessor._updatedSince(
|
||||
issue.updated_at,
|
||||
@@ -224,7 +223,7 @@ export class IssuesProcessor {
|
||||
// determine if this issue needs to be marked stale first
|
||||
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`
|
||||
`Marking $$type 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);
|
||||
issue.isStale = true; // this issue is now considered stale
|
||||
@@ -236,7 +235,7 @@ export class IssuesProcessor {
|
||||
|
||||
// process the issue if it was marked stale
|
||||
if (issue.isStale) {
|
||||
issueLogger.info(`Found a stale ${issueType}`);
|
||||
issueLogger.info(`Found a stale $$type`);
|
||||
await this._processStaleIssue(
|
||||
issue,
|
||||
issueType,
|
||||
@@ -271,37 +270,27 @@ export class IssuesProcessor {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
const markedStaleOn: string =
|
||||
(await this._getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
|
||||
issueLogger.info(
|
||||
`Issue #${issue.number} marked stale on: ${markedStaleOn}`
|
||||
);
|
||||
issueLogger.info(`$$type marked stale on: ${markedStaleOn}`);
|
||||
|
||||
const issueHasComments: boolean = await this._hasCommentsSince(
|
||||
issue,
|
||||
markedStaleOn,
|
||||
actor
|
||||
);
|
||||
issueLogger.info(
|
||||
`Issue #${issue.number} has been commented on: ${issueHasComments}`
|
||||
);
|
||||
issueLogger.info(`$$type has been commented on: ${issueHasComments}`);
|
||||
|
||||
const isPr: boolean = isPullRequest(issue);
|
||||
const daysBeforeClose: number = isPr
|
||||
? this._getDaysBeforePrClose()
|
||||
: this._getDaysBeforeIssueClose();
|
||||
|
||||
if (isPr) {
|
||||
issueLogger.info(`Days before pull request close: ${daysBeforeClose}`);
|
||||
} else {
|
||||
issueLogger.info(`Days before issue close: ${daysBeforeClose}`);
|
||||
}
|
||||
issueLogger.info(`Days before $$type close: ${daysBeforeClose}`);
|
||||
|
||||
const issueHasUpdate: boolean = IssuesProcessor._updatedSince(
|
||||
issue.updated_at,
|
||||
daysBeforeClose
|
||||
);
|
||||
issueLogger.info(
|
||||
`Issue #${issue.number} has been updated: ${issueHasUpdate}`
|
||||
);
|
||||
issueLogger.info(`$$type has been updated: ${issueHasUpdate}`);
|
||||
|
||||
// should we un-stale this issue?
|
||||
if (this.options.removeStaleWhenUpdated && issueHasComments) {
|
||||
@@ -315,20 +304,20 @@ export class IssuesProcessor {
|
||||
|
||||
if (!issueHasComments && !issueHasUpdate) {
|
||||
issueLogger.info(
|
||||
`Closing ${issueType} because it was last updated on ${issue.updated_at}`
|
||||
`Closing $$type because it was last updated on ${issue.updated_at}`
|
||||
);
|
||||
await this._closeIssue(issue, closeMessage, closeLabel);
|
||||
|
||||
if (this.options.deleteBranch && issue.pull_request) {
|
||||
issueLogger.info(
|
||||
`Deleting branch for #${issue.number} as delete-branch option was specified`
|
||||
`Deleting branch for as delete-branch option was specified`
|
||||
);
|
||||
await this._deleteBranch(issue);
|
||||
this.deletedBranchIssues.push(issue);
|
||||
}
|
||||
} else {
|
||||
issueLogger.info(
|
||||
`Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})`
|
||||
`Stale $$type is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -341,9 +330,7 @@ export class IssuesProcessor {
|
||||
): Promise<boolean> {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(
|
||||
`Checking for comments on issue #${issue.number} since ${sinceDate}`
|
||||
);
|
||||
issueLogger.info(`Checking for comments on $$type since ${sinceDate}`);
|
||||
|
||||
if (!sinceDate) {
|
||||
return true;
|
||||
@@ -433,7 +420,7 @@ export class IssuesProcessor {
|
||||
): Promise<void> {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(`Marking issue #${issue.number} as stale`);
|
||||
issueLogger.info(`Marking $$type as stale`);
|
||||
|
||||
this.staleIssues.push(issue);
|
||||
|
||||
@@ -481,7 +468,7 @@ export class IssuesProcessor {
|
||||
): Promise<void> {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(`Closing issue #${issue.number} for being stale`);
|
||||
issueLogger.info(`Closing $$type for being stale`);
|
||||
|
||||
this.closedIssues.push(issue);
|
||||
|
||||
@@ -525,7 +512,7 @@ export class IssuesProcessor {
|
||||
state: 'closed'
|
||||
});
|
||||
} catch (error) {
|
||||
issueLogger.error(`Error updating an issue: ${error.message}`);
|
||||
issueLogger.error(`Error updating this $$type: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,9 +531,7 @@ export class IssuesProcessor {
|
||||
|
||||
return pullRequest.data;
|
||||
} catch (error) {
|
||||
issueLogger.error(
|
||||
`Error getting pull request ${issue.number}: ${error.message}`
|
||||
);
|
||||
issueLogger.error(`Error getting this $$type: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,9 +539,7 @@ export class IssuesProcessor {
|
||||
private async _deleteBranch(issue: Issue): Promise<void> {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(
|
||||
`Delete branch from closed issue #${issue.number} - ${issue.title}`
|
||||
);
|
||||
issueLogger.info(`Delete branch from closed $$type - ${issue.title}`);
|
||||
|
||||
if (this.options.debugOnly) {
|
||||
return;
|
||||
@@ -566,15 +549,13 @@ export class IssuesProcessor {
|
||||
|
||||
if (!pullRequest) {
|
||||
issueLogger.info(
|
||||
`Not deleting branch as pull request not found for issue ${issue.number}`
|
||||
`Not deleting branch as pull request not found for this $$type`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const branch = pullRequest.head.ref;
|
||||
issueLogger.info(
|
||||
`Deleting branch ${branch} from closed issue #${issue.number}`
|
||||
);
|
||||
issueLogger.info(`Deleting branch ${branch} from closed $$type`);
|
||||
|
||||
this._operationsLeft -= 1;
|
||||
|
||||
@@ -586,7 +567,7 @@ export class IssuesProcessor {
|
||||
});
|
||||
} catch (error) {
|
||||
issueLogger.error(
|
||||
`Error deleting branch ${branch} from issue #${issue.number}: ${error.message}`
|
||||
`Error deleting branch ${branch} from $$type: ${error.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -595,7 +576,7 @@ export class IssuesProcessor {
|
||||
private async _removeLabel(issue: Issue, label: string): Promise<void> {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(`Removing label "${label}" from issue #${issue.number}`);
|
||||
issueLogger.info(`Removing label "${label}" from $$type`);
|
||||
|
||||
this.removedLabelIssues.push(issue);
|
||||
|
||||
@@ -626,7 +607,7 @@ export class IssuesProcessor {
|
||||
): Promise<string | undefined> {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(`Checking for label on issue #${issue.number}`);
|
||||
issueLogger.info(`Checking for label on $$type`);
|
||||
|
||||
this._operationsLeft -= 1;
|
||||
|
||||
@@ -682,9 +663,7 @@ export class IssuesProcessor {
|
||||
): Promise<void> {
|
||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||
|
||||
issueLogger.info(
|
||||
`Issue #${issue.number} is no longer stale. Removing stale label.`
|
||||
);
|
||||
issueLogger.info(`$$type is no longer stale. Removing stale label.`);
|
||||
|
||||
return this._removeLabel(issue, staleLabel);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import {DefaultProcessorOptions} from '../../../__tests__/constants/default-processor-options';
|
||||
import {generateIIssue} from '../../../__tests__/functions/generate-iissue';
|
||||
import {Issue} from '../issue';
|
||||
import {IssueLogger} from './issue-logger';
|
||||
import * as core from '@actions/core';
|
||||
@@ -5,21 +7,20 @@ import * as core from '@actions/core';
|
||||
describe('IssueLogger', (): void => {
|
||||
let issue: Issue;
|
||||
let issueLogger: IssueLogger;
|
||||
let message: string;
|
||||
|
||||
beforeEach((): void => {
|
||||
issue = {
|
||||
number: 8
|
||||
} as Issue;
|
||||
issueLogger = new IssueLogger(issue);
|
||||
});
|
||||
let coreWarningSpy: jest.SpyInstance;
|
||||
|
||||
describe('warning()', (): void => {
|
||||
let message: string;
|
||||
|
||||
let coreWarningSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach((): void => {
|
||||
message = 'dummy-message';
|
||||
issue = new Issue(
|
||||
DefaultProcessorOptions,
|
||||
generateIIssue({
|
||||
number: 8
|
||||
})
|
||||
);
|
||||
issueLogger = new IssueLogger(issue);
|
||||
|
||||
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation();
|
||||
});
|
||||
@@ -35,12 +36,17 @@ describe('IssueLogger', (): void => {
|
||||
});
|
||||
|
||||
describe('info()', (): void => {
|
||||
let message: string;
|
||||
|
||||
let coreInfoSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach((): void => {
|
||||
message = 'dummy-message';
|
||||
issue = new Issue(
|
||||
DefaultProcessorOptions,
|
||||
generateIIssue({
|
||||
number: 8
|
||||
})
|
||||
);
|
||||
issueLogger = new IssueLogger(issue);
|
||||
|
||||
coreInfoSpy = jest.spyOn(core, 'info').mockImplementation();
|
||||
});
|
||||
@@ -56,12 +62,17 @@ describe('IssueLogger', (): void => {
|
||||
});
|
||||
|
||||
describe('error()', (): void => {
|
||||
let message: string;
|
||||
|
||||
let coreErrorSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach((): void => {
|
||||
message = 'dummy-message';
|
||||
issue = new Issue(
|
||||
DefaultProcessorOptions,
|
||||
generateIIssue({
|
||||
number: 8
|
||||
})
|
||||
);
|
||||
issueLogger = new IssueLogger(issue);
|
||||
|
||||
coreErrorSpy = jest.spyOn(core, 'error').mockImplementation();
|
||||
});
|
||||
@@ -75,4 +86,82 @@ describe('IssueLogger', (): void => {
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith('[#8] dummy-message');
|
||||
});
|
||||
});
|
||||
|
||||
it('should prefix the message with the issue number', (): void => {
|
||||
expect.assertions(2);
|
||||
message = 'dummy-message';
|
||||
issue = new Issue(
|
||||
DefaultProcessorOptions,
|
||||
generateIIssue({
|
||||
number: 123
|
||||
})
|
||||
);
|
||||
issueLogger = new IssueLogger(issue);
|
||||
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation();
|
||||
|
||||
issueLogger.warning(message);
|
||||
|
||||
expect(coreWarningSpy).toHaveBeenCalledTimes(1);
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith('[#123] dummy-message');
|
||||
});
|
||||
|
||||
it.each`
|
||||
pull_request | replacement
|
||||
${{key: 'value'}} | ${'pull request'}
|
||||
${{}} | ${'pull request'}
|
||||
${null} | ${'issue'}
|
||||
${undefined} | ${'issue'}
|
||||
`(
|
||||
'should replace the special tokens "$$type" with the corresponding type',
|
||||
({pull_request, replacement}): void => {
|
||||
expect.assertions(2);
|
||||
message = 'The $$type will stale! $$type will soon be closed!';
|
||||
issue = new Issue(
|
||||
DefaultProcessorOptions,
|
||||
generateIIssue({
|
||||
number: 8,
|
||||
pull_request
|
||||
})
|
||||
);
|
||||
issueLogger = new IssueLogger(issue);
|
||||
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation();
|
||||
|
||||
issueLogger.warning(message);
|
||||
|
||||
expect(coreWarningSpy).toHaveBeenCalledTimes(1);
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(
|
||||
`[#8] The ${replacement} will stale! ${replacement} will soon be closed!`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each`
|
||||
pull_request | replacement
|
||||
${{key: 'value'}} | ${'Pull request'}
|
||||
${{}} | ${'Pull request'}
|
||||
${null} | ${'Issue'}
|
||||
${undefined} | ${'Issue'}
|
||||
`(
|
||||
'should replace the special token "$$type" with the corresponding type with first letter as uppercase',
|
||||
({pull_request, replacement}): void => {
|
||||
expect.assertions(2);
|
||||
message = '$$type will stale';
|
||||
issue = new Issue(
|
||||
DefaultProcessorOptions,
|
||||
generateIIssue({
|
||||
number: 8,
|
||||
pull_request
|
||||
})
|
||||
);
|
||||
issueLogger = new IssueLogger(issue);
|
||||
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation();
|
||||
|
||||
issueLogger.warning(message);
|
||||
|
||||
expect(coreWarningSpy).toHaveBeenCalledTimes(1);
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(
|
||||
`[#8] ${replacement} will stale`
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,19 @@ import * as core from '@actions/core';
|
||||
import {Issue} from '../issue';
|
||||
import {Logger} from './logger';
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Each log will prefix the message with the issue number
|
||||
*
|
||||
* @example
|
||||
* warning('No stale') => "[#123] No stale"
|
||||
*
|
||||
* Each log method can have special tokens:
|
||||
* - $$type => will replace this by either "pull request" or "issue" depending of the type of issue
|
||||
*
|
||||
* @example
|
||||
* warning('The $$type will stale') => "The pull request will stale"
|
||||
*/
|
||||
export class IssueLogger implements Logger {
|
||||
private readonly _issue: Issue;
|
||||
|
||||
@@ -10,15 +23,31 @@ export class IssueLogger implements Logger {
|
||||
}
|
||||
|
||||
warning(message: Readonly<string>): void {
|
||||
core.warning(this._prefixWithIssueNumber(message));
|
||||
core.warning(this._format(message));
|
||||
}
|
||||
|
||||
info(message: Readonly<string>): void {
|
||||
core.info(this._prefixWithIssueNumber(message));
|
||||
core.info(this._format(message));
|
||||
}
|
||||
|
||||
error(message: Readonly<string>): void {
|
||||
core.error(this._prefixWithIssueNumber(message));
|
||||
core.error(this._format(message));
|
||||
}
|
||||
|
||||
private _replaceTokens(message: Readonly<string>): string {
|
||||
return this._replaceTypeToken(message);
|
||||
}
|
||||
|
||||
private _replaceTypeToken(message: Readonly<string>): string {
|
||||
return message
|
||||
.replace(
|
||||
/^\$\$type/,
|
||||
this._issue.isPullRequest ? 'Pull request' : 'Issue'
|
||||
)
|
||||
.replace(
|
||||
/\$\$type/g,
|
||||
this._issue.isPullRequest ? 'pull request' : 'issue'
|
||||
);
|
||||
}
|
||||
|
||||
private _prefixWithIssueNumber(message: Readonly<string>): string {
|
||||
@@ -28,4 +57,8 @@ export class IssueLogger implements Logger {
|
||||
private _getIssueNumber(): number {
|
||||
return this._issue.number;
|
||||
}
|
||||
|
||||
private _format(message: Readonly<string>): string {
|
||||
return this._prefixWithIssueNumber(this._replaceTokens(message));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import {DefaultProcessorOptions} from '../../__tests__/constants/default-processor-options';
|
||||
import {generateIIssue} from '../../__tests__/functions/generate-iissue';
|
||||
import {IIssue} from '../interfaces/issue';
|
||||
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||
import {Issue} from './issue';
|
||||
@@ -10,51 +12,8 @@ describe('Milestones', (): void => {
|
||||
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: '',
|
||||
exemptAllMilestones: false,
|
||||
exemptAllIssueMilestones: undefined,
|
||||
exemptAllPrMilestones: undefined
|
||||
};
|
||||
issueInterface = {
|
||||
created_at: '',
|
||||
locked: false,
|
||||
milestone: undefined,
|
||||
number: 0,
|
||||
pull_request: undefined,
|
||||
state: '',
|
||||
title: '',
|
||||
updated_at: '',
|
||||
labels: []
|
||||
};
|
||||
optionsInterface = {...DefaultProcessorOptions};
|
||||
issueInterface = generateIIssue();
|
||||
});
|
||||
|
||||
describe('shouldExemptMilestones()', (): void => {
|
||||
|
||||
@@ -6,8 +6,8 @@ import {Issue} from './issue';
|
||||
type CleanMilestone = string;
|
||||
|
||||
export class Milestones {
|
||||
private static _cleanMilestone(label: Readonly<string>): CleanMilestone {
|
||||
return deburr(label.toLowerCase());
|
||||
private static _cleanMilestone(milestone: Readonly<string>): CleanMilestone {
|
||||
return deburr(milestone.toLowerCase());
|
||||
}
|
||||
|
||||
private readonly _options: IIssuesProcessorOptions;
|
||||
|
||||
3
src/interfaces/assignee.ts
Normal file
3
src/interfaces/assignee.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface IAssignee {
|
||||
login: string;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import {IsoDateString} from '../types/iso-date-string';
|
||||
import {IAssignee} from './assignee';
|
||||
import {ILabel} from './label';
|
||||
import {IMilestone} from './milestone';
|
||||
|
||||
@@ -12,4 +13,5 @@ export interface IIssue {
|
||||
state: string;
|
||||
locked: boolean;
|
||||
milestone: IMilestone | undefined;
|
||||
assignees: IAssignee[];
|
||||
}
|
||||
|
||||
@@ -33,4 +33,10 @@ export interface IIssuesProcessorOptions {
|
||||
exemptAllMilestones: boolean;
|
||||
exemptAllIssueMilestones: boolean | undefined;
|
||||
exemptAllPrMilestones: boolean | undefined;
|
||||
exemptAssignees: string;
|
||||
exemptIssueAssignees: string;
|
||||
exemptPrAssignees: string;
|
||||
exemptAllAssignees: boolean;
|
||||
exemptAllIssueAssignees: boolean | undefined;
|
||||
exemptAllPrAssignees: boolean | undefined;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,13 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
|
||||
exemptPrMilestones: core.getInput('exempt-pr-milestones'),
|
||||
exemptAllMilestones: core.getInput('exempt-all-milestones') === 'true',
|
||||
exemptAllIssueMilestones: _toOptionalBoolean('exempt-all-issue-milestones'),
|
||||
exemptAllPrMilestones: _toOptionalBoolean('exempt-all-pr-milestones')
|
||||
exemptAllPrMilestones: _toOptionalBoolean('exempt-all-pr-milestones'),
|
||||
exemptAssignees: core.getInput('exempt-assignees'),
|
||||
exemptIssueAssignees: core.getInput('exempt-issue-assignees'),
|
||||
exemptPrAssignees: core.getInput('exempt-pr-assignees'),
|
||||
exemptAllAssignees: core.getInput('exempt-all-assignees') === 'true',
|
||||
exemptAllIssueAssignees: _toOptionalBoolean('exempt-all-issue-assignees'),
|
||||
exemptAllPrAssignees: _toOptionalBoolean('exempt-all-pr-assignees')
|
||||
};
|
||||
|
||||
for (const numberInput of [
|
||||
|
||||
Reference in New Issue
Block a user