mirror of
https://github.com/actions/stale.git
synced 2025-12-10 20:21:20 +00:00
Remove stale labels and ignore stale issues with comments (#58)
* Ignore issues that have recent comments and ignore the stale label. Defaulting to this behavior but added an option to turn it off. * Fix up tests a bit to make this runnable. Add a test for the logic. * Add vscode debugging configuration.
This commit is contained in:
126
dist/index.js
vendored
126
dist/index.js
vendored
@@ -3756,6 +3756,7 @@ function getAndValidateArgs() {
|
||||
exemptPrLabels: core.getInput('exempt-pr-labels'),
|
||||
onlyLabels: core.getInput('only-labels'),
|
||||
operationsPerRun: parseInt(core.getInput('operations-per-run', { required: true })),
|
||||
removeStaleWhenUpdated: !(core.getInput('remove-stale-when-updated') === 'false'),
|
||||
debugOnly: core.getInput('debug-only') === 'true'
|
||||
};
|
||||
for (const numberInput of [
|
||||
@@ -8447,16 +8448,23 @@ const github = __importStar(__webpack_require__(469));
|
||||
* Handle processing of issues for staleness/closure.
|
||||
*/
|
||||
class IssueProcessor {
|
||||
constructor(options, getIssues) {
|
||||
constructor(options, getIssues, listIssueComments, getLabelCreationDate) {
|
||||
this.operationsLeft = 0;
|
||||
this.staleIssues = [];
|
||||
this.closedIssues = [];
|
||||
this.removedLabelIssues = [];
|
||||
this.options = options;
|
||||
this.operationsLeft = options.operationsPerRun;
|
||||
this.client = new github.GitHub(options.repoToken);
|
||||
if (getIssues) {
|
||||
this.getIssues = getIssues;
|
||||
}
|
||||
if (listIssueComments) {
|
||||
this.listIssueComments = listIssueComments;
|
||||
}
|
||||
if (getLabelCreationDate) {
|
||||
this.getLabelCreationDate = getLabelCreationDate;
|
||||
}
|
||||
if (this.options.debugOnly) {
|
||||
core.warning('Executing in debug mode. Debug output will be written but no issues will be processed.');
|
||||
}
|
||||
@@ -8490,23 +8498,23 @@ class IssueProcessor {
|
||||
core.debug(`Skipping ${issueType} due to empty stale message`);
|
||||
continue;
|
||||
}
|
||||
if (issue.state === 'closed') {
|
||||
core.debug(`Skipping ${issueType} because it is closed`);
|
||||
continue; // don't process closed issues
|
||||
}
|
||||
if (issue.locked) {
|
||||
core.debug(`Skipping ${issueType} because it is locked`);
|
||||
continue; // don't process locked issues
|
||||
}
|
||||
if (exemptLabels.some((exemptLabel) => IssueProcessor.isLabeled(issue, exemptLabel))) {
|
||||
core.debug(`Skipping ${issueType} because it has an exempt label`);
|
||||
continue; // don't process exempt issues
|
||||
}
|
||||
if (IssueProcessor.isLabeled(issue, staleLabel)) {
|
||||
core.debug(`Found a stale ${issueType}`);
|
||||
if (this.options.daysBeforeClose >= 0 &&
|
||||
IssueProcessor.wasLastUpdatedBefore(issue, this.options.daysBeforeClose)) {
|
||||
core.debug(`Closing ${issueType} because it was last updated on ${issue.updated_at}`);
|
||||
yield this.closeIssue(issue);
|
||||
this.operationsLeft -= 1;
|
||||
}
|
||||
else {
|
||||
core.debug(`Ignoring stale ${issueType} because it was updated recenlty`);
|
||||
}
|
||||
yield this.processStaleIssue(issue, issueType, staleLabel);
|
||||
}
|
||||
else if (IssueProcessor.wasLastUpdatedBefore(issue, this.options.daysBeforeStale)) {
|
||||
else if (!IssueProcessor.updatedSince(issue.updated_at, this.options.daysBeforeStale)) {
|
||||
core.debug(`Marking ${issueType} stale because it was last updated on ${issue.updated_at}`);
|
||||
yield this.markStale(issue, staleMessage, staleLabel);
|
||||
this.operationsLeft -= 2;
|
||||
@@ -8516,6 +8524,57 @@ class IssueProcessor {
|
||||
return this.processIssues(page + 1);
|
||||
});
|
||||
}
|
||||
// handle all of the stale issue logic when we find a stale issue
|
||||
processStaleIssue(issue, issueType, staleLabel) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (this.options.daysBeforeClose < 0) {
|
||||
return; // nothing to do because we aren't closing stale issues
|
||||
}
|
||||
const markedStaleOn = yield this.getLabelCreationDate(issue, staleLabel);
|
||||
const issueHasComments = yield this.isIssueStillStale(issue, markedStaleOn);
|
||||
const issueHasUpdate = IssueProcessor.updatedSince(issue.updated_at, this.options.daysBeforeClose);
|
||||
core.debug(`Issue #${issue.number} marked stale on: ${markedStaleOn}`);
|
||||
core.debug(`Issue #${issue.number} has been updated: ${issueHasUpdate}`);
|
||||
core.debug(`Issue #${issue.number} has been commented on: ${issueHasComments}`);
|
||||
if (!issueHasComments && !issueHasUpdate) {
|
||||
core.debug(`Closing ${issueType} because it was last updated on ${issue.updated_at}`);
|
||||
yield this.closeIssue(issue);
|
||||
}
|
||||
else {
|
||||
if (this.options.removeStaleWhenUpdated) {
|
||||
yield this.removeLabel(issue, staleLabel);
|
||||
}
|
||||
core.debug(`Ignoring stale ${issueType} because it was updated recenlty`);
|
||||
}
|
||||
});
|
||||
}
|
||||
// checks to see if a given issue is still stale (has had activity on it)
|
||||
isIssueStillStale(issue, sinceDate) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.debug(`Checking for comments on issue #${issue.number} since ${sinceDate} to see if it is still stale`);
|
||||
if (!sinceDate) {
|
||||
return true; // if no date was provided then the issue was marked stale a long time ago
|
||||
}
|
||||
this.operationsLeft -= 1;
|
||||
// find any comments since the stale label
|
||||
const comments = yield this.listIssueComments(issue.number, sinceDate);
|
||||
// if there are any user comments returned, issue is not stale anymore
|
||||
return comments.filter(comment => comment.user.type === 'User').length > 0;
|
||||
});
|
||||
}
|
||||
// grab comments for an issue since a given date
|
||||
listIssueComments(issueNumber, sinceDate) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// find any comments since date on the given issue
|
||||
const comments = yield this.client.issues.listComments({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
since: sinceDate
|
||||
});
|
||||
return comments.data;
|
||||
});
|
||||
}
|
||||
// grab issues from github in baches of 100
|
||||
getIssues(page) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
@@ -8535,6 +8594,7 @@ class IssueProcessor {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.debug(`Marking issue #${issue.number} - ${issue.title} as stale`);
|
||||
this.staleIssues.push(issue);
|
||||
this.operationsLeft -= 2;
|
||||
if (this.options.debugOnly) {
|
||||
return;
|
||||
}
|
||||
@@ -8552,11 +8612,12 @@ class IssueProcessor {
|
||||
});
|
||||
});
|
||||
}
|
||||
/// Close an issue based on staleness
|
||||
// Close an issue based on staleness
|
||||
closeIssue(issue) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.debug(`Closing issue #${issue.number} - ${issue.title} for being stale`);
|
||||
this.closedIssues.push(issue);
|
||||
this.operationsLeft -= 1;
|
||||
if (this.options.debugOnly) {
|
||||
return;
|
||||
}
|
||||
@@ -8568,14 +8629,49 @@ class IssueProcessor {
|
||||
});
|
||||
});
|
||||
}
|
||||
// Remove a label from an issue
|
||||
removeLabel(issue, label) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.debug(`Removing label ${label} from issue #${issue.number} - ${issue.title}`);
|
||||
this.removedLabelIssues.push(issue);
|
||||
this.operationsLeft -= 1;
|
||||
if (this.options.debugOnly) {
|
||||
return;
|
||||
}
|
||||
yield this.client.issues.removeLabel({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
name: encodeURIComponent(label) // A label can have a "?" in the name
|
||||
});
|
||||
});
|
||||
}
|
||||
// returns the creation date of a given label on an issue (or nothing if no label existed)
|
||||
///see https://developer.github.com/v3/activity/events/
|
||||
getLabelCreationDate(issue, label) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.debug(`Checking for label ${label} on issue #${issue.number}`);
|
||||
this.operationsLeft -= 1;
|
||||
const options = this.client.issues.listEvents.endpoint.merge({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
per_page: 100,
|
||||
issue_number: issue.number
|
||||
});
|
||||
const events = yield this.client.paginate(options);
|
||||
const reversedEvents = events.reverse();
|
||||
const staleLabeledEvent = reversedEvents.find(event => event.event === 'labeled' && event.label.name === label);
|
||||
return staleLabeledEvent.created_at;
|
||||
});
|
||||
}
|
||||
static isLabeled(issue, label) {
|
||||
const labelComparer = l => label.localeCompare(l.name, undefined, { sensitivity: 'accent' }) === 0;
|
||||
return issue.labels.filter(labelComparer).length > 0;
|
||||
}
|
||||
static wasLastUpdatedBefore(issue, num_days) {
|
||||
static updatedSince(timestamp, num_days) {
|
||||
const daysInMillis = 1000 * 60 * 60 * 24 * num_days;
|
||||
const millisSinceLastUpdated = new Date().getTime() - new Date(issue.updated_at).getTime();
|
||||
return millisSinceLastUpdated >= daysInMillis;
|
||||
const millisSinceLastUpdated = new Date().getTime() - new Date(timestamp).getTime();
|
||||
return millisSinceLastUpdated < daysInMillis;
|
||||
}
|
||||
static parseCommaSeparatedString(s) {
|
||||
// String.prototype.split defaults to [''] when called on an empty string
|
||||
|
||||
Reference in New Issue
Block a user