diff --git a/torchci/lib/bot/autoLabelBot.ts b/torchci/lib/bot/autoLabelBot.ts index 5623ae96e2..6620ccb3bb 100644 --- a/torchci/lib/bot/autoLabelBot.ts +++ b/torchci/lib/bot/autoLabelBot.ts @@ -357,6 +357,28 @@ function getReleaseNotesCategoryAndTopic( return ["uncategorized", topic]; } +export async function wasLabelRecentlyRemoved( + context: Context, + issueNumber: number, + labelName: string, + withinMs: number +): Promise { + const events = await context.octokit.paginate( + context.octokit.issues.listEvents, + context.repo({ + issue_number: issueNumber, + per_page: 100, + }) + ); + const cutoff = Date.now() - withinMs; + return events.some( + (e: any) => + e.event === "unlabeled" && + e.label?.name === labelName && + new Date(e.created_at).getTime() >= cutoff + ); +} + export async function addNewLabels( existingLabels: string[], labelsToAdd: string[], @@ -412,7 +434,20 @@ function myBot(app: Probot): void { switch (addedLabel) { case "high priority": case "critical": - newLabels.push("triage review"); + // Don't re-add triage review if a human just triaged the issue + // (removed triage review within the last hour). Older removals + // are ignored so that resurfacing an old issue with hi-pri does + // re-request triage. + if ( + !(await wasLabelRecentlyRemoved( + context, + context.payload.issue.number, + "triage review", + 60 * 60 * 1000 + )) + ) { + newLabels.push("triage review"); + } break; } diff --git a/torchci/test/autoLabelBot.test.ts b/torchci/test/autoLabelBot.test.ts index 2c53f75dc8..d88ed2199d 100644 --- a/torchci/test/autoLabelBot.test.ts +++ b/torchci/test/autoLabelBot.test.ts @@ -62,6 +62,91 @@ describe("auto-label-bot", () => { emptyMockConfig(payload.repository.full_name); const scope = nock("https://api.github.com") + .get( + "/repos/ezyang/testing-ideal-computing-machine/issues/5/events?per_page=100" + ) + .reply(200, []) + .post( + "/repos/ezyang/testing-ideal-computing-machine/issues/5/labels", + (body) => { + expect(body).toMatchObject({ labels: ["triage review"] }); + return true; + } + ) + .reply(200); + + await probot.receive({ name: "issues", payload, id: "2" }); + + scope.done(); + }); + + test("do not re-add triage review if it was just removed (within the hour)", async () => { + nock("https://api.github.com") + .post("/app/installations/2/access_tokens") + .reply(200, { token: "test" }); + + const payload = requireDeepCopy("./fixtures/issues.labeled"); + payload["label"] = { name: "high priority" }; + payload["issue"]["labels"] = [{ name: "high priority" }]; + emptyMockConfig(payload.repository.full_name); + + const recent = new Date(Date.now() - 5 * 60 * 1000).toISOString(); + const scope = nock("https://api.github.com") + .get( + "/repos/ezyang/testing-ideal-computing-machine/issues/5/events?per_page=100" + ) + .reply(200, [ + { + event: "labeled", + label: { name: "high priority" }, + created_at: recent, + }, + { + event: "labeled", + label: { name: "triage review" }, + created_at: recent, + }, + { + event: "unlabeled", + label: { name: "triage review" }, + created_at: recent, + }, + ]); + + await probot.receive({ name: "issues", payload, id: "2" }); + + scope.done(); + }); + + test("re-add triage review if old issue resurfaces (stale removal)", async () => { + nock("https://api.github.com") + .post("/app/installations/2/access_tokens") + .reply(200, { token: "test" }); + + const payload = requireDeepCopy("./fixtures/issues.labeled"); + payload["label"] = { name: "high priority" }; + payload["issue"]["labels"] = [{ name: "high priority" }]; + emptyMockConfig(payload.repository.full_name); + + // Triage review was removed long ago — issue is resurfacing and + // should be triaged again. + const old = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(); + const scope = nock("https://api.github.com") + .get( + "/repos/ezyang/testing-ideal-computing-machine/issues/5/events?per_page=100" + ) + .reply(200, [ + { + event: "labeled", + label: { name: "triage review" }, + created_at: old, + }, + { + event: "unlabeled", + label: { name: "triage review" }, + created_at: old, + }, + ]) .post( "/repos/ezyang/testing-ideal-computing-machine/issues/5/labels", (body) => {