feature: add dynamic messages support

This commit is contained in:
Sumit Anvekar 2022-12-08 08:06:29 +00:00
parent 65b52aff67
commit c565207b1b
6 changed files with 137 additions and 9 deletions

View File

@ -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'
}
}); });
} }

View File

@ -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
);
});

View File

@ -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 {

View File

@ -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;
}
} }

View File

@ -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'];

View File

@ -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'];