From ca1bc9f9191b3c8550e05ef06808b9f2f13fb732 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:22:50 +0100 Subject: [PATCH] fix: use task assignment event fallback for lastCheck --- src/handlers/collect-linked-pulls.ts | 2 +- src/helpers/task-metadata.ts | 116 ++++++++++++++++++--------- src/types/github-types.ts | 3 +- tests/__mocks__/handlers.ts | 4 +- tests/__mocks__/helpers.ts | 49 ++++++++++- tests/__mocks__/strings.ts | 4 + tests/main.test.ts | 59 +++++--------- 7 files changed, 154 insertions(+), 83 deletions(-) diff --git a/src/handlers/collect-linked-pulls.ts b/src/handlers/collect-linked-pulls.ts index 15d3180..6875c01 100644 --- a/src/handlers/collect-linked-pulls.ts +++ b/src/handlers/collect-linked-pulls.ts @@ -63,4 +63,4 @@ export async function collectLinkedPullRequests(context: Context, issue: { }); return result.repository.issue.closedByPullRequestsReferences.edges.map((edge) => edge.node); -} +} \ No newline at end of file diff --git a/src/helpers/task-metadata.ts b/src/helpers/task-metadata.ts index b5048eb..bd475bc 100644 --- a/src/helpers/task-metadata.ts +++ b/src/helpers/task-metadata.ts @@ -7,7 +7,7 @@ export async function getTaskMetadata( context: Context, repo: ListForOrg["data"][0], issue: ListIssueForRepo -) { +): Promise<{ metadata: { taskDeadline: string; taskAssignees: number[] }, lastCheck: DateTime } | false> { const { logger, octokit } = context; const comments = (await octokit.paginate(octokit.rest.issues.listComments, { @@ -18,8 +18,7 @@ export async function getTaskMetadata( })) as ListCommentsForIssue[]; const botComments = comments.filter((o) => o.user?.type === "Bot"); - const taskDeadlineJsonRegex = /"taskDeadline": "([^"]*)"/g; - const taskAssigneesJsonRegex = /"taskAssignees": \[([^\]]*)\]/g; + // Has the bot assigned them, typically via the `/start` command const assignmentRegex = /Ubiquity - Assignment - start -/gi; const botAssignmentComments = botComments.filter( (o) => assignmentRegex.test(o?.body || "") @@ -27,31 +26,42 @@ export async function getTaskMetadata( DateTime.fromISO(a.created_at).toMillis() - DateTime.fromISO(b.created_at).toMillis() ) + // Has the bot previously reminded them? const botFollowup = /`; } +export function lastCheckWasOn(deadlineStr: string) { + return `Last check was on ${deadlineStr}`; +} + export function updatingRemindersFor(repo: string) { return `Updating reminders for ${repo}`; } diff --git a/tests/main.test.ts b/tests/main.test.ts index 83a6e2d..7efe18c 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -10,10 +10,9 @@ import dotenv from "dotenv"; import { Logs } from "@ubiquity-dao/ubiquibot-logger"; import { Context } from "../src/types/context"; import mockUsers from "./__mocks__/mock-users"; -import { botAssignmentComment, getIssueHtmlUrl, getIssueUrl, noAssignmentCommentFor, STRINGS, updatingRemindersFor } from "./__mocks__/strings"; -import { createComment, createIssue, createRepo, ONE_DAY } from "./__mocks__/helpers"; +import { botAssignmentComment, getIssueHtmlUrl, STRINGS } from "./__mocks__/strings"; +import { createComment, createEvent, createIssue, createRepo, ONE_DAY } from "./__mocks__/helpers"; import { collectLinkedPullRequests } from "../src/handlers/collect-linked-pulls"; -import { createStructuredMetadata } from "../src/helpers/structured-metadata"; dotenv.config(); const octokit = jest.requireActual("@octokit/rest"); @@ -57,22 +56,23 @@ describe("User start/stop", () => { const infoSpy = jest.spyOn(context.logger, "info"); await runPlugin(context); - expect(infoSpy).toHaveBeenNthCalledWith(1, noAssignmentCommentFor(getIssueHtmlUrl(1))); - expect(infoSpy).toHaveBeenNthCalledWith(2, noAssignmentCommentFor(getIssueHtmlUrl(2))); - expect(infoSpy).toHaveBeenNthCalledWith(3, noAssignmentCommentFor(getIssueHtmlUrl(3))); - expect(infoSpy).toHaveBeenNthCalledWith(4, noAssignmentCommentFor(getIssueHtmlUrl(4))); + expect(infoSpy).toHaveBeenNthCalledWith(1, `Nothing to do for ${getIssueHtmlUrl(1)}, still within due-time.`); + expect(infoSpy).toHaveBeenNthCalledWith(3, `Nothing to do for ${getIssueHtmlUrl(2)}, still within due-time.`); + expect(infoSpy).toHaveBeenNthCalledWith(5, `Nothing to do for ${getIssueHtmlUrl(3)}, still within due-time.`); + expect(infoSpy).toHaveBeenNthCalledWith(7, `Nothing to do for ${getIssueHtmlUrl(4)}, still within due-time.`); + expect(infoSpy).not.toHaveBeenNthCalledWith(9, `Nothing to do for https://github.com/ubiquity/private-repo/issues/5, still within due-time.`); }); it("Should include the previously excluded repo", async () => { - const context = createContext(1, 1); + const context = createContext(1, 1, []); const infoSpy = jest.spyOn(context.logger, "info"); - context.config.watch.optOut = []; await runPlugin(context); - expect(infoSpy).toHaveBeenNthCalledWith(1, noAssignmentCommentFor(getIssueHtmlUrl(1))); - expect(infoSpy).toHaveBeenNthCalledWith(2, noAssignmentCommentFor(getIssueHtmlUrl(2))); - expect(infoSpy).toHaveBeenNthCalledWith(3, noAssignmentCommentFor(getIssueHtmlUrl(3))); - expect(infoSpy).toHaveBeenNthCalledWith(4, noAssignmentCommentFor(getIssueHtmlUrl(4))); + expect(infoSpy).toHaveBeenNthCalledWith(1, `Nothing to do for ${getIssueHtmlUrl(1)}, still within due-time.`); + expect(infoSpy).toHaveBeenNthCalledWith(3, `Nothing to do for ${getIssueHtmlUrl(2)}, still within due-time.`); + expect(infoSpy).toHaveBeenNthCalledWith(5, `Nothing to do for ${getIssueHtmlUrl(3)}, still within due-time.`); + expect(infoSpy).toHaveBeenNthCalledWith(7, `Nothing to do for ${getIssueHtmlUrl(4)}, still within due-time.`); + expect(infoSpy).toHaveBeenNthCalledWith(9, `Nothing to do for https://github.com/ubiquity/private-repo/issues/5, still within due-time.`); }); it("Should eject the user after the disqualification period", async () => { @@ -87,8 +87,7 @@ describe("User start/stop", () => { await runPlugin(context); - expect(infoSpy).toHaveBeenNthCalledWith(1, `Assignees mismatch found for ${getIssueHtmlUrl(1)}`, { caller: STRINGS.LOGS_ANON_CALLER, assigneeIds: [1], metadata: { taskDeadline: timestamp, taskAssignees: [2] } }); - expect(infoSpy).toHaveBeenNthCalledWith(2, `Passed the deadline on ${getIssueHtmlUrl(1)} and no activity is detected, removing assignees.`); + expect(infoSpy).toHaveBeenNthCalledWith(1, `Nothing to do for ${getIssueHtmlUrl(1)}, still within due-time.`); expect(infoSpy).toHaveBeenNthCalledWith(3, `Passed the deadline on ${getIssueHtmlUrl(2)} and no activity is detected, removing assignees.`); expect(infoSpy).toHaveBeenNthCalledWith(4, `Passed the deadline on ${getIssueHtmlUrl(3)} and no activity is detected, removing assignees.`); @@ -98,8 +97,6 @@ describe("User start/stop", () => { it("Should warn the user after the warning period", async () => { const context = createContext(4, 2); - const infoSpy = jest.spyOn(context.logger, "info"); - const timestamp = daysPriorToNow(5); createComment(3, 3, STRINGS.BOT, "Bot", botAssignmentComment(2, timestamp), timestamp); @@ -109,13 +106,6 @@ describe("User start/stop", () => { await runPlugin(context); - expect(infoSpy).toHaveBeenNthCalledWith(1, `Assignees mismatch found for ${getIssueHtmlUrl(1)}`, { - caller: STRINGS.LOGS_ANON_CALLER, assigneeIds: [1], metadata: { - taskDeadline: timestamp, - taskAssignees: [2], - } - }); - const updatedIssue = db.issue.findFirst({ where: { id: { equals: 4 } } }); expect(updatedIssue?.assignees).toEqual([{ login: STRINGS.USER, id: 2 }]); @@ -137,17 +127,9 @@ describe("User start/stop", () => { await runPlugin(context); - expect(infoSpy).toHaveBeenNthCalledWith(1, `Assignees mismatch found for ${getIssueHtmlUrl(1)}`, { - caller: STRINGS.LOGS_ANON_CALLER, - assigneeIds: [1], - metadata: { - taskDeadline: timestamp, - taskAssignees: [2], - } - }); - expect(infoSpy).toHaveBeenNthCalledWith(2, `Nothing to do for ${getIssueHtmlUrl(1)}, still within due-time.`); - expect(infoSpy).toHaveBeenNthCalledWith(4, `Nothing to do for ${getIssueHtmlUrl(2)}, still within due-time.`); - expect(infoSpy).toHaveBeenNthCalledWith(6, `Nothing to do for ${getIssueHtmlUrl(3)}, still within due-time.`); + expect(infoSpy).toHaveBeenNthCalledWith(1, `Nothing to do for ${getIssueHtmlUrl(1)}, still within due-time.`); + expect(infoSpy).toHaveBeenNthCalledWith(3, `Nothing to do for ${getIssueHtmlUrl(2)}, still within due-time.`); + expect(infoSpy).toHaveBeenNthCalledWith(5, `Nothing to do for ${getIssueHtmlUrl(3)}, still within due-time.`); const updatedIssue = db.issue.findFirst({ where: { id: { equals: 4 } } }); expect(updatedIssue?.assignees).toEqual([{ login: STRINGS.USER, id: 2 }]); @@ -197,16 +179,19 @@ async function setupTests() { createIssue(3, [{ login: STRINGS.USER, id: 2 }], STRINGS.UBIQUITY, daysPriorToNow(4), "fixes #2"); // disqualification createIssue(4, [{ login: STRINGS.USER, id: 2 }], STRINGS.UBIQUITY, daysPriorToNow(12), "closes #3"); + createIssue(5, [{ login: STRINGS.USER, id: 2 }], STRINGS.UBIQUITY, daysPriorToNow(12), "closes #1", STRINGS.PRIVATE_REPO_NAME); createComment(1, 1, STRINGS.UBIQUITY); createComment(2, 2, STRINGS.UBIQUITY); + + createEvent() } function daysPriorToNow(days: number) { return new Date(Date.now() - ONE_DAY * days).toISOString(); } -function createContext(issueId: number, senderId: number): Context<"issue_comment.created"> { +function createContext(issueId: number, senderId: number, optOut = [STRINGS.PRIVATE_REPO_NAME]): Context<"issue_comment.created"> { return { payload: { issue: db.issue.findFirst({ where: { id: { equals: issueId } } }) as unknown as Context<"issue_comment.created">["payload"]["issue"], @@ -221,7 +206,7 @@ function createContext(issueId: number, senderId: number): Context<"issue_commen config: { disqualification: ONE_DAY * 7, warning: ONE_DAY * 3.5, - watch: { optOut: [STRINGS.PRIVATE_REPO_NAME] }, + watch: { optOut }, }, octokit: new octokit.Octokit(), eventName: "issue_comment.created",