🎨 Add message grouping (#483)
* 🎨 add message grouping * ⚗️ try output * 🔥 remove unnecessary code * ⬆️ bump deps * 🎨 build project * ⬆️ bump deps * 🎨 formatting code * ⬇️ revert bumps * 🎨 using logger service * 🎨 using package lock version 1 * 🎨 add engines keyword * ♻️ create processIssue method * ✨ add grouping method * 🎨 build project * 🎨 update engine declaration * 💚 fix merge conflicts
This commit is contained in:
parent
5f6f311ca6
commit
52f5648db3
|
@ -1112,14 +1112,12 @@ class IssuesProcessorBuilder {
|
||||||
|
|
||||||
issues(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
issues(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
||||||
this.issuesOrPrs(
|
this.issuesOrPrs(
|
||||||
issues.map(
|
issues.map((issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
||||||
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
return {
|
||||||
return {
|
...issue,
|
||||||
...issue,
|
pull_request: null
|
||||||
pull_request: null
|
};
|
||||||
};
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -1127,14 +1125,12 @@ class IssuesProcessorBuilder {
|
||||||
|
|
||||||
prs(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
prs(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
||||||
this.issuesOrPrs(
|
this.issuesOrPrs(
|
||||||
issues.map(
|
issues.map((issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
||||||
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
return {
|
||||||
return {
|
...issue,
|
||||||
...issue,
|
pull_request: {key: 'value'}
|
||||||
pull_request: {key: 'value'}
|
};
|
||||||
};
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -32,12 +32,10 @@ export function generateIssue(
|
||||||
title: milestone
|
title: milestone
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
assignees: assignees.map(
|
assignees: assignees.map((assignee: Readonly<string>): IAssignee => {
|
||||||
(assignee: Readonly<string>): IAssignee => {
|
return {
|
||||||
return {
|
login: assignee
|
||||||
login: assignee
|
};
|
||||||
};
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1112,14 +1112,12 @@ class IssuesProcessorBuilder {
|
||||||
|
|
||||||
issues(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
issues(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
||||||
this.issuesOrPrs(
|
this.issuesOrPrs(
|
||||||
issues.map(
|
issues.map((issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
||||||
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
return {
|
||||||
return {
|
...issue,
|
||||||
...issue,
|
pull_request: null
|
||||||
pull_request: null
|
};
|
||||||
};
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -1127,14 +1125,12 @@ class IssuesProcessorBuilder {
|
||||||
|
|
||||||
prs(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
prs(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
||||||
this.issuesOrPrs(
|
this.issuesOrPrs(
|
||||||
issues.map(
|
issues.map((issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
||||||
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
return {
|
||||||
return {
|
...issue,
|
||||||
...issue,
|
pull_request: {key: 'value'}
|
||||||
pull_request: {key: 'value'}
|
};
|
||||||
};
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -188,18 +188,16 @@ class SUT {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setTestIssueList(): SUT {
|
private _setTestIssueList(): SUT {
|
||||||
this._testIssueList = this._sutIssues.map(
|
this._testIssueList = this._sutIssues.map((sutIssue: SUTIssue): Issue => {
|
||||||
(sutIssue: SUTIssue): Issue => {
|
return generateIssue(
|
||||||
return generateIssue(
|
this._opts,
|
||||||
this._opts,
|
1,
|
||||||
1,
|
'My first issue',
|
||||||
'My first issue',
|
sutIssue.updatedAt,
|
||||||
sutIssue.updatedAt,
|
sutIssue.updatedAt,
|
||||||
sutIssue.updatedAt,
|
false
|
||||||
false
|
);
|
||||||
);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -464,14 +464,12 @@ class IssuesProcessorBuilder {
|
||||||
|
|
||||||
issues(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
issues(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
||||||
this.issuesOrPrs(
|
this.issuesOrPrs(
|
||||||
issues.map(
|
issues.map((issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
||||||
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
return {
|
||||||
return {
|
...issue,
|
||||||
...issue,
|
pull_request: null
|
||||||
pull_request: null
|
};
|
||||||
};
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -479,27 +477,23 @@ class IssuesProcessorBuilder {
|
||||||
|
|
||||||
staleIssues(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
staleIssues(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
||||||
this.issues(
|
this.issues(
|
||||||
issues.map(
|
issues.map((issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
||||||
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
return {
|
||||||
return {
|
...issue,
|
||||||
...issue,
|
updated_at: '2020-01-01T17:00:00Z',
|
||||||
updated_at: '2020-01-01T17:00:00Z',
|
created_at: '2020-01-01T17:00:00Z',
|
||||||
created_at: '2020-01-01T17:00:00Z',
|
labels: issue.labels?.map((label: Readonly<ILabel>): ILabel => {
|
||||||
labels: issue.labels?.map(
|
return {
|
||||||
(label: Readonly<ILabel>): ILabel => {
|
...label,
|
||||||
return {
|
name: 'Stale'
|
||||||
...label,
|
};
|
||||||
name: 'Stale'
|
}) ?? [
|
||||||
};
|
{
|
||||||
}
|
name: 'Stale'
|
||||||
) ?? [
|
}
|
||||||
{
|
]
|
||||||
name: 'Stale'
|
};
|
||||||
}
|
})
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -507,14 +501,12 @@ class IssuesProcessorBuilder {
|
||||||
|
|
||||||
prs(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
prs(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
||||||
this.issuesOrPrs(
|
this.issuesOrPrs(
|
||||||
issues.map(
|
issues.map((issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
||||||
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
return {
|
||||||
return {
|
...issue,
|
||||||
...issue,
|
pull_request: {key: 'value'}
|
||||||
pull_request: {key: 'value'}
|
};
|
||||||
};
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -522,27 +514,23 @@ class IssuesProcessorBuilder {
|
||||||
|
|
||||||
stalePrs(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
stalePrs(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
|
||||||
this.prs(
|
this.prs(
|
||||||
issues.map(
|
issues.map((issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
||||||
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
|
return {
|
||||||
return {
|
...issue,
|
||||||
...issue,
|
updated_at: '2020-01-01T17:00:00Z',
|
||||||
updated_at: '2020-01-01T17:00:00Z',
|
created_at: '2020-01-01T17:00:00Z',
|
||||||
created_at: '2020-01-01T17:00:00Z',
|
labels: issue.labels?.map((label: Readonly<ILabel>): ILabel => {
|
||||||
labels: issue.labels?.map(
|
return {
|
||||||
(label: Readonly<ILabel>): ILabel => {
|
...label,
|
||||||
return {
|
name: 'Stale'
|
||||||
...label,
|
};
|
||||||
name: 'Stale'
|
}) ?? [
|
||||||
};
|
{
|
||||||
}
|
name: 'Stale'
|
||||||
) ?? [
|
}
|
||||||
{
|
]
|
||||||
name: 'Stale'
|
};
|
||||||
}
|
})
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -286,7 +286,7 @@ class IssuesProcessor {
|
||||||
return issue.isPullRequest ? option_1.Option.ClosePrLabel : option_1.Option.CloseIssueLabel;
|
return issue.isPullRequest ? option_1.Option.ClosePrLabel : option_1.Option.CloseIssueLabel;
|
||||||
}
|
}
|
||||||
processIssues(page = 1) {
|
processIssues(page = 1) {
|
||||||
var _a, _b, _c;
|
var _a, _b;
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
// get the next batch of issues
|
// get the next batch of issues
|
||||||
const issues = yield this.getIssues(page);
|
const issues = yield this.getIssues(page);
|
||||||
|
@ -305,159 +305,14 @@ class IssuesProcessor {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||||
(_b = this._statistics) === null || _b === void 0 ? void 0 : _b.incrementProcessedItemsCount(issue);
|
yield issueLogger.grouping(`$$type #${issue.number}`, () => __awaiter(this, void 0, void 0, function* () {
|
||||||
issueLogger.info(`Found this $$type last updated at: ${logger_service_1.LoggerService.cyan(issue.updated_at)}`);
|
yield this.processIssue(issue, actor);
|
||||||
// calculate string based messages for this issue
|
}));
|
||||||
const staleMessage = issue.isPullRequest
|
|
||||||
? this.options.stalePrMessage
|
|
||||||
: this.options.staleIssueMessage;
|
|
||||||
const closeMessage = issue.isPullRequest
|
|
||||||
? this.options.closePrMessage
|
|
||||||
: this.options.closeIssueMessage;
|
|
||||||
const staleLabel = issue.isPullRequest
|
|
||||||
? this.options.stalePrLabel
|
|
||||||
: this.options.staleIssueLabel;
|
|
||||||
const closeLabel = issue.isPullRequest
|
|
||||||
? this.options.closePrLabel
|
|
||||||
: this.options.closeIssueLabel;
|
|
||||||
const skipMessage = issue.isPullRequest
|
|
||||||
? this.options.stalePrMessage.length === 0
|
|
||||||
: this.options.staleIssueMessage.length === 0;
|
|
||||||
const daysBeforeStale = issue.isPullRequest
|
|
||||||
? this._getDaysBeforePrStale()
|
|
||||||
: this._getDaysBeforeIssueStale();
|
|
||||||
const onlyLabels = words_to_list_1.wordsToList(this._getOnlyLabels(issue));
|
|
||||||
if (onlyLabels.length > 0) {
|
|
||||||
issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.OnlyLabels)} was specified to only process issues and pull requests with all those labels (${logger_service_1.LoggerService.cyan(onlyLabels.length)})`);
|
|
||||||
const hasAllWhitelistedLabels = onlyLabels.every((label) => {
|
|
||||||
return is_labeled_1.isLabeled(issue, label);
|
|
||||||
});
|
|
||||||
if (!hasAllWhitelistedLabels) {
|
|
||||||
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Skipping this $$type because it doesn't have all the required labels`);
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process issues without all of the required labels
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
issueLogger.info(logger_service_1.LoggerService.white('├──'), `All the required labels are present on this $$type`);
|
|
||||||
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Continuing the process for this $$type`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.OnlyLabels)} was not specified`);
|
|
||||||
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Continuing the process for this $$type`);
|
|
||||||
}
|
|
||||||
issueLogger.info(`Days before $$type stale: ${logger_service_1.LoggerService.cyan(daysBeforeStale)}`);
|
|
||||||
const shouldMarkAsStale = should_mark_when_stale_1.shouldMarkWhenStale(daysBeforeStale);
|
|
||||||
if (issue.state === 'closed') {
|
|
||||||
issueLogger.info(`Skipping this $$type because it is closed`);
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process closed issues
|
|
||||||
}
|
|
||||||
if (issue.locked) {
|
|
||||||
issueLogger.info(`Skipping this $$type because it is locked`);
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process locked issues
|
|
||||||
}
|
|
||||||
// Try to remove the close label when not close/locked issue or PR
|
|
||||||
yield this._removeCloseLabel(issue, closeLabel);
|
|
||||||
if (this.options.startDate) {
|
|
||||||
const startDate = new Date(this.options.startDate);
|
|
||||||
const createdAt = new Date(issue.created_at);
|
|
||||||
issueLogger.info(`A start date was specified for the ${get_humanized_date_1.getHumanizedDate(startDate)} (${logger_service_1.LoggerService.cyan(this.options.startDate)})`);
|
|
||||||
// Expecting that GitHub will always set a creation date on the issues and PRs
|
|
||||||
// But you never know!
|
|
||||||
if (!is_valid_date_1.isValidDate(createdAt)) {
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
core.setFailed(new Error(`Invalid issue field: "created_at". Expected a valid date`));
|
|
||||||
}
|
|
||||||
issueLogger.info(`$$type created the ${get_humanized_date_1.getHumanizedDate(createdAt)} (${logger_service_1.LoggerService.cyan(issue.created_at)})`);
|
|
||||||
if (!is_date_more_recent_than_1.isDateMoreRecentThan(createdAt, startDate)) {
|
|
||||||
issueLogger.info(`Skipping this $$type because it was created before the specified start date`);
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process issues which were created before the start date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (issue.isStale) {
|
|
||||||
issueLogger.info(`This $$type has a stale label`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
issueLogger.info(`This $$type hasn't a stale label`);
|
|
||||||
}
|
|
||||||
const exemptLabels = words_to_list_1.wordsToList(issue.isPullRequest
|
|
||||||
? this.options.exemptPrLabels
|
|
||||||
: this.options.exemptIssueLabels);
|
|
||||||
if (exemptLabels.some((exemptLabel) => is_labeled_1.isLabeled(issue, exemptLabel))) {
|
|
||||||
if (issue.isStale) {
|
|
||||||
issueLogger.info(`An exempt label was added after the stale label.`);
|
|
||||||
yield this._removeStaleLabel(issue, staleLabel);
|
|
||||||
}
|
|
||||||
issueLogger.info(`Skipping this $$type because it has an exempt label`);
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process exempt issues
|
|
||||||
}
|
|
||||||
const anyOfLabels = words_to_list_1.wordsToList(this._getAnyOfLabels(issue));
|
|
||||||
if (anyOfLabels.length > 0) {
|
|
||||||
issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.AnyOfLabels)} was specified to only process the issues and pull requests with one of those labels (${logger_service_1.LoggerService.cyan(anyOfLabels.length)})`);
|
|
||||||
const hasOneOfWhitelistedLabels = anyOfLabels.some((label) => {
|
|
||||||
return is_labeled_1.isLabeled(issue, label);
|
|
||||||
});
|
|
||||||
if (!hasOneOfWhitelistedLabels) {
|
|
||||||
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Skipping this $$type because it doesn't have one of the required labels`);
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process issues without any of the required labels
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
issueLogger.info(logger_service_1.LoggerService.white('├──'), `One of the required labels is present on this $$type`);
|
|
||||||
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Continuing the process for this $$type`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.AnyOfLabels)} was not specified`);
|
|
||||||
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Continuing the process for this $$type`);
|
|
||||||
}
|
|
||||||
const milestones = new milestones_1.Milestones(this.options, issue);
|
|
||||||
if (milestones.shouldExemptMilestones()) {
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process exempt milestones
|
|
||||||
}
|
|
||||||
const assignees = new assignees_1.Assignees(this.options, issue);
|
|
||||||
if (assignees.shouldExemptAssignees()) {
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process exempt assignees
|
|
||||||
}
|
|
||||||
// Should this issue be marked stale?
|
|
||||||
const shouldBeStale = !IssuesProcessor._updatedSince(issue.updated_at, daysBeforeStale);
|
|
||||||
// Determine if this issue needs to be marked stale first
|
|
||||||
if (!issue.isStale) {
|
|
||||||
issueLogger.info(`This $$type is not stale`);
|
|
||||||
const updatedAtDate = new Date(issue.updated_at);
|
|
||||||
if (shouldBeStale) {
|
|
||||||
issueLogger.info(`This $$type should be stale based on the last update date the ${get_humanized_date_1.getHumanizedDate(updatedAtDate)} (${logger_service_1.LoggerService.cyan(issue.updated_at)})`);
|
|
||||||
if (shouldMarkAsStale) {
|
|
||||||
issueLogger.info(`This $$type should be marked as stale based on the option ${issueLogger.createOptionLink(this._getDaysBeforeStaleUsedOptionName(issue))} (${logger_service_1.LoggerService.cyan(daysBeforeStale)})`);
|
|
||||||
yield this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
|
||||||
issue.isStale = true; // This issue is now considered stale
|
|
||||||
issueLogger.info(`This $$type is now stale`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
issueLogger.info(`This $$type should not be marked as stale based on the option ${issueLogger.createOptionLink(this._getDaysBeforeStaleUsedOptionName(issue))} (${logger_service_1.LoggerService.cyan(daysBeforeStale)})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
issueLogger.info(`This $$type should not be stale based on the last update date the ${get_humanized_date_1.getHumanizedDate(updatedAtDate)} (${logger_service_1.LoggerService.cyan(issue.updated_at)})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Process the issue if it was marked stale
|
|
||||||
if (issue.isStale) {
|
|
||||||
issueLogger.info(`This $$type is already stale`);
|
|
||||||
yield this._processStaleIssue(issue, staleLabel, actor, closeMessage, closeLabel);
|
|
||||||
}
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
}
|
}
|
||||||
if (!this.operations.hasRemainingOperations()) {
|
if (!this.operations.hasRemainingOperations()) {
|
||||||
this._logger.warning(logger_service_1.LoggerService.yellowBright(`No more operations left! Exiting...`));
|
this._logger.warning(logger_service_1.LoggerService.yellowBright(`No more operations left! Exiting...`));
|
||||||
this._logger.warning(`${logger_service_1.LoggerService.yellowBright('If you think that not enough issues were processed you could try to increase the quantity related to the')} ${this._logger.createOptionLink(option_1.Option.OperationsPerRun)} ${logger_service_1.LoggerService.yellowBright('option which is currently set to')} ${logger_service_1.LoggerService.cyan(this.options.operationsPerRun)}`);
|
this._logger.warning(`${logger_service_1.LoggerService.yellowBright('If you think that not enough issues were processed you could try to increase the quantity related to the')} ${this._logger.createOptionLink(option_1.Option.OperationsPerRun)} ${logger_service_1.LoggerService.yellowBright('option which is currently set to')} ${logger_service_1.LoggerService.cyan(this.options.operationsPerRun)}`);
|
||||||
(_c = this._statistics) === null || _c === void 0 ? void 0 : _c.setOperationsCount(this.operations.getConsumedOperationsCount()).logStats();
|
(_b = this._statistics) === null || _b === void 0 ? void 0 : _b.setOperationsCount(this.operations.getConsumedOperationsCount()).logStats();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
this._logger.info(`${logger_service_1.LoggerService.green('Batch')} ${logger_service_1.LoggerService.cyan(`#${page}`)} ${logger_service_1.LoggerService.green('processed.')}`);
|
this._logger.info(`${logger_service_1.LoggerService.green('Batch')} ${logger_service_1.LoggerService.cyan(`#${page}`)} ${logger_service_1.LoggerService.green('processed.')}`);
|
||||||
|
@ -465,6 +320,160 @@ class IssuesProcessor {
|
||||||
return this.processIssues(page + 1);
|
return this.processIssues(page + 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
processIssue(issue, actor) {
|
||||||
|
var _a;
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
(_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementProcessedItemsCount(issue);
|
||||||
|
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||||
|
issueLogger.info(`Found this $$type last updated at: ${logger_service_1.LoggerService.cyan(issue.updated_at)}`);
|
||||||
|
// calculate string based messages for this issue
|
||||||
|
const staleMessage = issue.isPullRequest
|
||||||
|
? this.options.stalePrMessage
|
||||||
|
: this.options.staleIssueMessage;
|
||||||
|
const closeMessage = issue.isPullRequest
|
||||||
|
? this.options.closePrMessage
|
||||||
|
: this.options.closeIssueMessage;
|
||||||
|
const staleLabel = issue.isPullRequest
|
||||||
|
? this.options.stalePrLabel
|
||||||
|
: this.options.staleIssueLabel;
|
||||||
|
const closeLabel = issue.isPullRequest
|
||||||
|
? this.options.closePrLabel
|
||||||
|
: this.options.closeIssueLabel;
|
||||||
|
const skipMessage = issue.isPullRequest
|
||||||
|
? this.options.stalePrMessage.length === 0
|
||||||
|
: this.options.staleIssueMessage.length === 0;
|
||||||
|
const daysBeforeStale = issue.isPullRequest
|
||||||
|
? this._getDaysBeforePrStale()
|
||||||
|
: this._getDaysBeforeIssueStale();
|
||||||
|
const onlyLabels = words_to_list_1.wordsToList(this._getOnlyLabels(issue));
|
||||||
|
if (onlyLabels.length > 0) {
|
||||||
|
issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.OnlyLabels)} was specified to only process issues and pull requests with all those labels (${logger_service_1.LoggerService.cyan(onlyLabels.length)})`);
|
||||||
|
const hasAllWhitelistedLabels = onlyLabels.every((label) => {
|
||||||
|
return is_labeled_1.isLabeled(issue, label);
|
||||||
|
});
|
||||||
|
if (!hasAllWhitelistedLabels) {
|
||||||
|
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Skipping this $$type because it doesn't have all the required labels`);
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process issues without all of the required labels
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
issueLogger.info(logger_service_1.LoggerService.white('├──'), `All the required labels are present on this $$type`);
|
||||||
|
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Continuing the process for this $$type`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.OnlyLabels)} was not specified`);
|
||||||
|
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Continuing the process for this $$type`);
|
||||||
|
}
|
||||||
|
issueLogger.info(`Days before $$type stale: ${logger_service_1.LoggerService.cyan(daysBeforeStale)}`);
|
||||||
|
const shouldMarkAsStale = should_mark_when_stale_1.shouldMarkWhenStale(daysBeforeStale);
|
||||||
|
if (issue.state === 'closed') {
|
||||||
|
issueLogger.info(`Skipping this $$type because it is closed`);
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process closed issues
|
||||||
|
}
|
||||||
|
if (issue.locked) {
|
||||||
|
issueLogger.info(`Skipping this $$type because it is locked`);
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process locked issues
|
||||||
|
}
|
||||||
|
// Try to remove the close label when not close/locked issue or PR
|
||||||
|
yield this._removeCloseLabel(issue, closeLabel);
|
||||||
|
if (this.options.startDate) {
|
||||||
|
const startDate = new Date(this.options.startDate);
|
||||||
|
const createdAt = new Date(issue.created_at);
|
||||||
|
issueLogger.info(`A start date was specified for the ${get_humanized_date_1.getHumanizedDate(startDate)} (${logger_service_1.LoggerService.cyan(this.options.startDate)})`);
|
||||||
|
// Expecting that GitHub will always set a creation date on the issues and PRs
|
||||||
|
// But you never know!
|
||||||
|
if (!is_valid_date_1.isValidDate(createdAt)) {
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
core.setFailed(new Error(`Invalid issue field: "created_at". Expected a valid date`));
|
||||||
|
}
|
||||||
|
issueLogger.info(`$$type created the ${get_humanized_date_1.getHumanizedDate(createdAt)} (${logger_service_1.LoggerService.cyan(issue.created_at)})`);
|
||||||
|
if (!is_date_more_recent_than_1.isDateMoreRecentThan(createdAt, startDate)) {
|
||||||
|
issueLogger.info(`Skipping this $$type because it was created before the specified start date`);
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process issues which were created before the start date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (issue.isStale) {
|
||||||
|
issueLogger.info(`This $$type has a stale label`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
issueLogger.info(`This $$type hasn't a stale label`);
|
||||||
|
}
|
||||||
|
const exemptLabels = words_to_list_1.wordsToList(issue.isPullRequest
|
||||||
|
? this.options.exemptPrLabels
|
||||||
|
: this.options.exemptIssueLabels);
|
||||||
|
if (exemptLabels.some((exemptLabel) => is_labeled_1.isLabeled(issue, exemptLabel))) {
|
||||||
|
if (issue.isStale) {
|
||||||
|
issueLogger.info(`An exempt label was added after the stale label.`);
|
||||||
|
yield this._removeStaleLabel(issue, staleLabel);
|
||||||
|
}
|
||||||
|
issueLogger.info(`Skipping this $$type because it has an exempt label`);
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process exempt issues
|
||||||
|
}
|
||||||
|
const anyOfLabels = words_to_list_1.wordsToList(this._getAnyOfLabels(issue));
|
||||||
|
if (anyOfLabels.length > 0) {
|
||||||
|
issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.AnyOfLabels)} was specified to only process the issues and pull requests with one of those labels (${logger_service_1.LoggerService.cyan(anyOfLabels.length)})`);
|
||||||
|
const hasOneOfWhitelistedLabels = anyOfLabels.some((label) => {
|
||||||
|
return is_labeled_1.isLabeled(issue, label);
|
||||||
|
});
|
||||||
|
if (!hasOneOfWhitelistedLabels) {
|
||||||
|
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Skipping this $$type because it doesn't have one of the required labels`);
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process issues without any of the required labels
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
issueLogger.info(logger_service_1.LoggerService.white('├──'), `One of the required labels is present on this $$type`);
|
||||||
|
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Continuing the process for this $$type`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.AnyOfLabels)} was not specified`);
|
||||||
|
issueLogger.info(logger_service_1.LoggerService.white('└──'), `Continuing the process for this $$type`);
|
||||||
|
}
|
||||||
|
const milestones = new milestones_1.Milestones(this.options, issue);
|
||||||
|
if (milestones.shouldExemptMilestones()) {
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process exempt milestones
|
||||||
|
}
|
||||||
|
const assignees = new assignees_1.Assignees(this.options, issue);
|
||||||
|
if (assignees.shouldExemptAssignees()) {
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process exempt assignees
|
||||||
|
}
|
||||||
|
// Should this issue be marked stale?
|
||||||
|
const shouldBeStale = !IssuesProcessor._updatedSince(issue.updated_at, daysBeforeStale);
|
||||||
|
// Determine if this issue needs to be marked stale first
|
||||||
|
if (!issue.isStale) {
|
||||||
|
issueLogger.info(`This $$type is not stale`);
|
||||||
|
const updatedAtDate = new Date(issue.updated_at);
|
||||||
|
if (shouldBeStale) {
|
||||||
|
issueLogger.info(`This $$type should be stale based on the last update date the ${get_humanized_date_1.getHumanizedDate(updatedAtDate)} (${logger_service_1.LoggerService.cyan(issue.updated_at)})`);
|
||||||
|
if (shouldMarkAsStale) {
|
||||||
|
issueLogger.info(`This $$type should be marked as stale based on the option ${issueLogger.createOptionLink(this._getDaysBeforeStaleUsedOptionName(issue))} (${logger_service_1.LoggerService.cyan(daysBeforeStale)})`);
|
||||||
|
yield this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
||||||
|
issue.isStale = true; // This issue is now considered stale
|
||||||
|
issueLogger.info(`This $$type is now stale`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
issueLogger.info(`This $$type should not be marked as stale based on the option ${issueLogger.createOptionLink(this._getDaysBeforeStaleUsedOptionName(issue))} (${logger_service_1.LoggerService.cyan(daysBeforeStale)})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
issueLogger.info(`This $$type should not be stale based on the last update date the ${get_humanized_date_1.getHumanizedDate(updatedAtDate)} (${logger_service_1.LoggerService.cyan(issue.updated_at)})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Process the issue if it was marked stale
|
||||||
|
if (issue.isStale) {
|
||||||
|
issueLogger.info(`This $$type is already stale`);
|
||||||
|
yield this._processStaleIssue(issue, staleLabel, actor, closeMessage, closeLabel);
|
||||||
|
}
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
});
|
||||||
|
}
|
||||||
// Grab comments for an issue since a given date
|
// Grab comments for an issue since a given date
|
||||||
listIssueComments(issueNumber, sinceDate) {
|
listIssueComments(issueNumber, sinceDate) {
|
||||||
var _a;
|
var _a;
|
||||||
|
@ -913,10 +922,19 @@ exports.IssuesProcessor = IssuesProcessor;
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 2984:
|
/***/ 2984:
|
||||||
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.IssueLogger = void 0;
|
exports.IssueLogger = void 0;
|
||||||
const logger_1 = __nccwpck_require__(6212);
|
const logger_1 = __nccwpck_require__(6212);
|
||||||
|
@ -948,6 +966,14 @@ class IssueLogger extends logger_1.Logger {
|
||||||
error(...message) {
|
error(...message) {
|
||||||
super.error(this._format(...message));
|
super.error(this._format(...message));
|
||||||
}
|
}
|
||||||
|
grouping(message, fn) {
|
||||||
|
const _super = Object.create(null, {
|
||||||
|
grouping: { get: () => super.grouping }
|
||||||
|
});
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return _super.grouping.call(this, this._format(message), fn);
|
||||||
|
});
|
||||||
|
}
|
||||||
_replaceTokens(message) {
|
_replaceTokens(message) {
|
||||||
return this._replaceTypeToken(message);
|
return this._replaceTypeToken(message);
|
||||||
}
|
}
|
||||||
|
@ -1006,6 +1032,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
__setModuleDefault(result, mod);
|
__setModuleDefault(result, mod);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
|
@ -1024,6 +1059,11 @@ class Logger {
|
||||||
error(...message) {
|
error(...message) {
|
||||||
core.error(logger_service_1.LoggerService.whiteBright(message.join(' ')));
|
core.error(logger_service_1.LoggerService.whiteBright(message.join(' ')));
|
||||||
}
|
}
|
||||||
|
grouping(message, fn) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return core.group(logger_service_1.LoggerService.whiteBright(message), fn);
|
||||||
|
});
|
||||||
|
}
|
||||||
createLink(name, link) {
|
createLink(name, link) {
|
||||||
return terminal_link_1.default(name, link);
|
return terminal_link_1.default(name, link);
|
||||||
}
|
}
|
||||||
|
@ -8897,4 +8937,4 @@ module.exports = require("zlib");;
|
||||||
/******/ // Load entry module and return exports
|
/******/ // Load entry module and return exports
|
||||||
/******/ return __nccwpck_require__(3109);
|
/******/ return __nccwpck_require__(3109);
|
||||||
/******/ })()
|
/******/ })()
|
||||||
;
|
;
|
|
@ -30,6 +30,10 @@
|
||||||
"node",
|
"node",
|
||||||
"stale"
|
"stale"
|
||||||
],
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "12",
|
||||||
|
"npm": "6"
|
||||||
|
},
|
||||||
"author": "GitHub",
|
"author": "GitHub",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -37,7 +37,8 @@ export class IssuesProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _endIssueProcessing(issue: Issue): void {
|
private static _endIssueProcessing(issue: Issue): void {
|
||||||
const consumedOperationsCount: number = issue.operations.getConsumedOperationsCount();
|
const consumedOperationsCount: number =
|
||||||
|
issue.operations.getConsumedOperationsCount();
|
||||||
|
|
||||||
if (consumedOperationsCount > 0) {
|
if (consumedOperationsCount > 0) {
|
||||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
@ -133,281 +134,9 @@ export class IssuesProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
this._statistics?.incrementProcessedItemsCount(issue);
|
await issueLogger.grouping(`$$type #${issue.number}`, async () => {
|
||||||
|
await this.processIssue(issue, actor);
|
||||||
issueLogger.info(
|
});
|
||||||
`Found this $$type last updated at: ${LoggerService.cyan(
|
|
||||||
issue.updated_at
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// calculate string based messages for this issue
|
|
||||||
const staleMessage: string = issue.isPullRequest
|
|
||||||
? this.options.stalePrMessage
|
|
||||||
: this.options.staleIssueMessage;
|
|
||||||
const closeMessage: string = issue.isPullRequest
|
|
||||||
? this.options.closePrMessage
|
|
||||||
: this.options.closeIssueMessage;
|
|
||||||
const staleLabel: string = issue.isPullRequest
|
|
||||||
? this.options.stalePrLabel
|
|
||||||
: this.options.staleIssueLabel;
|
|
||||||
const closeLabel: string = issue.isPullRequest
|
|
||||||
? this.options.closePrLabel
|
|
||||||
: this.options.closeIssueLabel;
|
|
||||||
const skipMessage = issue.isPullRequest
|
|
||||||
? this.options.stalePrMessage.length === 0
|
|
||||||
: this.options.staleIssueMessage.length === 0;
|
|
||||||
const daysBeforeStale: number = issue.isPullRequest
|
|
||||||
? this._getDaysBeforePrStale()
|
|
||||||
: this._getDaysBeforeIssueStale();
|
|
||||||
const onlyLabels: string[] = wordsToList(this._getOnlyLabels(issue));
|
|
||||||
|
|
||||||
if (onlyLabels.length > 0) {
|
|
||||||
issueLogger.info(
|
|
||||||
`The option ${issueLogger.createOptionLink(
|
|
||||||
Option.OnlyLabels
|
|
||||||
)} was specified to only process issues and pull requests with all those labels (${LoggerService.cyan(
|
|
||||||
onlyLabels.length
|
|
||||||
)})`
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasAllWhitelistedLabels: boolean = onlyLabels.every(
|
|
||||||
(label: Readonly<string>): boolean => {
|
|
||||||
return isLabeled(issue, label);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!hasAllWhitelistedLabels) {
|
|
||||||
issueLogger.info(
|
|
||||||
LoggerService.white('└──'),
|
|
||||||
`Skipping this $$type because it doesn't have all the required labels`
|
|
||||||
);
|
|
||||||
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process issues without all of the required labels
|
|
||||||
} else {
|
|
||||||
issueLogger.info(
|
|
||||||
LoggerService.white('├──'),
|
|
||||||
`All the required labels are present on this $$type`
|
|
||||||
);
|
|
||||||
issueLogger.info(
|
|
||||||
LoggerService.white('└──'),
|
|
||||||
`Continuing the process for this $$type`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
issueLogger.info(
|
|
||||||
`The option ${issueLogger.createOptionLink(
|
|
||||||
Option.OnlyLabels
|
|
||||||
)} was not specified`
|
|
||||||
);
|
|
||||||
issueLogger.info(
|
|
||||||
LoggerService.white('└──'),
|
|
||||||
`Continuing the process for this $$type`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
issueLogger.info(
|
|
||||||
`Days before $$type stale: ${LoggerService.cyan(daysBeforeStale)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const shouldMarkAsStale: boolean = shouldMarkWhenStale(daysBeforeStale);
|
|
||||||
|
|
||||||
if (issue.state === 'closed') {
|
|
||||||
issueLogger.info(`Skipping this $$type because it is closed`);
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process closed issues
|
|
||||||
}
|
|
||||||
|
|
||||||
if (issue.locked) {
|
|
||||||
issueLogger.info(`Skipping this $$type because it is locked`);
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process locked issues
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to remove the close label when not close/locked issue or PR
|
|
||||||
await this._removeCloseLabel(issue, closeLabel);
|
|
||||||
|
|
||||||
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
|
|
||||||
)} (${LoggerService.cyan(this.options.startDate)})`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Expecting that GitHub will always set a creation date on the issues and PRs
|
|
||||||
// But you never know!
|
|
||||||
if (!isValidDate(createdAt)) {
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
core.setFailed(
|
|
||||||
new Error(
|
|
||||||
`Invalid issue field: "created_at". Expected a valid date`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
issueLogger.info(
|
|
||||||
`$$type created the ${getHumanizedDate(
|
|
||||||
createdAt
|
|
||||||
)} (${LoggerService.cyan(issue.created_at)})`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isDateMoreRecentThan(createdAt, startDate)) {
|
|
||||||
issueLogger.info(
|
|
||||||
`Skipping this $$type because it was created before the specified start date`
|
|
||||||
);
|
|
||||||
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process issues which were created before the start date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (issue.isStale) {
|
|
||||||
issueLogger.info(`This $$type has a stale label`);
|
|
||||||
} else {
|
|
||||||
issueLogger.info(`This $$type hasn't a stale label`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const exemptLabels: string[] = wordsToList(
|
|
||||||
issue.isPullRequest
|
|
||||||
? this.options.exemptPrLabels
|
|
||||||
: this.options.exemptIssueLabels
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
exemptLabels.some((exemptLabel: Readonly<string>): boolean =>
|
|
||||||
isLabeled(issue, exemptLabel)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (issue.isStale) {
|
|
||||||
issueLogger.info(`An exempt label was added after the stale label.`);
|
|
||||||
await this._removeStaleLabel(issue, staleLabel);
|
|
||||||
}
|
|
||||||
|
|
||||||
issueLogger.info(`Skipping this $$type because it has an exempt label`);
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process exempt issues
|
|
||||||
}
|
|
||||||
|
|
||||||
const anyOfLabels: string[] = wordsToList(this._getAnyOfLabels(issue));
|
|
||||||
|
|
||||||
if (anyOfLabels.length > 0) {
|
|
||||||
issueLogger.info(
|
|
||||||
`The option ${issueLogger.createOptionLink(
|
|
||||||
Option.AnyOfLabels
|
|
||||||
)} was specified to only process the issues and pull requests with one of those labels (${LoggerService.cyan(
|
|
||||||
anyOfLabels.length
|
|
||||||
)})`
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasOneOfWhitelistedLabels: boolean = anyOfLabels.some(
|
|
||||||
(label: Readonly<string>): boolean => {
|
|
||||||
return isLabeled(issue, label);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!hasOneOfWhitelistedLabels) {
|
|
||||||
issueLogger.info(
|
|
||||||
LoggerService.white('└──'),
|
|
||||||
`Skipping this $$type because it doesn't have one of the required labels`
|
|
||||||
);
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process issues without any of the required labels
|
|
||||||
} else {
|
|
||||||
issueLogger.info(
|
|
||||||
LoggerService.white('├──'),
|
|
||||||
`One of the required labels is present on this $$type`
|
|
||||||
);
|
|
||||||
issueLogger.info(
|
|
||||||
LoggerService.white('└──'),
|
|
||||||
`Continuing the process for this $$type`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
issueLogger.info(
|
|
||||||
`The option ${issueLogger.createOptionLink(
|
|
||||||
Option.AnyOfLabels
|
|
||||||
)} was not specified`
|
|
||||||
);
|
|
||||||
issueLogger.info(
|
|
||||||
LoggerService.white('└──'),
|
|
||||||
`Continuing the process for this $$type`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const milestones: Milestones = new Milestones(this.options, issue);
|
|
||||||
|
|
||||||
if (milestones.shouldExemptMilestones()) {
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process exempt milestones
|
|
||||||
}
|
|
||||||
|
|
||||||
const assignees: Assignees = new Assignees(this.options, issue);
|
|
||||||
|
|
||||||
if (assignees.shouldExemptAssignees()) {
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
continue; // Don't process exempt assignees
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should this issue be marked stale?
|
|
||||||
const shouldBeStale = !IssuesProcessor._updatedSince(
|
|
||||||
issue.updated_at,
|
|
||||||
daysBeforeStale
|
|
||||||
);
|
|
||||||
|
|
||||||
// Determine if this issue needs to be marked stale first
|
|
||||||
if (!issue.isStale) {
|
|
||||||
issueLogger.info(`This $$type is not stale`);
|
|
||||||
const updatedAtDate: Date = new Date(issue.updated_at);
|
|
||||||
|
|
||||||
if (shouldBeStale) {
|
|
||||||
issueLogger.info(
|
|
||||||
`This $$type should be stale based on the last update date the ${getHumanizedDate(
|
|
||||||
updatedAtDate
|
|
||||||
)} (${LoggerService.cyan(issue.updated_at)})`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldMarkAsStale) {
|
|
||||||
issueLogger.info(
|
|
||||||
`This $$type should be marked as stale based on the option ${issueLogger.createOptionLink(
|
|
||||||
this._getDaysBeforeStaleUsedOptionName(issue)
|
|
||||||
)} (${LoggerService.cyan(daysBeforeStale)})`
|
|
||||||
);
|
|
||||||
await this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
|
||||||
issue.isStale = true; // This issue is now considered stale
|
|
||||||
issueLogger.info(`This $$type is now stale`);
|
|
||||||
} else {
|
|
||||||
issueLogger.info(
|
|
||||||
`This $$type should not be marked as stale based on the option ${issueLogger.createOptionLink(
|
|
||||||
this._getDaysBeforeStaleUsedOptionName(issue)
|
|
||||||
)} (${LoggerService.cyan(daysBeforeStale)})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
issueLogger.info(
|
|
||||||
`This $$type should not be stale based on the last update date the ${getHumanizedDate(
|
|
||||||
updatedAtDate
|
|
||||||
)} (${LoggerService.cyan(issue.updated_at)})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the issue if it was marked stale
|
|
||||||
if (issue.isStale) {
|
|
||||||
issueLogger.info(`This $$type is already stale`);
|
|
||||||
await this._processStaleIssue(
|
|
||||||
issue,
|
|
||||||
staleLabel,
|
|
||||||
actor,
|
|
||||||
closeMessage,
|
|
||||||
closeLabel
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
IssuesProcessor._endIssueProcessing(issue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.operations.hasRemainingOperations()) {
|
if (!this.operations.hasRemainingOperations()) {
|
||||||
|
@ -440,6 +169,283 @@ export class IssuesProcessor {
|
||||||
return this.processIssues(page + 1);
|
return this.processIssues(page + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async processIssue(issue: Issue, actor: string): Promise<void> {
|
||||||
|
this._statistics?.incrementProcessedItemsCount(issue);
|
||||||
|
|
||||||
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
issueLogger.info(
|
||||||
|
`Found this $$type last updated at: ${LoggerService.cyan(
|
||||||
|
issue.updated_at
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// calculate string based messages for this issue
|
||||||
|
const staleMessage: string = issue.isPullRequest
|
||||||
|
? this.options.stalePrMessage
|
||||||
|
: this.options.staleIssueMessage;
|
||||||
|
const closeMessage: string = issue.isPullRequest
|
||||||
|
? this.options.closePrMessage
|
||||||
|
: this.options.closeIssueMessage;
|
||||||
|
const staleLabel: string = issue.isPullRequest
|
||||||
|
? this.options.stalePrLabel
|
||||||
|
: this.options.staleIssueLabel;
|
||||||
|
const closeLabel: string = issue.isPullRequest
|
||||||
|
? this.options.closePrLabel
|
||||||
|
: this.options.closeIssueLabel;
|
||||||
|
const skipMessage = issue.isPullRequest
|
||||||
|
? this.options.stalePrMessage.length === 0
|
||||||
|
: this.options.staleIssueMessage.length === 0;
|
||||||
|
const daysBeforeStale: number = issue.isPullRequest
|
||||||
|
? this._getDaysBeforePrStale()
|
||||||
|
: this._getDaysBeforeIssueStale();
|
||||||
|
const onlyLabels: string[] = wordsToList(this._getOnlyLabels(issue));
|
||||||
|
|
||||||
|
if (onlyLabels.length > 0) {
|
||||||
|
issueLogger.info(
|
||||||
|
`The option ${issueLogger.createOptionLink(
|
||||||
|
Option.OnlyLabels
|
||||||
|
)} was specified to only process issues and pull requests with all those labels (${LoggerService.cyan(
|
||||||
|
onlyLabels.length
|
||||||
|
)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasAllWhitelistedLabels: boolean = onlyLabels.every(
|
||||||
|
(label: Readonly<string>): boolean => {
|
||||||
|
return isLabeled(issue, label);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasAllWhitelistedLabels) {
|
||||||
|
issueLogger.info(
|
||||||
|
LoggerService.white('└──'),
|
||||||
|
`Skipping this $$type because it doesn't have all the required labels`
|
||||||
|
);
|
||||||
|
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process issues without all of the required labels
|
||||||
|
} else {
|
||||||
|
issueLogger.info(
|
||||||
|
LoggerService.white('├──'),
|
||||||
|
`All the required labels are present on this $$type`
|
||||||
|
);
|
||||||
|
issueLogger.info(
|
||||||
|
LoggerService.white('└──'),
|
||||||
|
`Continuing the process for this $$type`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issueLogger.info(
|
||||||
|
`The option ${issueLogger.createOptionLink(
|
||||||
|
Option.OnlyLabels
|
||||||
|
)} was not specified`
|
||||||
|
);
|
||||||
|
issueLogger.info(
|
||||||
|
LoggerService.white('└──'),
|
||||||
|
`Continuing the process for this $$type`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
issueLogger.info(
|
||||||
|
`Days before $$type stale: ${LoggerService.cyan(daysBeforeStale)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const shouldMarkAsStale: boolean = shouldMarkWhenStale(daysBeforeStale);
|
||||||
|
|
||||||
|
if (issue.state === 'closed') {
|
||||||
|
issueLogger.info(`Skipping this $$type because it is closed`);
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process closed issues
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issue.locked) {
|
||||||
|
issueLogger.info(`Skipping this $$type because it is locked`);
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process locked issues
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to remove the close label when not close/locked issue or PR
|
||||||
|
await this._removeCloseLabel(issue, closeLabel);
|
||||||
|
|
||||||
|
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
|
||||||
|
)} (${LoggerService.cyan(this.options.startDate)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Expecting that GitHub will always set a creation date on the issues and PRs
|
||||||
|
// But you never know!
|
||||||
|
if (!isValidDate(createdAt)) {
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
core.setFailed(
|
||||||
|
new Error(`Invalid issue field: "created_at". Expected a valid date`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
issueLogger.info(
|
||||||
|
`$$type created the ${getHumanizedDate(
|
||||||
|
createdAt
|
||||||
|
)} (${LoggerService.cyan(issue.created_at)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDateMoreRecentThan(createdAt, startDate)) {
|
||||||
|
issueLogger.info(
|
||||||
|
`Skipping this $$type because it was created before the specified start date`
|
||||||
|
);
|
||||||
|
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process issues which were created before the start date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issue.isStale) {
|
||||||
|
issueLogger.info(`This $$type has a stale label`);
|
||||||
|
} else {
|
||||||
|
issueLogger.info(`This $$type hasn't a stale label`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const exemptLabels: string[] = wordsToList(
|
||||||
|
issue.isPullRequest
|
||||||
|
? this.options.exemptPrLabels
|
||||||
|
: this.options.exemptIssueLabels
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
exemptLabels.some((exemptLabel: Readonly<string>): boolean =>
|
||||||
|
isLabeled(issue, exemptLabel)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (issue.isStale) {
|
||||||
|
issueLogger.info(`An exempt label was added after the stale label.`);
|
||||||
|
await this._removeStaleLabel(issue, staleLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
issueLogger.info(`Skipping this $$type because it has an exempt label`);
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process exempt issues
|
||||||
|
}
|
||||||
|
|
||||||
|
const anyOfLabels: string[] = wordsToList(this._getAnyOfLabels(issue));
|
||||||
|
|
||||||
|
if (anyOfLabels.length > 0) {
|
||||||
|
issueLogger.info(
|
||||||
|
`The option ${issueLogger.createOptionLink(
|
||||||
|
Option.AnyOfLabels
|
||||||
|
)} was specified to only process the issues and pull requests with one of those labels (${LoggerService.cyan(
|
||||||
|
anyOfLabels.length
|
||||||
|
)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasOneOfWhitelistedLabels: boolean = anyOfLabels.some(
|
||||||
|
(label: Readonly<string>): boolean => {
|
||||||
|
return isLabeled(issue, label);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasOneOfWhitelistedLabels) {
|
||||||
|
issueLogger.info(
|
||||||
|
LoggerService.white('└──'),
|
||||||
|
`Skipping this $$type because it doesn't have one of the required labels`
|
||||||
|
);
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process issues without any of the required labels
|
||||||
|
} else {
|
||||||
|
issueLogger.info(
|
||||||
|
LoggerService.white('├──'),
|
||||||
|
`One of the required labels is present on this $$type`
|
||||||
|
);
|
||||||
|
issueLogger.info(
|
||||||
|
LoggerService.white('└──'),
|
||||||
|
`Continuing the process for this $$type`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issueLogger.info(
|
||||||
|
`The option ${issueLogger.createOptionLink(
|
||||||
|
Option.AnyOfLabels
|
||||||
|
)} was not specified`
|
||||||
|
);
|
||||||
|
issueLogger.info(
|
||||||
|
LoggerService.white('└──'),
|
||||||
|
`Continuing the process for this $$type`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const milestones: Milestones = new Milestones(this.options, issue);
|
||||||
|
|
||||||
|
if (milestones.shouldExemptMilestones()) {
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process exempt milestones
|
||||||
|
}
|
||||||
|
|
||||||
|
const assignees: Assignees = new Assignees(this.options, issue);
|
||||||
|
|
||||||
|
if (assignees.shouldExemptAssignees()) {
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
return; // Don't process exempt assignees
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should this issue be marked stale?
|
||||||
|
const shouldBeStale = !IssuesProcessor._updatedSince(
|
||||||
|
issue.updated_at,
|
||||||
|
daysBeforeStale
|
||||||
|
);
|
||||||
|
|
||||||
|
// Determine if this issue needs to be marked stale first
|
||||||
|
if (!issue.isStale) {
|
||||||
|
issueLogger.info(`This $$type is not stale`);
|
||||||
|
const updatedAtDate: Date = new Date(issue.updated_at);
|
||||||
|
|
||||||
|
if (shouldBeStale) {
|
||||||
|
issueLogger.info(
|
||||||
|
`This $$type should be stale based on the last update date the ${getHumanizedDate(
|
||||||
|
updatedAtDate
|
||||||
|
)} (${LoggerService.cyan(issue.updated_at)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldMarkAsStale) {
|
||||||
|
issueLogger.info(
|
||||||
|
`This $$type should be marked as stale based on the option ${issueLogger.createOptionLink(
|
||||||
|
this._getDaysBeforeStaleUsedOptionName(issue)
|
||||||
|
)} (${LoggerService.cyan(daysBeforeStale)})`
|
||||||
|
);
|
||||||
|
await this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
||||||
|
issue.isStale = true; // This issue is now considered stale
|
||||||
|
issueLogger.info(`This $$type is now stale`);
|
||||||
|
} else {
|
||||||
|
issueLogger.info(
|
||||||
|
`This $$type should not be marked as stale based on the option ${issueLogger.createOptionLink(
|
||||||
|
this._getDaysBeforeStaleUsedOptionName(issue)
|
||||||
|
)} (${LoggerService.cyan(daysBeforeStale)})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issueLogger.info(
|
||||||
|
`This $$type should not be stale based on the last update date the ${getHumanizedDate(
|
||||||
|
updatedAtDate
|
||||||
|
)} (${LoggerService.cyan(issue.updated_at)})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the issue if it was marked stale
|
||||||
|
if (issue.isStale) {
|
||||||
|
issueLogger.info(`This $$type is already stale`);
|
||||||
|
await this._processStaleIssue(
|
||||||
|
issue,
|
||||||
|
staleLabel,
|
||||||
|
actor,
|
||||||
|
closeMessage,
|
||||||
|
closeLabel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
IssuesProcessor._endIssueProcessing(issue);
|
||||||
|
}
|
||||||
|
|
||||||
// Grab comments for an issue since a given date
|
// Grab comments for an issue since a given date
|
||||||
async listIssueComments(
|
async listIssueComments(
|
||||||
issueNumber: Readonly<number>,
|
issueNumber: Readonly<number>,
|
||||||
|
@ -484,16 +490,15 @@ export class IssuesProcessor {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.operations.consumeOperation();
|
this.operations.consumeOperation();
|
||||||
const issueResult: OctoKitIssueList = await this.client.issues.listForRepo(
|
const issueResult: OctoKitIssueList =
|
||||||
{
|
await this.client.issues.listForRepo({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
state: 'open',
|
state: 'open',
|
||||||
per_page: 100,
|
per_page: 100,
|
||||||
direction: this.options.ascending ? 'asc' : 'desc',
|
direction: this.options.ascending ? 'asc' : 'desc',
|
||||||
page
|
page
|
||||||
}
|
});
|
||||||
);
|
|
||||||
this._statistics?.incrementFetchedItemsCount(issueResult.data.length);
|
this._statistics?.incrementFetchedItemsCount(issueResult.data.length);
|
||||||
|
|
||||||
return issueResult.data.map(
|
return issueResult.data.map(
|
||||||
|
@ -579,9 +584,8 @@ export class IssuesProcessor {
|
||||||
`$$type has been updated: ${LoggerService.cyan(issueHasUpdate)}`
|
`$$type has been updated: ${LoggerService.cyan(issueHasUpdate)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const shouldRemoveStaleWhenUpdated: boolean = this._shouldRemoveStaleWhenUpdated(
|
const shouldRemoveStaleWhenUpdated: boolean =
|
||||||
issue
|
this._shouldRemoveStaleWhenUpdated(issue);
|
||||||
);
|
|
||||||
|
|
||||||
issueLogger.info(
|
issueLogger.info(
|
||||||
`The option ${issueLogger.createOptionLink(
|
`The option ${issueLogger.createOptionLink(
|
||||||
|
|
|
@ -35,6 +35,10 @@ export class IssueLogger extends Logger {
|
||||||
super.error(this._format(...message));
|
super.error(this._format(...message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async grouping(message: string, fn: () => Promise<void>): Promise<void> {
|
||||||
|
return super.grouping(this._format(message), fn);
|
||||||
|
}
|
||||||
|
|
||||||
private _replaceTokens(message: Readonly<string>): string {
|
private _replaceTokens(message: Readonly<string>): string {
|
||||||
return this._replaceTypeToken(message);
|
return this._replaceTypeToken(message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,10 @@ export class Logger {
|
||||||
core.error(LoggerService.whiteBright(message.join(' ')));
|
core.error(LoggerService.whiteBright(message.join(' ')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async grouping(message: string, fn: () => Promise<void>): Promise<void> {
|
||||||
|
return core.group(LoggerService.whiteBright(message), fn);
|
||||||
|
}
|
||||||
|
|
||||||
createLink(name: Readonly<string>, link: Readonly<string>): string {
|
createLink(name: Readonly<string>, link: Readonly<string>): string {
|
||||||
return terminalLink(name, link);
|
return terminalLink(name, link);
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,9 +195,8 @@ export class Milestones {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanMilestone: CleanMilestone = Milestones._cleanMilestone(
|
const cleanMilestone: CleanMilestone =
|
||||||
milestone
|
Milestones._cleanMilestone(milestone);
|
||||||
);
|
|
||||||
|
|
||||||
const isSameMilestone: boolean =
|
const isSameMilestone: boolean =
|
||||||
cleanMilestone ===
|
cleanMilestone ===
|
||||||
|
|
|
@ -7,9 +7,9 @@ describe('isLabeled()', (): void => {
|
||||||
|
|
||||||
describe('when the given issue contains no label', (): void => {
|
describe('when the given issue contains no label', (): void => {
|
||||||
beforeEach((): void => {
|
beforeEach((): void => {
|
||||||
issue = ({
|
issue = {
|
||||||
labels: []
|
labels: []
|
||||||
} as unknown) as Issue;
|
} as unknown as Issue;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the given label is a simple label', (): void => {
|
describe('when the given label is a simple label', (): void => {
|
||||||
|
|
Loading…
Reference in New Issue