feature: add dynamic messages support
This commit is contained in:
parent
65b52aff67
commit
c565207b1b
|
@ -14,7 +14,8 @@ export function generateIssue(
|
||||||
isClosed = false,
|
isClosed = false,
|
||||||
isLocked = false,
|
isLocked = false,
|
||||||
milestone: string | undefined = undefined,
|
milestone: string | undefined = undefined,
|
||||||
assignees: string[] = []
|
assignees: string[] = [],
|
||||||
|
userLogin: string | undefined = undefined
|
||||||
): Issue {
|
): Issue {
|
||||||
return new Issue(options, {
|
return new Issue(options, {
|
||||||
number: id,
|
number: id,
|
||||||
|
@ -37,6 +38,10 @@ export function generateIssue(
|
||||||
login: assignee,
|
login: assignee,
|
||||||
type: 'User'
|
type: 'User'
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
|
user: {
|
||||||
|
login: userLogin ? userLogin : 'dummy-test-user',
|
||||||
|
type: 'User'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-option
|
||||||
import {IssuesProcessorMock} from './classes/issues-processor-mock';
|
import {IssuesProcessorMock} from './classes/issues-processor-mock';
|
||||||
import {DefaultProcessorOptions} from './constants/default-processor-options';
|
import {DefaultProcessorOptions} from './constants/default-processor-options';
|
||||||
import {generateIssue} from './functions/generate-issue';
|
import {generateIssue} from './functions/generate-issue';
|
||||||
|
import {isPullRequest} from '../src/functions/is-pull-request';
|
||||||
|
|
||||||
test('processing an issue with no label will make it stale and close it, if it is old enough only if days-before-close is set to 0', async () => {
|
test('processing an issue with no label will make it stale and close it, if it is old enough only if days-before-close is set to 0', async () => {
|
||||||
const opts: IIssuesProcessorOptions = {
|
const opts: IIssuesProcessorOptions = {
|
||||||
|
@ -2595,3 +2596,103 @@ test('processing an issue with the "includeOnlyAssigned" option set and no assig
|
||||||
expect(processor.staleIssues).toHaveLength(0);
|
expect(processor.staleIssues).toHaveLength(0);
|
||||||
expect(processor.closedIssues).toHaveLength(0);
|
expect(processor.closedIssues).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('interpolate stale message on prs when there is placeholder', async () => {
|
||||||
|
const opts = {...DefaultProcessorOptions};
|
||||||
|
opts.daysBeforeStale = 5; // stale after 5 days
|
||||||
|
opts.daysBeforeClose = 20; // closes after 25 days
|
||||||
|
opts.stalePrMessage = 'Hello {author}, Please take care of this pr!';
|
||||||
|
const lastUpdate = new Date();
|
||||||
|
lastUpdate.setDate(lastUpdate.getDate() - 10);
|
||||||
|
const loginUser = 'dummy-user';
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
opts,
|
||||||
|
1,
|
||||||
|
'An issue that should be marked stale but not closed.',
|
||||||
|
lastUpdate.toString(),
|
||||||
|
lastUpdate.toString(),
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
[],
|
||||||
|
loginUser
|
||||||
|
)
|
||||||
|
];
|
||||||
|
const processor = new IssuesProcessorMock(
|
||||||
|
opts,
|
||||||
|
async p => (p === 1 ? TestIssueList : []),
|
||||||
|
async () => [],
|
||||||
|
async () => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// for sake of testing, mocking private function
|
||||||
|
const markSpy = jest.spyOn(processor as any, '_markStale');
|
||||||
|
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
// issue should be staled
|
||||||
|
expect(processor.closedIssues).toHaveLength(0);
|
||||||
|
expect(processor.removedLabelIssues).toHaveLength(0);
|
||||||
|
expect(processor.staleIssues).toHaveLength(1);
|
||||||
|
|
||||||
|
// comment should not be created
|
||||||
|
expect(markSpy).toHaveBeenCalledWith(
|
||||||
|
TestIssueList[0],
|
||||||
|
'Hello @dummy-user, Please take care of this pr!',
|
||||||
|
opts.stalePrLabel,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('interpolate stale message on issues when there is placeholder', async () => {
|
||||||
|
const opts = {...DefaultProcessorOptions};
|
||||||
|
opts.daysBeforeStale = 5; // stale after 5 days
|
||||||
|
opts.daysBeforeClose = 20; // closes after 25 days
|
||||||
|
opts.staleIssueMessage = 'Hello {author}, Please take care of this issue!';
|
||||||
|
const lastUpdate = new Date();
|
||||||
|
lastUpdate.setDate(lastUpdate.getDate() - 10);
|
||||||
|
const loginUser = 'dummy-user';
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
opts,
|
||||||
|
1,
|
||||||
|
'An issue that should be marked stale but not closed',
|
||||||
|
lastUpdate.toString(),
|
||||||
|
lastUpdate.toString(),
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
[],
|
||||||
|
loginUser
|
||||||
|
)
|
||||||
|
];
|
||||||
|
const processor = new IssuesProcessorMock(
|
||||||
|
opts,
|
||||||
|
async p => (p === 1 ? TestIssueList : []),
|
||||||
|
async () => [],
|
||||||
|
async () => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// for sake of testing, mocking private function
|
||||||
|
const markSpy = jest.spyOn(processor as any, '_markStale');
|
||||||
|
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
// issue should be staled
|
||||||
|
expect(processor.closedIssues).toHaveLength(0);
|
||||||
|
expect(processor.removedLabelIssues).toHaveLength(0);
|
||||||
|
expect(processor.staleIssues).toHaveLength(1);
|
||||||
|
|
||||||
|
// comment should be created
|
||||||
|
expect(markSpy).toHaveBeenCalledWith(
|
||||||
|
TestIssueList[0],
|
||||||
|
'Hello @dummy-user, Please take care of this issue!',
|
||||||
|
opts.staleIssueLabel,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {ILabel} from '../interfaces/label';
|
||||||
import {IMilestone} from '../interfaces/milestone';
|
import {IMilestone} from '../interfaces/milestone';
|
||||||
import {IsoDateString} from '../types/iso-date-string';
|
import {IsoDateString} from '../types/iso-date-string';
|
||||||
import {Operations} from './operations';
|
import {Operations} from './operations';
|
||||||
|
import {IUser} from '../interfaces/user';
|
||||||
|
|
||||||
export class Issue implements IIssue {
|
export class Issue implements IIssue {
|
||||||
readonly title: string;
|
readonly title: string;
|
||||||
|
@ -23,6 +24,7 @@ export class Issue implements IIssue {
|
||||||
markedStaleThisRun: boolean;
|
markedStaleThisRun: boolean;
|
||||||
operations = new Operations();
|
operations = new Operations();
|
||||||
private readonly _options: IIssuesProcessorOptions;
|
private readonly _options: IIssuesProcessorOptions;
|
||||||
|
readonly user?: IUser | null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
options: Readonly<IIssuesProcessorOptions>,
|
options: Readonly<IIssuesProcessorOptions>,
|
||||||
|
@ -41,6 +43,7 @@ export class Issue implements IIssue {
|
||||||
this.assignees = issue.assignees || [];
|
this.assignees = issue.assignees || [];
|
||||||
this.isStale = isLabeled(this, this.staleLabel);
|
this.isStale = isLabeled(this, this.staleLabel);
|
||||||
this.markedStaleThisRun = false;
|
this.markedStaleThisRun = false;
|
||||||
|
this.user = issue.user
|
||||||
}
|
}
|
||||||
|
|
||||||
get isPullRequest(): boolean {
|
get isPullRequest(): boolean {
|
||||||
|
|
|
@ -190,12 +190,18 @@ export class IssuesProcessor {
|
||||||
);
|
);
|
||||||
|
|
||||||
// calculate string based messages for this issue
|
// calculate string based messages for this issue
|
||||||
const staleMessage: string = issue.isPullRequest
|
const staleMessage: string = this._interpolatePlaceholders(
|
||||||
|
issue,
|
||||||
|
issue.isPullRequest
|
||||||
? this.options.stalePrMessage
|
? this.options.stalePrMessage
|
||||||
: this.options.staleIssueMessage;
|
: this.options.staleIssueMessage
|
||||||
const closeMessage: string = issue.isPullRequest
|
);
|
||||||
|
const closeMessage: string = this._interpolatePlaceholders(
|
||||||
|
issue,
|
||||||
|
issue.isPullRequest
|
||||||
? this.options.closePrMessage
|
? this.options.closePrMessage
|
||||||
: this.options.closeIssueMessage;
|
: this.options.closeIssueMessage
|
||||||
|
);
|
||||||
const staleLabel: string = issue.isPullRequest
|
const staleLabel: string = issue.isPullRequest
|
||||||
? this.options.stalePrLabel
|
? this.options.stalePrLabel
|
||||||
: this.options.staleIssueLabel;
|
: this.options.staleIssueLabel;
|
||||||
|
@ -1227,4 +1233,10 @@ export class IssuesProcessor {
|
||||||
|
|
||||||
return Option.RemoveStaleWhenUpdated;
|
return Option.RemoveStaleWhenUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _interpolatePlaceholders(issue: Issue, message: string) {
|
||||||
|
return issue.user
|
||||||
|
? message.replace('{author}', `@${issue.user?.login}`)
|
||||||
|
: message;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,8 @@ import {Assignee} from './assignee';
|
||||||
import {ILabel} from './label';
|
import {ILabel} from './label';
|
||||||
import {IMilestone} from './milestone';
|
import {IMilestone} from './milestone';
|
||||||
import {components} from '@octokit/openapi-types';
|
import {components} from '@octokit/openapi-types';
|
||||||
|
import {IUser} from './user';
|
||||||
|
|
||||||
export interface IIssue {
|
export interface IIssue {
|
||||||
title: string;
|
title: string;
|
||||||
number: number;
|
number: number;
|
||||||
|
@ -14,6 +16,7 @@ export interface IIssue {
|
||||||
locked: boolean;
|
locked: boolean;
|
||||||
milestone?: IMilestone | null;
|
milestone?: IMilestone | null;
|
||||||
assignees?: Assignee[] | null;
|
assignees?: Assignee[] | null;
|
||||||
|
user?: IUser | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OctokitIssue = components['schemas']['issue'];
|
export type OctokitIssue = components['schemas']['issue'];
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
import {components} from '@octokit/openapi-types';
|
||||||
|
|
||||||
export interface IUser {
|
export interface IUser {
|
||||||
type: string | 'User';
|
type: string | 'User';
|
||||||
login: string;
|
login: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OctokitUser = components['schemas']['nullable-simple-user'];
|
Loading…
Reference in New Issue