in functions/src/cron.ts [167:316]
async handleStaleIssue(
org: string,
name: string,
issue: types.internal.Issue,
issueConfig: types.IssueCleanupConfig
): Promise<types.Action[]> {
const actions: types.Action[] = [];
const number = issue.number;
const labelNames = issue.labels.map(label => label.name);
const stateNeedsInfo = labelNames.includes(issueConfig.label_needs_info);
const stateStale = labelNames.includes(issueConfig.label_stale);
// If an issue is not labeled with either the stale or needs-info labels
// then we don't need to do any cron processing on it.
if (!(stateNeedsInfo || stateStale)) {
return actions;
}
// If the issue has one of the specified labels to ignore, then we
// never mark it as stale or close it automatically.
let hasIgnoredLabel = false;
const ignoredLabels = issueConfig.ignore_labels || [];
ignoredLabels.forEach(label => {
hasIgnoredLabel = hasIgnoredLabel || labelNames.includes(label);
});
if (hasIgnoredLabel) {
log.debug(
`Issue ${name}#${number} is ignored due to labels: ${JSON.stringify(
labelNames
)}`
);
return actions;
}
// We fetch the comments for the issue so we can determine when the last actions were taken.
// We manually sort the API response by timestamp (newest to oldest) because the API
// does not guarantee an order.
let comments = await this.gh_client.getCommentsForIssue(org, name, number);
comments = comments.sort(util.compareTimestamps).reverse();
if (!comments || comments.length === 0) {
console.log(`Issue ${name}#${number} has no comments.`);
return actions;
}
// When the issue was marked stale, the bot will have left a comment with certain metadata
const markStaleComment = comments.find(comment => {
return comment.body.includes(EVT_MARK_STALE);
});
if (stateStale && !markStaleComment) {
log.warn(
`Issue ${name}/${number} is stale but no relevant comment was found.`
);
}
if (stateNeedsInfo || stateStale) {
log.debug(
`Processing ${name}#${number} as needs-info or stale, labels=${JSON.stringify(
labelNames
)}`
);
}
// The github webhook handler will automatically remove the needs-info label
// if the author comments, so we can assume inside the cronjob that this has
// not happened and just look at the date of the last comment.
//
// A comment by anyone in the last 7 days makes the issue non-stale.
const lastCommentTime = util.createdDate(comments[0]);
const shouldMarkStale =
stateNeedsInfo &&
util.workingDaysAgo(lastCommentTime) >= issueConfig.needs_info_days;
const shouldClose =
stateStale &&
markStaleComment != undefined &&
util.workingDaysAgo(util.createdDate(markStaleComment)) >=
issueConfig.stale_days;
if (shouldClose) {
// 1) Add a comment about closing
const addClosingComment = new types.GitHubCommentAction(
org,
name,
number,
this.getCloseComment(issue.user.login),
false,
`Comment after closing issue for being stale (comment at ${util.createdDate(
markStaleComment!
)}).`
);
actions.push(addClosingComment);
// 2) Close the issue
const closeIssue = new types.GitHubCloseAction(
org,
name,
number,
`Closing issue for being stale.`
);
actions.push(closeIssue);
// 3) Add and remove labels (according to config)
if (issueConfig.auto_close_labels) {
for (const l of issueConfig.auto_close_labels.add) {
actions.push(new types.GitHubAddLabelAction(org, name, number, l));
}
for (const l of issueConfig.auto_close_labels.remove) {
actions.push(new types.GitHubRemoveLabelAction(org, name, number, l));
}
} else {
// Default is to add 'closed-by-bot'
actions.push(
new types.GitHubAddLabelAction(org, name, number, "closed-by-bot")
);
}
} else if (shouldMarkStale) {
// We add the 'stale' label and also add a comment. Note that
// if the issue was labeled 'needs-info' this label is not removed
// here.
const addStaleLabel = new types.GitHubAddLabelAction(
org,
name,
number,
issueConfig.label_stale,
`Last comment was ${util.workingDaysAgo(
lastCommentTime
)} working days ago (${lastCommentTime}).`
);
const addStaleComment = new types.GitHubCommentAction(
org,
name,
number,
this.getMarkStaleComment(
issue.user.login,
issueConfig.needs_info_days,
issueConfig.stale_days
),
false,
`Comment that goes alongside the stale label.`
);
actions.push(addStaleLabel, addStaleComment);
}
return actions;
}