From 1aa530b0c8a6709e63f79f19eaeae4a6929ed1ff Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:57:08 +0100 Subject: [PATCH 01/30] chore: teamate assignment --- src/handlers/shared/start.ts | 7 +++---- src/handlers/user-start-stop.ts | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index fbe77ba9..f8c88915 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -6,7 +6,7 @@ import { generateAssignmentComment } from "./generate-assignment-comment"; import structuredMetadata from "./structured-metadata"; import { assignTableComment } from "./table"; -export async function start(context: Context, issue: Context["payload"]["issue"], sender: Context["payload"]["sender"]) { +export async function start(context: Context, issue: Context["payload"]["issue"], sender: Context["payload"]["sender"], teammates: string[]) { const { logger, config } = context; const { maxConcurrentTasks } = config.miscellaneous; const { taskStaleTimeoutDuration } = config.timers; @@ -35,9 +35,8 @@ export async function start(context: Context, issue: Context["payload"]["issue"] } // check max assigned issues - const openedPullRequests = await getAvailableOpenedPullRequests(context, sender.login); - logger.info(`Opened Pull Requests with approved reviews or with no reviews but over 24 hours have passed: ${JSON.stringify(openedPullRequests)}`); + logger.info(`Opened Pull Requests with approved reviews or with no reviews but over 24 hours have passed: `, { openedPullRequests }); const assignedIssues = await getAssignedIssues(context, sender.login); logger.info("Max issue allowed is", { maxConcurrentTasks }); @@ -83,7 +82,7 @@ export async function start(context: Context, issue: Context["payload"]["issue"] const duration: number = calculateDurations(labels).shift() ?? 0; const { id, login } = sender; - const logMessage = logger.info("Task assigned successfully", { duration, priceLabel, revision: commitHash?.substring(0, 7) }); + const logMessage = logger.info("Task assigned successfully", { duration, priceLabel, revision: commitHash?.substring(0, 7), teammate: teammates, assignee: login, issue: issue.number }); const assignmentComment = await generateAssignmentComment(context, issue.created_at, issue.number, id, duration); const metadata = structuredMetadata.create("Assignment", logMessage); diff --git a/src/handlers/user-start-stop.ts b/src/handlers/user-start-stop.ts index 46148f3e..23b961f8 100644 --- a/src/handlers/user-start-stop.ts +++ b/src/handlers/user-start-stop.ts @@ -6,11 +6,12 @@ export async function userStartStop(context: Context): Promise<{ output: string const { payload } = context; const { issue, comment, sender, repository } = payload; const slashCommand = comment.body.split(" ")[0].replace("/", ""); + const teamMates = comment.body.split("@").slice(1).map((teamMate) => teamMate.split(" ")[0]); if (slashCommand === "stop") { return await stop(context, issue, sender, repository); } else if (slashCommand === "start") { - return await start(context, issue, sender); + return await start(context, issue, sender, teamMates); } return { output: null }; From 7281a72e960d05436293116684438e842c7fdb00 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:14:11 +0100 Subject: [PATCH 02/30] chore: unassign command user --- src/utils/get-linked-prs.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/get-linked-prs.ts b/src/utils/get-linked-prs.ts index 187f8087..205afaef 100644 --- a/src/utils/get-linked-prs.ts +++ b/src/utils/get-linked-prs.ts @@ -43,6 +43,5 @@ export async function getLinkedPullRequests(context: Context, { owner, repositor body: pr.body, }; }) - .filter((pr) => pr !== null) - .filter((pr) => pr.state === "open") as GetLinkedResults[]; + .filter((pr) => pr !== null && pr.state === "open") as GetLinkedResults[]; } From e486effa0465fe12e2fa5cbff686549b91ea9c51 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 15 Jul 2024 18:51:24 +0100 Subject: [PATCH 03/30] chore: teams test --- src/handlers/shared/start.ts | 9 +++++++- tests/main.test.ts | 41 ++++++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index f8c88915..9e4b16f6 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -82,7 +82,14 @@ export async function start(context: Context, issue: Context["payload"]["issue"] const duration: number = calculateDurations(labels).shift() ?? 0; const { id, login } = sender; - const logMessage = logger.info("Task assigned successfully", { duration, priceLabel, revision: commitHash?.substring(0, 7), teammate: teammates, assignee: login, issue: issue.number }); + const logMessage = logger.info("Task assigned successfully", { + duration, + priceLabel, + revision: commitHash?.substring(0, 7), + teammate: teammates, + assignee: login, + issue: issue.number, + }); const assignmentComment = await generateAssignmentComment(context, issue.created_at, issue.number, id, duration); const metadata = structuredMetadata.create("Assignment", logMessage); diff --git a/tests/main.test.ts b/tests/main.test.ts index 15181f1c..2543d894 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -52,6 +52,25 @@ describe("User start/stop", () => { expect(output).toEqual("Task assigned successfully"); }); + test("User can start an issue with teammates", async () => { + const issue = db.issue.findFirst({ where: { id: { equals: 1 } } }) as unknown as Issue; + const sender = db.users.findFirst({ where: { id: { equals: 1 } } }) as unknown as Sender; + + const context = createContext(issue, sender, "/start @user2"); + + context.adapters = createAdapters(getSupabase(), context as unknown as Context); + + const { output } = await userStartStop(context as unknown as Context); + + expect(output).toEqual("Task assigned successfully"); + + const issue2 = db.issue.findFirst({ where: { id: { equals: 1 } } }) as unknown as Issue; + + expect(issue2.assignees).toHaveLength(2); + + expect(issue2.assignees).toEqual(expect.arrayContaining(["ubiquity", "user2"])); + }); + test("User can stop an issue", async () => { const issue = db.issue.findFirst({ where: { id: { equals: 2 } } }) as unknown as Issue; const sender = db.users.findFirst({ where: { id: { equals: 2 } } }) as unknown as Sender; @@ -66,7 +85,7 @@ describe("User start/stop", () => { }); test("Stopping an issue should close the author's linked PR", async () => { - const infoSpy = jest.spyOn(console, "info").mockImplementation(() => {}); + const infoSpy = jest.spyOn(console, "info").mockImplementation(() => { }); const issue = db.issue.findFirst({ where: { id: { equals: 2 } } }) as unknown as Issue; const sender = db.users.findFirst({ where: { id: { equals: 2 } } }) as unknown as Sender; const context = createContext(issue, sender, "/stop"); @@ -416,7 +435,6 @@ async function setupTests() { }, body: "Pull request body", owner: "ubiquity", - repo: "test-repo", state: "open", closed_at: null, @@ -454,6 +472,7 @@ async function setupTests() { state: "open", body: `Resolves #2`, html_url: "https://github.com/ubiquity/test-repo/pull/10", + state: "open", repository: { full_name: TEST_REPO, }, @@ -566,17 +585,17 @@ function getSupabase(withData = true) { single: jest.fn().mockResolvedValue({ data: withData ? { - id: 1, - wallets: { - address: "0x123", - }, - } + id: 1, + wallets: { + address: "0x123", + }, + } : { - id: 1, - wallets: { - address: undefined, - }, + id: 1, + wallets: { + address: undefined, }, + }, }), }), }), From 790926780fc0cfad8029a700fd21fbb7bda6cedd Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 16 Jul 2024 00:39:54 +0100 Subject: [PATCH 04/30] chore: correct comment --- src/handlers/shared/start.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index 9e4b16f6..e2cbe711 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -63,13 +63,14 @@ export async function start(context: Context, issue: Context["payload"]["issue"] const assignees = (issue?.assignees ?? []).filter(Boolean); if (assignees.length !== 0) { - const log = logger.error("The issue is already assigned. Please choose another unassigned task.", { issueNumber: issue.number }); - await addCommentToIssue(context, log?.logMessage.diff as string); - throw new Error("Issue is already assigned"); + // const log = logger.error("The issue is already assigned. Please choose another unassigned task.", { issueNumber: issue.number }); + // await addCommentToIssue(context, log?.logMessage.diff as string); + const currentUserAssigned = !!assignees.find((assignee) => assignee?.login === sender.login); + const log = logger.error(currentUserAssigned ? "You are already assigned to this task." : "The issue is already assigned. Please choose another unassigned task.", { issueNumber: issue.number }); + return await addCommentToIssue(context, log?.logMessage.diff as string); } // get labels - const labels = issue.labels; const priceLabel = labels.find((label: Label) => label.name.startsWith("Price: ")); From deaf33edb6ece4f1d748338c30df3704ed132ef3 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 16 Jul 2024 02:14:25 +0100 Subject: [PATCH 05/30] chore: max assigned check all users --- src/handlers/shared/start.ts | 32 +++++++++++++++++++++++++++++--- src/utils/issue.ts | 15 ++++----------- tests/main.test.ts | 2 +- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index e2cbe711..8e51e735 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -61,13 +61,23 @@ export async function start(context: Context, issue: Context["payload"]["issue"] throw new Error("Issue is closed"); } - const assignees = (issue?.assignees ?? []).filter(Boolean); + const assignees = issue?.assignees ?? [] + if (assignees.length !== 0) { // const log = logger.error("The issue is already assigned. Please choose another unassigned task.", { issueNumber: issue.number }); // await addCommentToIssue(context, log?.logMessage.diff as string); const currentUserAssigned = !!assignees.find((assignee) => assignee?.login === sender.login); - const log = logger.error(currentUserAssigned ? "You are already assigned to this task." : "The issue is already assigned. Please choose another unassigned task.", { issueNumber: issue.number }); - return await addCommentToIssue(context, log?.logMessage.diff as string); + const comment = currentUserAssigned ? "You are already assigned to this task." : "The issue is already assigned. Please choose another unassigned task."; + await addCommentToIssue(context, `\`\`\`diff\n! ${comment}`); + throw new Error(comment); + } + + teammates.push(sender.login) + + // check max assigned issues + for (const user of teammates) { + if (!user) continue; + await handleTaskLimitChecks(user, context, maxConcurrentTasks, logger, sender.login); } // get labels @@ -118,3 +128,19 @@ export async function start(context: Context, issue: Context["payload"]["issue"] return { output: "Task assigned successfully" }; } + +async function handleTaskLimitChecks(username: string, context: Context, maxConcurrentTasks: number, logger: Context["logger"], sender: string) { + const openedPullRequests = await getAvailableOpenedPullRequests(context, username); + logger.info(`Opened Pull Requests with approved reviews or with no reviews but over 24 hours have passed: `, { openedPullRequests }); + + const assignedIssues = await getAssignedIssues(context, username); + logger.info("Max issue allowed is", { maxConcurrentTasks, assignedIssues: assignedIssues.map((issue) => `${issue.url}`) }); + + // check for max and enforce max + if (assignedIssues.length - openedPullRequests.length >= maxConcurrentTasks) { + const isSender = username === sender; + const comment = (isSender ? "You have" : `${username} has`) + ` reached the max limit of ${maxConcurrentTasks} assigned issues.`; + await addCommentToIssue(context, `\`\`\`diff\n! ${comment}\n\`\`\``); + throw new Error(`Too many assigned issues, you have reached your max limit of ${maxConcurrentTasks} issues.`); + } +} \ No newline at end of file diff --git a/src/utils/issue.ts b/src/utils/issue.ts index e6746e80..b7d5288f 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -8,19 +8,12 @@ export function isParentIssue(body: string) { } export async function getAssignedIssues(context: Context, username: string): Promise { - const payload = context.payload; + const { payload } = context try { - return await context.octokit.paginate( - context.octokit.issues.listForRepo, - { - owner: payload.repository.owner.login, - repo: payload.repository.name, - state: ISSUE_TYPE.OPEN, - per_page: 100, - }, - ({ data: issues }) => issues.filter((issue: Issue) => !issue.pull_request && issue.assignee && issue.assignee.login === username) - ); + return await context.octokit.search.issuesAndPullRequests({ + q: `is:open assignee:${username} org:${payload.repository.owner.login}`, + }).then((response) => response.data.items) as Issue[]; } catch (err: unknown) { context.logger.error("Fetching assigned issues failed!", { error: err as Error }); return []; diff --git a/tests/main.test.ts b/tests/main.test.ts index 2543d894..ca741621 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -138,7 +138,7 @@ describe("User start/stop", () => { context.adapters = createAdapters(getSupabase(), context as unknown as Context); - const err = "Issue is already assigned"; + const err = "The issue is already assigned. Please choose another unassigned task."; try { await userStartStop(context as unknown as Context); From aafc15e76ae4ec4dfa90b76fc3e4cb16696f39f2 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 16 Jul 2024 02:16:40 +0100 Subject: [PATCH 06/30] chore: fix eslint naming convention --- src/handlers/shared/start.ts | 15 ++++++--------- src/utils/issue.ts | 12 +++++++----- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index 8e51e735..e29481a9 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -61,18 +61,15 @@ export async function start(context: Context, issue: Context["payload"]["issue"] throw new Error("Issue is closed"); } - const assignees = issue?.assignees ?? [] + const assignees = issue?.assignees ?? []; if (assignees.length !== 0) { - // const log = logger.error("The issue is already assigned. Please choose another unassigned task.", { issueNumber: issue.number }); - // await addCommentToIssue(context, log?.logMessage.diff as string); - const currentUserAssigned = !!assignees.find((assignee) => assignee?.login === sender.login); - const comment = currentUserAssigned ? "You are already assigned to this task." : "The issue is already assigned. Please choose another unassigned task."; - await addCommentToIssue(context, `\`\`\`diff\n! ${comment}`); - throw new Error(comment); + const isCurrentUserAssigned = !!assignees.find((assignee) => assignee?.login === sender.login); + const log = logger.error(isCurrentUserAssigned ? "You are already assigned to this task." : "The issue is already assigned. Please choose another unassigned task.", { issueNumber: issue.number }); + return await addCommentToIssue(context, log?.logMessage.diff as string); } - teammates.push(sender.login) + teammates.push(sender.login); // check max assigned issues for (const user of teammates) { @@ -143,4 +140,4 @@ async function handleTaskLimitChecks(username: string, context: Context, maxConc await addCommentToIssue(context, `\`\`\`diff\n! ${comment}\n\`\`\``); throw new Error(`Too many assigned issues, you have reached your max limit of ${maxConcurrentTasks} issues.`); } -} \ No newline at end of file +} diff --git a/src/utils/issue.ts b/src/utils/issue.ts index b7d5288f..eb8442f7 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -1,5 +1,5 @@ import { Context } from "../types/context"; -import { Issue, ISSUE_TYPE, PullRequest, Review } from "../types/payload"; +import { Issue, PullRequest, Review } from "../types/payload"; import { getLinkedPullRequests, GetLinkedResults } from "./get-linked-prs"; export function isParentIssue(body: string) { @@ -8,12 +8,14 @@ export function isParentIssue(body: string) { } export async function getAssignedIssues(context: Context, username: string): Promise { - const { payload } = context + const { payload } = context; try { - return await context.octokit.search.issuesAndPullRequests({ - q: `is:open assignee:${username} org:${payload.repository.owner.login}`, - }).then((response) => response.data.items) as Issue[]; + return (await context.octokit.search + .issuesAndPullRequests({ + q: `is:open assignee:${username} org:${payload.repository.owner.login}`, + }) + .then((response) => response.data.items)) as Issue[]; } catch (err: unknown) { context.logger.error("Fetching assigned issues failed!", { error: err as Error }); return []; From ed2e2ddfbfddef1603a7fc48091c72baa1cf79c8 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:50:07 +0100 Subject: [PATCH 07/30] chore: update logs and test --- src/handlers/shared/start.ts | 38 +++++++++++++++++++++------------ src/handlers/user-start-stop.ts | 5 ++++- src/utils/get-linked-prs.ts | 3 +-- tests/__mocks__/handlers.ts | 7 +++++- tests/main.test.ts | 25 ++++++++++------------ 5 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index e29481a9..b086fa70 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -39,7 +39,7 @@ export async function start(context: Context, issue: Context["payload"]["issue"] logger.info(`Opened Pull Requests with approved reviews or with no reviews but over 24 hours have passed: `, { openedPullRequests }); const assignedIssues = await getAssignedIssues(context, sender.login); - logger.info("Max issue allowed is", { maxConcurrentTasks }); + logger.info("Max issue allowed is", { maxConcurrentTasks, assignedIssues: assignedIssues?.map((issue) => `${issue.url}`) }); // check for max and enforce max @@ -65,8 +65,12 @@ export async function start(context: Context, issue: Context["payload"]["issue"] if (assignees.length !== 0) { const isCurrentUserAssigned = !!assignees.find((assignee) => assignee?.login === sender.login); - const log = logger.error(isCurrentUserAssigned ? "You are already assigned to this task." : "The issue is already assigned. Please choose another unassigned task.", { issueNumber: issue.number }); - return await addCommentToIssue(context, log?.logMessage.diff as string); + const log = logger.error( + isCurrentUserAssigned ? "You are already assigned to this task." : "This issue is already assigned. Please choose another unassigned task.", + { issueNumber: issue.number } + ); + await addCommentToIssue(context, log?.logMessage.diff as string); + throw new Error(log?.logMessage.diff); } teammates.push(sender.login); @@ -94,7 +98,7 @@ export async function start(context: Context, issue: Context["payload"]["issue"] duration, priceLabel, revision: commitHash?.substring(0, 7), - teammate: teammates, + teammates: teammates, assignee: login, issue: issue.number, }); @@ -102,11 +106,17 @@ export async function start(context: Context, issue: Context["payload"]["issue"] const assignmentComment = await generateAssignmentComment(context, issue.created_at, issue.number, id, duration); const metadata = structuredMetadata.create("Assignment", logMessage); - // add assignee - if (!assignees.map((i: Partial) => i?.login).includes(login)) { - await addAssignees(context, issue.number, [login]); + const toAssign = []; + + for (const teammate of teammates) { + if (!assignees.find((assignee: Partial) => assignee?.login?.toLowerCase() === teammate.toLowerCase())) { + toAssign.push(teammate); + } } + // assign the issue + await addAssignees(context, issue.number, toAssign); + const isTaskStale = checkTaskStale(taskStaleTimeoutDuration, issue.created_at); await addCommentToIssue( @@ -128,16 +138,16 @@ export async function start(context: Context, issue: Context["payload"]["issue"] async function handleTaskLimitChecks(username: string, context: Context, maxConcurrentTasks: number, logger: Context["logger"], sender: string) { const openedPullRequests = await getAvailableOpenedPullRequests(context, username); - logger.info(`Opened Pull Requests with approved reviews or with no reviews but over 24 hours have passed: `, { openedPullRequests }); - const assignedIssues = await getAssignedIssues(context, username); - logger.info("Max issue allowed is", { maxConcurrentTasks, assignedIssues: assignedIssues.map((issue) => `${issue.url}`) }); // check for max and enforce max if (assignedIssues.length - openedPullRequests.length >= maxConcurrentTasks) { - const isSender = username === sender; - const comment = (isSender ? "You have" : `${username} has`) + ` reached the max limit of ${maxConcurrentTasks} assigned issues.`; - await addCommentToIssue(context, `\`\`\`diff\n! ${comment}\n\`\`\``); - throw new Error(`Too many assigned issues, you have reached your max limit of ${maxConcurrentTasks} issues.`); + const log = logger.error(username === sender ? "You have reached your max task limit" : `${username} has reached their max task limit`, { + assignedIssues: assignedIssues.length, + openedPullRequests: openedPullRequests.length, + maxConcurrentTasks, + }); + await addCommentToIssue(context, log?.logMessage.diff as string); + throw new Error(log?.logMessage.diff); } } diff --git a/src/handlers/user-start-stop.ts b/src/handlers/user-start-stop.ts index 23b961f8..a7c8815b 100644 --- a/src/handlers/user-start-stop.ts +++ b/src/handlers/user-start-stop.ts @@ -6,7 +6,10 @@ export async function userStartStop(context: Context): Promise<{ output: string const { payload } = context; const { issue, comment, sender, repository } = payload; const slashCommand = comment.body.split(" ")[0].replace("/", ""); - const teamMates = comment.body.split("@").slice(1).map((teamMate) => teamMate.split(" ")[0]); + const teamMates = comment.body + .split("@") + .slice(1) + .map((teamMate) => teamMate.split(" ")[0]); if (slashCommand === "stop") { return await stop(context, issue, sender, repository); diff --git a/src/utils/get-linked-prs.ts b/src/utils/get-linked-prs.ts index 205afaef..0ffd852a 100644 --- a/src/utils/get-linked-prs.ts +++ b/src/utils/get-linked-prs.ts @@ -42,6 +42,5 @@ export async function getLinkedPullRequests(context: Context, { owner, repositor state: pr.state, body: pr.body, }; - }) - .filter((pr) => pr !== null && pr.state === "open") as GetLinkedResults[]; + }).filter((pr) => pr !== null && pr.state === "open") as GetLinkedResults[]; } diff --git a/tests/__mocks__/handlers.ts b/tests/__mocks__/handlers.ts index d975b95c..24a882b1 100644 --- a/tests/__mocks__/handlers.ts +++ b/tests/__mocks__/handlers.ts @@ -81,7 +81,7 @@ export const handlers = [ db.issue.update({ where: { id: { equals: issue.id } }, data: { - assignees, + assignees: [...issue.assignees, ...assignees], }, }); } @@ -107,4 +107,9 @@ export const handlers = [ http.delete("https://api.github.com/repos/:owner/:repo/issues/:issue_number/assignees", ({ params: { owner, repo, issue_number: issueNumber } }) => HttpResponse.json({ owner, repo, issueNumber }) ), + // search issues + http.get("https://api.github.com/search/issues", () => { + const issues = [db.issue.findFirst({ where: { number: { equals: 1 } } })]; + return HttpResponse.json({ items: issues }); + }), ]; diff --git a/tests/main.test.ts b/tests/main.test.ts index ca741621..40d5cb72 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -65,9 +65,7 @@ describe("User start/stop", () => { expect(output).toEqual("Task assigned successfully"); const issue2 = db.issue.findFirst({ where: { id: { equals: 1 } } }) as unknown as Issue; - expect(issue2.assignees).toHaveLength(2); - expect(issue2.assignees).toEqual(expect.arrayContaining(["ubiquity", "user2"])); }); @@ -85,7 +83,7 @@ describe("User start/stop", () => { }); test("Stopping an issue should close the author's linked PR", async () => { - const infoSpy = jest.spyOn(console, "info").mockImplementation(() => { }); + const infoSpy = jest.spyOn(console, "info").mockImplementation(() => {}); const issue = db.issue.findFirst({ where: { id: { equals: 2 } } }) as unknown as Issue; const sender = db.users.findFirst({ where: { id: { equals: 2 } } }) as unknown as Sender; const context = createContext(issue, sender, "/stop"); @@ -138,7 +136,7 @@ describe("User start/stop", () => { context.adapters = createAdapters(getSupabase(), context as unknown as Context); - const err = "The issue is already assigned. Please choose another unassigned task."; + const err = "```diff\n! This issue is already assigned. Please choose another unassigned task.\n```"; try { await userStartStop(context as unknown as Context); @@ -472,7 +470,6 @@ async function setupTests() { state: "open", body: `Resolves #2`, html_url: "https://github.com/ubiquity/test-repo/pull/10", - state: "open", repository: { full_name: TEST_REPO, }, @@ -585,17 +582,17 @@ function getSupabase(withData = true) { single: jest.fn().mockResolvedValue({ data: withData ? { - id: 1, - wallets: { - address: "0x123", - }, - } + id: 1, + wallets: { + address: "0x123", + }, + } : { - id: 1, - wallets: { - address: undefined, + id: 1, + wallets: { + address: undefined, + }, }, - }, }), }), }), From 5b3c4d90a633cc138eff4dabf1c2f1a3929d5bcf Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 5 Aug 2024 13:55:44 +0100 Subject: [PATCH 08/30] chore: remove redunant logic --- src/handlers/shared/start.ts | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index b086fa70..0adbc284 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -1,4 +1,4 @@ -import { Assignee, Context, ISSUE_TYPE, Label } from "../../types"; +import { Context, ISSUE_TYPE, Label } from "../../types"; import { isParentIssue, getAvailableOpenedPullRequests, getAssignedIssues, addAssignees, addCommentToIssue } from "../../utils/issue"; import { calculateDurations } from "../../utils/shared"; import { checkTaskStale } from "./check-task-stale"; @@ -34,25 +34,6 @@ export async function start(context: Context, issue: Context["payload"]["issue"] logger.error("Error while getting commit hash", { error: e as Error }); } - // check max assigned issues - const openedPullRequests = await getAvailableOpenedPullRequests(context, sender.login); - logger.info(`Opened Pull Requests with approved reviews or with no reviews but over 24 hours have passed: `, { openedPullRequests }); - - const assignedIssues = await getAssignedIssues(context, sender.login); - logger.info("Max issue allowed is", { maxConcurrentTasks, assignedIssues: assignedIssues?.map((issue) => `${issue.url}`) }); - - // check for max and enforce max - - if (assignedIssues.length - openedPullRequests.length >= maxConcurrentTasks) { - const log = logger.error("Too many assigned issues, you have reached your max limit", { - assignedIssues: assignedIssues.length, - openedPullRequests: openedPullRequests.length, - maxConcurrentTasks, - }); - await addCommentToIssue(context, log?.logMessage.diff as string); - throw new Error(`Too many assigned issues, you have reached your max limit of ${maxConcurrentTasks} issues.`); - } - // is it assignable? if (issue.state === ISSUE_TYPE.CLOSED) { @@ -63,6 +44,7 @@ export async function start(context: Context, issue: Context["payload"]["issue"] const assignees = issue?.assignees ?? []; + // find out if the issue is already assigned if (assignees.length !== 0) { const isCurrentUserAssigned = !!assignees.find((assignee) => assignee?.login === sender.login); const log = logger.error( @@ -77,7 +59,6 @@ export async function start(context: Context, issue: Context["payload"]["issue"] // check max assigned issues for (const user of teammates) { - if (!user) continue; await handleTaskLimitChecks(user, context, maxConcurrentTasks, logger, sender.login); } @@ -106,16 +87,8 @@ export async function start(context: Context, issue: Context["payload"]["issue"] const assignmentComment = await generateAssignmentComment(context, issue.created_at, issue.number, id, duration); const metadata = structuredMetadata.create("Assignment", logMessage); - const toAssign = []; - - for (const teammate of teammates) { - if (!assignees.find((assignee: Partial) => assignee?.login?.toLowerCase() === teammate.toLowerCase())) { - toAssign.push(teammate); - } - } - // assign the issue - await addAssignees(context, issue.number, toAssign); + await addAssignees(context, issue.number, teammates); const isTaskStale = checkTaskStale(taskStaleTimeoutDuration, issue.created_at); From 570f60e87d5ee26e44fea8d9b0dcb1ea0202de45 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 6 Aug 2024 19:44:08 +0100 Subject: [PATCH 09/30] chore: throw errors and paginate --- src/handlers/shared/start.ts | 5 ++--- src/handlers/shared/stop.ts | 18 +++++++++++------- src/utils/issue.ts | 23 +++++++++-------------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index 0adbc284..80537eab 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -74,13 +74,12 @@ export async function start(context: Context, issue: Context["payload"]["issue"] const duration: number = calculateDurations(labels).shift() ?? 0; - const { id, login } = sender; + const { id } = sender; const logMessage = logger.info("Task assigned successfully", { duration, priceLabel, revision: commitHash?.substring(0, 7), - teammates: teammates, - assignee: login, + assignees: teammates, issue: issue.number, }); diff --git a/src/handlers/shared/stop.ts b/src/handlers/shared/stop.ts index 4f101867..5bce080e 100644 --- a/src/handlers/shared/stop.ts +++ b/src/handlers/shared/stop.ts @@ -26,16 +26,20 @@ export async function stop(context: Context, issue: Context["payload"]["issue"], // remove assignee - await context.octokit.rest.issues.removeAssignees({ - owner: login, - repo: name, - issue_number: issueNumber, - assignees: [sender.login], - }); + try { + await context.octokit.rest.issues.removeAssignees({ + owner: login, + repo: name, + issue_number: issueNumber, + assignees: [userToUnassign.login], + }); + } catch (err) { + throw new Error(`Error while removing ${userToUnassign.login} from the issue: ${err}`); + } const unassignedLog = logger.info("You have been unassigned from the task", { issueNumber, - user: sender.login, + user: userToUnassign.login, }); await addCommentToIssue(context, unassignedLog?.logMessage.diff as string); diff --git a/src/utils/issue.ts b/src/utils/issue.ts index eb8442f7..29906301 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -11,14 +11,11 @@ export async function getAssignedIssues(context: Context, username: string): Pro const { payload } = context; try { - return (await context.octokit.search - .issuesAndPullRequests({ - q: `is:open assignee:${username} org:${payload.repository.owner.login}`, - }) - .then((response) => response.data.items)) as Issue[]; + return (await context.octokit.paginate(context.octokit.search.issuesAndPullRequests, { + q: `is:issue is:open assignee:${username} org:${payload.repository.owner.login}`, + })) as Issue[]; } catch (err: unknown) { - context.logger.error("Fetching assigned issues failed!", { error: err as Error }); - return []; + throw context.logger.error("Fetching assigned issues failed!", { error: err as Error }); } } @@ -36,7 +33,7 @@ export async function addCommentToIssue(context: Context, message: string | null body: comment, }); } catch (err: unknown) { - context.logger.error("Adding a comment failed!", { error: err as Error }); + throw context.logger.error("Adding a comment failed!", { error: err as Error }); } } @@ -52,7 +49,7 @@ export async function closePullRequest(context: Context, results: GetLinkedResul state: "closed", }); } catch (err: unknown) { - context.logger.error("Closing pull requests failed!", { error: err as Error }); + throw context.logger.error("Closing pull requests failed!", { error: err as Error }); } } @@ -111,7 +108,7 @@ export async function addAssignees(context: Context, issueNo: number, assignees: const payload = context.payload; try { - await context.octokit.rest.issues.addAssignees({ + await context.octokit.issues.addAssignees({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNo, @@ -133,8 +130,7 @@ export async function getAllPullRequests(context: Context, state: "open" | "clos per_page: 100, })) as PullRequest[]; } catch (err: unknown) { - context.logger.error("Fetching all pull requests failed!", { error: err as Error }); - return []; + throw context.logger.error("Fetching all pull requests failed!", { error: err as Error }); } } @@ -155,8 +151,7 @@ export async function getAllPullRequestReviews(context: Context, pullNumber: num }, })) as Review[]; } catch (err: unknown) { - context.logger.error("Fetching all pull request reviews failed!", { error: err as Error }); - return []; + throw context.logger.error("Fetching all pull request reviews failed!", { error: err as Error }); } } From 4342e5f24abcfa10cb3e261c7db4130e54d20361 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 7 Aug 2024 00:41:31 +0100 Subject: [PATCH 10/30] feat: custom message for private issues without plan --- src/utils/issue.ts | 33 +++++++++++++++++++++++++++++++++ tests/__mocks__/handlers.ts | 8 ++++++++ 2 files changed, 41 insertions(+) diff --git a/src/utils/issue.ts b/src/utils/issue.ts index 29906301..8af20d56 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -104,6 +104,37 @@ export async function closePullRequestForAnIssue(context: Context, issueNumber: return logger.info(comment); } +async function confirmMultiAssignment(context: Context, issueNumber: number, usernames: string[]) { + const { logger, payload, octokit } = context; + + if (usernames.length < 2) { + return; + } + + const { private: isPrivate } = payload.repository; + + const { + data: { assignees }, + } = await octokit.issues.get({ + owner: payload.repository.owner.login, + repo: payload.repository.name, + issue_number: issueNumber, + }); + + if (!assignees?.length) { + const log = logger.error("We detected that this task was not assigned to anyone. Please report this to the maintainers.", { issueNumber, usernames }); + await addCommentToIssue(context, log?.logMessage.diff as string); + throw new Error(log?.logMessage.raw); + } + + if (isPrivate && assignees?.length <= 1) { + const log = logger.error("This task belongs to a private repo and can only be assigned to one user without an official paid GitHub subscription.", { + issueNumber, + }); + await addCommentToIssue(context, log?.logMessage.diff as string); + } +} + export async function addAssignees(context: Context, issueNo: number, assignees: string[]) { const payload = context.payload; @@ -117,6 +148,8 @@ export async function addAssignees(context: Context, issueNo: number, assignees: } catch (e: unknown) { throw context.logger.error("Adding the assignee failed", { assignee: assignees, issueNo, error: e as Error }); } + + await confirmMultiAssignment(context, issueNo, assignees); } export async function getAllPullRequests(context: Context, state: "open" | "closed" | "all" = "open") { diff --git a/tests/__mocks__/handlers.ts b/tests/__mocks__/handlers.ts index 24a882b1..f247b080 100644 --- a/tests/__mocks__/handlers.ts +++ b/tests/__mocks__/handlers.ts @@ -112,4 +112,12 @@ export const handlers = [ const issues = [db.issue.findFirst({ where: { number: { equals: 1 } } })]; return HttpResponse.json({ items: issues }); }), + // get issue by number + http.get("https://api.github.com/repos/:owner/:repo/issues/:issue_number", ({ params: { owner, repo, issue_number: issueNumber } }) => + HttpResponse.json( + db.issue.findFirst({ + where: { owner: { equals: owner as string }, repo: { equals: repo as string }, number: { equals: Number(issueNumber) } }, + }) + ) + ), ]; From 23f8a775ad03aa7c4b0553eca318a08d16737d5c Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Thu, 8 Aug 2024 09:37:20 +0100 Subject: [PATCH 11/30] chore: use octokit.rest --- src/handlers/shared/start.ts | 2 +- src/utils/get-linked-prs.ts | 2 +- src/utils/issue.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index 80537eab..eeb38d1e 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -24,7 +24,7 @@ export async function start(context: Context, issue: Context["payload"]["issue"] let commitHash: string | null = null; try { - const hashResponse = await context.octokit.repos.getCommit({ + const hashResponse = await context.octokit.rest.repos.getCommit({ owner: context.payload.repository.owner.login, repo: context.payload.repository.name, ref: context.payload.repository.default_branch, diff --git a/src/utils/get-linked-prs.ts b/src/utils/get-linked-prs.ts index 0ffd852a..5614ce54 100644 --- a/src/utils/get-linked-prs.ts +++ b/src/utils/get-linked-prs.ts @@ -22,7 +22,7 @@ export async function getLinkedPullRequests(context: Context, { owner, repositor throw new Error("Issue is not defined"); } - const { data: timeline } = (await context.octokit.issues.listEventsForTimeline({ + const { data: timeline } = (await context.octokit.rest.issues.listEventsForTimeline({ owner, repo: repository, issue_number: issue, diff --git a/src/utils/issue.ts b/src/utils/issue.ts index 8af20d56..fe432396 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -11,7 +11,7 @@ export async function getAssignedIssues(context: Context, username: string): Pro const { payload } = context; try { - return (await context.octokit.paginate(context.octokit.search.issuesAndPullRequests, { + return (await context.octokit.paginate(context.octokit.rest.search.issuesAndPullRequests, { q: `is:issue is:open assignee:${username} org:${payload.repository.owner.login}`, })) as Issue[]; } catch (err: unknown) { @@ -26,7 +26,7 @@ export async function addCommentToIssue(context: Context, message: string | null const issueNumber = payload.issue.number; try { - await context.octokit.issues.createComment({ + await context.octokit.rest.issues.createComment({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNumber, @@ -115,7 +115,7 @@ async function confirmMultiAssignment(context: Context, issueNumber: number, use const { data: { assignees }, - } = await octokit.issues.get({ + } = await octokit.rest.issues.get({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNumber, @@ -139,7 +139,7 @@ export async function addAssignees(context: Context, issueNo: number, assignees: const payload = context.payload; try { - await context.octokit.issues.addAssignees({ + await context.octokit.rest.issues.addAssignees({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNo, From 26fdd0e9dbd1a29e5dcf62eecd842f1e251aba4a Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:28:29 +0100 Subject: [PATCH 12/30] chore: throw logReturn and catch-all error comment --- src/handlers/shared/start.ts | 18 +++++------------- src/handlers/shared/stop.ts | 10 ++++++---- src/plugin.ts | 12 ++++++++++-- src/utils/issue.ts | 12 ++++++------ 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index eeb38d1e..e8262c78 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -18,7 +18,7 @@ export async function start(context: Context, issue: Context["payload"]["issue"] "```diff\n# Please select a child issue from the specification checklist to work on. The '/start' command is disabled on parent issues.\n```" ); logger.error(`Skipping '/start' since the issue is a parent issue`); - throw new Error("Issue is a parent issue"); + return { output: "Parent issue detected" }; } let commitHash: string | null = null; @@ -37,9 +37,7 @@ export async function start(context: Context, issue: Context["payload"]["issue"] // is it assignable? if (issue.state === ISSUE_TYPE.CLOSED) { - const log = logger.error("This issue is closed, please choose another.", { issueNumber: issue.number }); - await addCommentToIssue(context, log?.logMessage.diff as string); - throw new Error("Issue is closed"); + throw logger.error("This issue is closed, please choose another.", { issueNumber: issue.number }); } const assignees = issue?.assignees ?? []; @@ -47,12 +45,10 @@ export async function start(context: Context, issue: Context["payload"]["issue"] // find out if the issue is already assigned if (assignees.length !== 0) { const isCurrentUserAssigned = !!assignees.find((assignee) => assignee?.login === sender.login); - const log = logger.error( + throw logger.error( isCurrentUserAssigned ? "You are already assigned to this task." : "This issue is already assigned. Please choose another unassigned task.", { issueNumber: issue.number } ); - await addCommentToIssue(context, log?.logMessage.diff as string); - throw new Error(log?.logMessage.diff); } teammates.push(sender.login); @@ -67,9 +63,7 @@ export async function start(context: Context, issue: Context["payload"]["issue"] const priceLabel = labels.find((label: Label) => label.name.startsWith("Price: ")); if (!priceLabel) { - const log = logger.error("No price label is set to calculate the duration", { issueNumber: issue.number }); - await addCommentToIssue(context, log?.logMessage.diff as string); - throw new Error("No price label is set to calculate the duration"); + throw logger.error("No price label is set to calculate the duration", { issueNumber: issue.number }); } const duration: number = calculateDurations(labels).shift() ?? 0; @@ -114,12 +108,10 @@ async function handleTaskLimitChecks(username: string, context: Context, maxConc // check for max and enforce max if (assignedIssues.length - openedPullRequests.length >= maxConcurrentTasks) { - const log = logger.error(username === sender ? "You have reached your max task limit" : `${username} has reached their max task limit`, { + throw logger.error(username === sender ? "You have reached your max task limit" : `${username} has reached their max task limit`, { assignedIssues: assignedIssues.length, openedPullRequests: openedPullRequests.length, maxConcurrentTasks, }); - await addCommentToIssue(context, log?.logMessage.diff as string); - throw new Error(log?.logMessage.diff); } } diff --git a/src/handlers/shared/stop.ts b/src/handlers/shared/stop.ts index 5bce080e..633b0997 100644 --- a/src/handlers/shared/stop.ts +++ b/src/handlers/shared/stop.ts @@ -11,9 +11,7 @@ export async function stop(context: Context, issue: Context["payload"]["issue"], const userToUnassign = assignees.find((assignee: Partial) => assignee?.login?.toLowerCase() === sender.login.toLowerCase()); if (!userToUnassign) { - const log = logger.error("You are not assigned to this task", { issueNumber, user: sender.login }); - await addCommentToIssue(context, log?.logMessage.diff as string); - return { output: "You are not assigned to this task" }; + throw logger.error("You are not assigned to this task", { issueNumber, user: sender.login }); } // close PR @@ -34,7 +32,11 @@ export async function stop(context: Context, issue: Context["payload"]["issue"], assignees: [userToUnassign.login], }); } catch (err) { - throw new Error(`Error while removing ${userToUnassign.login} from the issue: ${err}`); + throw logger.error(`Error while removing ${userToUnassign.login} from the issue: `, { + err, + issueNumber, + user: userToUnassign.login, + }); } const unassignedLog = logger.info("You have been unassigned from the task", { diff --git a/src/plugin.ts b/src/plugin.ts index ec2bf094..64ae6214 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,9 +1,10 @@ import { Octokit } from "@octokit/rest"; import { createClient } from "@supabase/supabase-js"; -import { Logs } from "@ubiquity-dao/ubiquibot-logger"; +import { LogReturn, Logs } from "@ubiquity-dao/ubiquibot-logger"; import { createAdapters } from "./adapters"; import { userStartStop } from "./handlers/user-start-stop"; import { Context, Env, PluginInputs } from "./types"; +import { addCommentToIssue } from "./utils/issue"; export async function startStopTask(inputs: PluginInputs, env: Env) { const octokit = new Octokit({ auth: inputs.authToken }); @@ -22,7 +23,14 @@ export async function startStopTask(inputs: PluginInputs, env: Env) { context.adapters = createAdapters(supabase, context); if (context.eventName === "issue_comment.created") { - await userStartStop(context); + try { + return await userStartStop(context); + } catch (err) { + if (err instanceof LogReturn) { + const errorMessage = context.logger.error(`Failed to run comment evaluation. ${err.logMessage?.raw || err}`, { err }); + await addCommentToIssue(context, `${errorMessage?.logMessage.diff}\n`); + } + } } else { context.logger.error(`Unsupported event: ${context.eventName}`); } diff --git a/src/utils/issue.ts b/src/utils/issue.ts index fe432396..d019d377 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -56,8 +56,10 @@ export async function closePullRequest(context: Context, results: GetLinkedResul export async function closePullRequestForAnIssue(context: Context, issueNumber: number, repository: Context["payload"]["repository"], author: string) { const { logger } = context; if (!issueNumber) { - logger.error("Issue is not defined"); - return; + throw logger.error("Issue is not defined", { + issueNumber, + repository: repository.name, + }); } const linkedPullRequests = await getLinkedPullRequests(context, { @@ -122,13 +124,11 @@ async function confirmMultiAssignment(context: Context, issueNumber: number, use }); if (!assignees?.length) { - const log = logger.error("We detected that this task was not assigned to anyone. Please report this to the maintainers.", { issueNumber, usernames }); - await addCommentToIssue(context, log?.logMessage.diff as string); - throw new Error(log?.logMessage.raw); + throw logger.error("We detected that this task was not assigned to anyone. Please report this to the maintainers.", { issueNumber, usernames }); } if (isPrivate && assignees?.length <= 1) { - const log = logger.error("This task belongs to a private repo and can only be assigned to one user without an official paid GitHub subscription.", { + const log = logger.info("This task belongs to a private repo and can only be assigned to one user without an official paid GitHub subscription.", { issueNumber, }); await addCommentToIssue(context, log?.logMessage.diff as string); From 3f4fa4ee0ad85b6051987f39dbdd4624810fc499 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:30:19 +0100 Subject: [PATCH 13/30] chore: catch other errors --- src/plugin.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index 64ae6214..7c9b3650 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -26,10 +26,13 @@ export async function startStopTask(inputs: PluginInputs, env: Env) { try { return await userStartStop(context); } catch (err) { + let errorMessage; if (err instanceof LogReturn) { - const errorMessage = context.logger.error(`Failed to run comment evaluation. ${err.logMessage?.raw || err}`, { err }); - await addCommentToIssue(context, `${errorMessage?.logMessage.diff}\n`); + errorMessage = context.logger.error(`Failed to run comment evaluation. ${err.logMessage?.raw || err}`, { err }); + } else { + errorMessage = context.logger.error(`Failed to run comment evaluation. ${err}`, { err }); } + await addCommentToIssue(context, `${errorMessage?.logMessage.diff}\n`); } } else { context.logger.error(`Unsupported event: ${context.eventName}`); From 1f9d69365d3bbda58526781a40951303397a00bb Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:47:12 +0100 Subject: [PATCH 14/30] chore: update tests --- tests/main.test.ts | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/tests/main.test.ts b/tests/main.test.ts index 40d5cb72..dffa8591 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -9,7 +9,7 @@ import issueTemplate from "./__mocks__/issue-template"; import { createAdapters } from "../src/adapters"; import { createClient } from "@supabase/supabase-js"; import dotenv from "dotenv"; -import { Logs, cleanLogString } from "@ubiquity-dao/ubiquibot-logger"; +import { LogReturn, Logs, cleanLogString } from "@ubiquity-dao/ubiquibot-logger"; dotenv.config(); type Issue = Context["payload"]["issue"]; @@ -110,9 +110,25 @@ describe("User start/stop", () => { context.adapters = createAdapters(getSupabase(), context as unknown as Context); - const output = await userStartStop(context as unknown as Context); + const logReturn: LogReturn = { + logMessage: { + diff: "```diff\n! You are not assigned to this task\n```", + level: "error", + raw: "You are not assigned to this task", + type: "error", + }, + metadata: { + caller: "error", + issueNumber: 2, + user: "ubiquity", + }, + }; - expect(output).toEqual({ output: "You are not assigned to this task" }); + try { + expect(await userStartStop(context as unknown as Context)).toThrow(logReturn); + } catch (error) { + expect(error).toEqual(logReturn); + } }); test("User can't stop an issue without assignees", async () => { @@ -120,12 +136,27 @@ describe("User start/stop", () => { const sender = db.users.findFirst({ where: { id: { equals: 1 } } }) as unknown as Sender; const context = createContext(issue, sender, "/stop"); - context.adapters = createAdapters(getSupabase(), context as unknown as Context); - const output = await userStartStop(context as unknown as Context); + const logReturn: LogReturn = { + logMessage: { + diff: "```diff\n! You are not assigned to this task\n```", + level: "error", + raw: "You are not assigned to this task", + type: "error", + }, + metadata: { + caller: "error", + issueNumber: 5, + user: "ubiquity", + }, + }; - expect(output).toEqual({ output: "You are not assigned to this task" }); + try { + expect(await userStartStop(context as unknown as Context)).toThrow(logReturn); + } catch (error) { + expect(error).toEqual(logReturn); + } }); test("User can't start an issue that's already assigned", async () => { From e7ac474474e377a5f6f1103f08fce8050a3697a1 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 13 Aug 2024 07:10:30 +0100 Subject: [PATCH 15/30] refactor: config time strings --- src/types/plugin-input.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/types/plugin-input.ts b/src/types/plugin-input.ts index fada82bf..8e30beed 100644 --- a/src/types/plugin-input.ts +++ b/src/types/plugin-input.ts @@ -11,23 +11,18 @@ export interface PluginInputs; From 198e64a48200e5cd91343377fde53031b0751790 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 13 Aug 2024 07:27:05 +0100 Subject: [PATCH 16/30] chore: refactor config usage --- README.md | 10 +++--- src/adapters/supabase/helpers/user.ts | 2 +- src/handlers/shared/start.ts | 7 ++-- src/types/plugin-input.ts | 27 ++++++++------- src/utils/issue.ts | 18 ++++++++-- tests/main.test.ts | 50 ++++++++++----------------- 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index cf3264f8..5f13e9ac 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,10 @@ To configure your Ubiquibot to run this plugin, add the following to the `.ubiqu command: "\/start|\/stop" example: "/start" # or "/stop" with: - timers: - reviewDelayTolerance: 86000 - taskStaleTimeoutDuration: 2580000 - miscellaneous: - maxConcurrentTasks: 3 - startRequiresWallet: true # default is true + reviewDelayTolerance: "3 Days" + taskStaleTimeoutDuration: "30 Days" + maxConcurrentTasks: 3 + startRequiresWallet: true # default is true ``` # Testing diff --git a/src/adapters/supabase/helpers/user.ts b/src/adapters/supabase/helpers/user.ts index 3396ad24..635e4ff9 100644 --- a/src/adapters/supabase/helpers/user.ts +++ b/src/adapters/supabase/helpers/user.ts @@ -16,7 +16,7 @@ export class User extends Super { const { data, error } = (await this.supabase.from("users").select("wallets(*)").eq("id", userId).single()) as { data: { wallets: Wallet }; error: unknown }; if ((error && !data) || !data.wallets?.address) { this.context.logger.error("No wallet address found", { userId, issueNumber }); - if (this.context.config.miscellaneous.startRequiresWallet) { + if (this.context.config.startRequiresWallet) { await addCommentToIssue(this.context, "```diff\n! Please set your wallet address with the /wallet command first and try again.\n```"); throw new Error("No wallet address found"); } else { diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index fbe77ba9..448acedf 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -1,5 +1,5 @@ import { Assignee, Context, ISSUE_TYPE, Label } from "../../types"; -import { isParentIssue, getAvailableOpenedPullRequests, getAssignedIssues, addAssignees, addCommentToIssue } from "../../utils/issue"; +import { isParentIssue, getAvailableOpenedPullRequests, getAssignedIssues, addAssignees, addCommentToIssue, getTimeValue } from "../../utils/issue"; import { calculateDurations } from "../../utils/shared"; import { checkTaskStale } from "./check-task-stale"; import { generateAssignmentComment } from "./generate-assignment-comment"; @@ -8,8 +8,7 @@ import { assignTableComment } from "./table"; export async function start(context: Context, issue: Context["payload"]["issue"], sender: Context["payload"]["sender"]) { const { logger, config } = context; - const { maxConcurrentTasks } = config.miscellaneous; - const { taskStaleTimeoutDuration } = config.timers; + const { maxConcurrentTasks, taskStaleTimeoutDuration } = config; // is it a child issue? if (issue.body && isParentIssue(issue.body)) { @@ -93,7 +92,7 @@ export async function start(context: Context, issue: Context["payload"]["issue"] await addAssignees(context, issue.number, [login]); } - const isTaskStale = checkTaskStale(taskStaleTimeoutDuration, issue.created_at); + const isTaskStale = checkTaskStale(getTimeValue(taskStaleTimeoutDuration), issue.created_at); await addCommentToIssue( context, diff --git a/src/types/plugin-input.ts b/src/types/plugin-input.ts index 8e30beed..e9ce0b5a 100644 --- a/src/types/plugin-input.ts +++ b/src/types/plugin-input.ts @@ -11,19 +11,22 @@ export interface PluginInputs; export const startStopSettingsValidator = new StandardValidator(startStopSchema); diff --git a/src/utils/issue.ts b/src/utils/issue.ts index e6746e80..34f2157c 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -1,3 +1,4 @@ +import ms from "ms"; import { Context } from "../types/context"; import { Issue, ISSUE_TYPE, PullRequest, Review } from "../types/payload"; import { getLinkedPullRequests, GetLinkedResults } from "./get-linked-prs"; @@ -166,7 +167,7 @@ export async function getAllPullRequestReviews(context: Context, pullNumber: num } export async function getAvailableOpenedPullRequests(context: Context, username: string) { - const { reviewDelayTolerance } = context.config.timers; + const { reviewDelayTolerance } = context.config; if (!reviewDelayTolerance) return []; const openedPullRequests = await getOpenedPullRequests(context, username); @@ -183,13 +184,26 @@ export async function getAvailableOpenedPullRequests(context: Context, username: } } - if (reviews.length === 0 && (new Date().getTime() - new Date(openedPullRequest.created_at).getTime()) / (1000 * 60 * 60) >= reviewDelayTolerance) { + if ( + reviews.length === 0 && + (new Date().getTime() - new Date(openedPullRequest.created_at).getTime()) / (1000 * 60 * 60) >= getTimeValue(reviewDelayTolerance) + ) { result.push(openedPullRequest); } } return result; } +export function getTimeValue(timeString: string): number { + const timeValue = ms(timeString); + + if (timeValue === undefined) { + throw new Error("Invalid offset format"); + } + + return timeValue; +} + async function getOpenedPullRequests(context: Context, username: string): Promise> { const prs = await getAllPullRequests(context, "open"); return prs.filter((pr) => !pr.draft && (pr.user?.login === username || !username)); diff --git a/tests/main.test.ts b/tests/main.test.ts index 15181f1c..5aeeddac 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -18,13 +18,6 @@ type Sender = Context["payload"]["sender"]; const octokit = jest.requireActual("@octokit/rest"); const TEST_REPO = "ubiquity/test-repo"; -const url = process.env.SUPABASE_URL; -const key = process.env.SUPABASE_KEY; - -if (!url || !key) { - throw new Error("Supabase URL and Key are required"); -} - beforeAll(() => { server.listen(); }); @@ -66,7 +59,7 @@ describe("User start/stop", () => { }); test("Stopping an issue should close the author's linked PR", async () => { - const infoSpy = jest.spyOn(console, "info").mockImplementation(() => {}); + const infoSpy = jest.spyOn(console, "info").mockImplementation(() => { }); const issue = db.issue.findFirst({ where: { id: { equals: 2 } } }) as unknown as Issue; const sender = db.users.findFirst({ where: { id: { equals: 2 } } }) as unknown as Sender; const context = createContext(issue, sender, "/stop"); @@ -524,7 +517,7 @@ async function setupTests() { }); } -function createContext(issue: Record, sender: Record, body = "/start") { +function createContext(issue: Record, sender: Record, body = "/start"): Context { return { adapters: {} as ReturnType, payload: { @@ -532,29 +525,22 @@ function createContext(issue: Record, sender: Record Date: Tue, 13 Aug 2024 07:33:20 +0100 Subject: [PATCH 17/30] chore: refactor isTaskStale using ms --- src/handlers/shared/check-task-stale.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/handlers/shared/check-task-stale.ts b/src/handlers/shared/check-task-stale.ts index b043bee2..47e79a9e 100644 --- a/src/handlers/shared/check-task-stale.ts +++ b/src/handlers/shared/check-task-stale.ts @@ -1,9 +1,19 @@ -export function checkTaskStale(staleTask: number, createdAt: string) { - if (staleTask !== 0) { - const days = Math.floor((new Date().getTime() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24)); - const staleToDays = Math.floor(staleTask / (1000 * 60 * 60 * 24)); - return days >= staleToDays && staleToDays > 0; - } +import ms from 'ms'; - return false; +function calculateDaysDifference(date1: Date, date2: Date): number { + const millisecondsPerDay = ms('1d'); + return Math.floor((date1.getTime() - date2.getTime()) / millisecondsPerDay); } + +export function checkTaskStale(staleTaskMilliseconds: number, createdAt: string): boolean { + if (staleTaskMilliseconds === 0) { + return false; + } + + const currentDate = new Date(); + const createdDate = new Date(createdAt); + const daysSinceCreation = calculateDaysDifference(currentDate, createdDate); + const staleDaysThreshold = Math.floor(staleTaskMilliseconds / ms('1d')); + + return daysSinceCreation >= staleDaysThreshold && staleDaysThreshold > 0; +} \ No newline at end of file From 394e7aa2bc7ebac93a003ab470e198da02bffb52 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 13 Aug 2024 07:44:58 +0100 Subject: [PATCH 18/30] chore: eslint --- src/handlers/shared/check-task-stale.ts | 8 ++++---- tests/main.test.ts | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/handlers/shared/check-task-stale.ts b/src/handlers/shared/check-task-stale.ts index 47e79a9e..ae8a33e9 100644 --- a/src/handlers/shared/check-task-stale.ts +++ b/src/handlers/shared/check-task-stale.ts @@ -1,7 +1,7 @@ -import ms from 'ms'; +import ms from "ms"; function calculateDaysDifference(date1: Date, date2: Date): number { - const millisecondsPerDay = ms('1d'); + const millisecondsPerDay = ms("1d"); return Math.floor((date1.getTime() - date2.getTime()) / millisecondsPerDay); } @@ -13,7 +13,7 @@ export function checkTaskStale(staleTaskMilliseconds: number, createdAt: string) const currentDate = new Date(); const createdDate = new Date(createdAt); const daysSinceCreation = calculateDaysDifference(currentDate, createdDate); - const staleDaysThreshold = Math.floor(staleTaskMilliseconds / ms('1d')); + const staleDaysThreshold = Math.floor(staleTaskMilliseconds / ms("1d")); return daysSinceCreation >= staleDaysThreshold && staleDaysThreshold > 0; -} \ No newline at end of file +} diff --git a/tests/main.test.ts b/tests/main.test.ts index 5aeeddac..6a757d93 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -59,7 +59,7 @@ describe("User start/stop", () => { }); test("Stopping an issue should close the author's linked PR", async () => { - const infoSpy = jest.spyOn(console, "info").mockImplementation(() => { }); + const infoSpy = jest.spyOn(console, "info").mockImplementation(() => {}); const issue = db.issue.findFirst({ where: { id: { equals: 2 } } }) as unknown as Issue; const sender = db.users.findFirst({ where: { id: { equals: 2 } } }) as unknown as Sender; const context = createContext(issue, sender, "/stop"); @@ -552,17 +552,17 @@ function getSupabase(withData = true) { single: jest.fn().mockResolvedValue({ data: withData ? { - id: 1, - wallets: { - address: "0x123", - }, - } + id: 1, + wallets: { + address: "0x123", + }, + } : { - id: 1, - wallets: { - address: undefined, + id: 1, + wallets: { + address: undefined, + }, }, - }, }), }), }), From 7ee8bc362db89bdbbb11f94bdc7d4b40633575a8 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 13 Aug 2024 07:57:14 +0100 Subject: [PATCH 19/30] fix: startRequiresWallet default true --- src/types/plugin-input.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/plugin-input.ts b/src/types/plugin-input.ts index e9ce0b5a..b33868e5 100644 --- a/src/types/plugin-input.ts +++ b/src/types/plugin-input.ts @@ -16,14 +16,14 @@ export const startStopSchema = T.Object( reviewDelayTolerance: T.String({ default: "5 Days" }), taskStaleTimeoutDuration: T.String({ default: "30 Days" }), maxConcurrentTasks: T.Number({ default: 3 }), - startRequiresWallet: T.Boolean({ default: false }), + startRequiresWallet: T.Boolean({ default: true }), }, { default: { reviewDelayTolerance: "5 Days", taskStaleTimeoutDuration: "30 Days", maxConcurrentTasks: 3, - startRequiresWallet: false, + startRequiresWallet: true, }, } ); From 94f28cb2c840fcda577e1a3b15d9942b0e17930a Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:19:54 +0100 Subject: [PATCH 20/30] chore: fix default, refactor isTaskStale --- src/handlers/shared/check-task-stale.ts | 12 ++---------- src/types/plugin-input.ts | 7 +------ src/utils/issue.ts | 9 +++------ 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/handlers/shared/check-task-stale.ts b/src/handlers/shared/check-task-stale.ts index ae8a33e9..71b1ee03 100644 --- a/src/handlers/shared/check-task-stale.ts +++ b/src/handlers/shared/check-task-stale.ts @@ -1,10 +1,3 @@ -import ms from "ms"; - -function calculateDaysDifference(date1: Date, date2: Date): number { - const millisecondsPerDay = ms("1d"); - return Math.floor((date1.getTime() - date2.getTime()) / millisecondsPerDay); -} - export function checkTaskStale(staleTaskMilliseconds: number, createdAt: string): boolean { if (staleTaskMilliseconds === 0) { return false; @@ -12,8 +5,7 @@ export function checkTaskStale(staleTaskMilliseconds: number, createdAt: string) const currentDate = new Date(); const createdDate = new Date(createdAt); - const daysSinceCreation = calculateDaysDifference(currentDate, createdDate); - const staleDaysThreshold = Math.floor(staleTaskMilliseconds / ms("1d")); + const millisecondsSinceCreation = currentDate.getTime() - createdDate.getTime(); - return daysSinceCreation >= staleDaysThreshold && staleDaysThreshold > 0; + return millisecondsSinceCreation >= staleTaskMilliseconds && staleTaskMilliseconds > 0; } diff --git a/src/types/plugin-input.ts b/src/types/plugin-input.ts index b33868e5..1f254d5d 100644 --- a/src/types/plugin-input.ts +++ b/src/types/plugin-input.ts @@ -19,12 +19,7 @@ export const startStopSchema = T.Object( startRequiresWallet: T.Boolean({ default: true }), }, { - default: { - reviewDelayTolerance: "5 Days", - taskStaleTimeoutDuration: "30 Days", - maxConcurrentTasks: 3, - startRequiresWallet: true, - }, + default: {}, } ); diff --git a/src/utils/issue.ts b/src/utils/issue.ts index 34f2157c..ee41e78e 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -184,10 +184,7 @@ export async function getAvailableOpenedPullRequests(context: Context, username: } } - if ( - reviews.length === 0 && - (new Date().getTime() - new Date(openedPullRequest.created_at).getTime()) / (1000 * 60 * 60) >= getTimeValue(reviewDelayTolerance) - ) { + if (reviews.length === 0 && (new Date().getTime() - new Date(openedPullRequest.created_at).getTime()) / ms("1h") >= getTimeValue(reviewDelayTolerance)) { result.push(openedPullRequest); } } @@ -197,8 +194,8 @@ export async function getAvailableOpenedPullRequests(context: Context, username: export function getTimeValue(timeString: string): number { const timeValue = ms(timeString); - if (timeValue === undefined) { - throw new Error("Invalid offset format"); + if (!timeValue || timeValue <= 0 || isNaN(timeValue)) { + throw new Error("Invalid config time value"); } return timeValue; From 8db028c5ba4a1350879d0dff5ea4262f4b50b950 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 13 Aug 2024 23:12:46 +0100 Subject: [PATCH 21/30] chore: fix test by throwing Error --- src/handlers/shared/stop.ts | 2 +- tests/main.test.ts | 76 ++----------------------------------- 2 files changed, 4 insertions(+), 74 deletions(-) diff --git a/src/handlers/shared/stop.ts b/src/handlers/shared/stop.ts index 633b0997..3a2541e4 100644 --- a/src/handlers/shared/stop.ts +++ b/src/handlers/shared/stop.ts @@ -11,7 +11,7 @@ export async function stop(context: Context, issue: Context["payload"]["issue"], const userToUnassign = assignees.find((assignee: Partial) => assignee?.login?.toLowerCase() === sender.login.toLowerCase()); if (!userToUnassign) { - throw logger.error("You are not assigned to this task", { issueNumber, user: sender.login }); + throw new Error(logger.error("You are not assigned to this task", { issueNumber, user: sender.login })?.logMessage.diff as string); } // close PR diff --git a/tests/main.test.ts b/tests/main.test.ts index dffa8591..8c109d0e 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -9,7 +9,7 @@ import issueTemplate from "./__mocks__/issue-template"; import { createAdapters } from "../src/adapters"; import { createClient } from "@supabase/supabase-js"; import dotenv from "dotenv"; -import { LogReturn, Logs, cleanLogString } from "@ubiquity-dao/ubiquibot-logger"; +import { Logs, cleanLogString } from "@ubiquity-dao/ubiquibot-logger"; dotenv.config(); type Issue = Context["payload"]["issue"]; @@ -110,25 +110,7 @@ describe("User start/stop", () => { context.adapters = createAdapters(getSupabase(), context as unknown as Context); - const logReturn: LogReturn = { - logMessage: { - diff: "```diff\n! You are not assigned to this task\n```", - level: "error", - raw: "You are not assigned to this task", - type: "error", - }, - metadata: { - caller: "error", - issueNumber: 2, - user: "ubiquity", - }, - }; - - try { - expect(await userStartStop(context as unknown as Context)).toThrow(logReturn); - } catch (error) { - expect(error).toEqual(logReturn); - } + await expect(userStartStop(context as unknown as Context)).rejects.toThrow("```diff\n! You are not assigned to this task\n```"); }); test("User can't stop an issue without assignees", async () => { @@ -138,25 +120,7 @@ describe("User start/stop", () => { const context = createContext(issue, sender, "/stop"); context.adapters = createAdapters(getSupabase(), context as unknown as Context); - const logReturn: LogReturn = { - logMessage: { - diff: "```diff\n! You are not assigned to this task\n```", - level: "error", - raw: "You are not assigned to this task", - type: "error", - }, - metadata: { - caller: "error", - issueNumber: 5, - user: "ubiquity", - }, - }; - - try { - expect(await userStartStop(context as unknown as Context)).toThrow(logReturn); - } catch (error) { - expect(error).toEqual(logReturn); - } + await expect(userStartStop(context as unknown as Context)).rejects.toThrow("```diff\n! You are not assigned to this task\n```"); }); test("User can't start an issue that's already assigned", async () => { @@ -231,40 +195,6 @@ describe("User start/stop", () => { } }); - test("User can't start if command is disabled", async () => { - const issue = db.issue.findFirst({ where: { id: { equals: 1 } } }) as unknown as Issue; - const sender = db.users.findFirst({ where: { id: { equals: 1 } } }) as unknown as Sender; - - const context = createContext(issue, sender, "/start"); - - context.adapters = createAdapters(getSupabase(), context as unknown as Context); - - try { - await userStartStop(context as unknown as Context); - } catch (error) { - if (error instanceof Error) { - expect(error.message).toEqual("The '/start' command is disabled for this repository."); - } - } - }); - - test("User can't stop if command is disabled", async () => { - const issue = db.issue.findFirst({ where: { id: { equals: 1 } } }) as unknown as Issue; - const sender = db.users.findFirst({ where: { id: { equals: 1 } } }) as unknown as Sender; - - const context = createContext(issue, sender, "/stop"); - - context.adapters = createAdapters(getSupabase(), context as unknown as Context); - - try { - await userStartStop(context as unknown as Context); - } catch (error) { - if (error instanceof Error) { - expect(error.message).toEqual("The '/stop' command is disabled for this repository."); - } - } - }); - test("User can't start an issue that's a parent issue", async () => { const issue = db.issue.findFirst({ where: { id: { equals: 1 } } }) as unknown as Issue; const sender = db.users.findFirst({ where: { id: { equals: 1 } } }) as unknown as Sender; From 8c9d33c9e1d1cb366ed61982490ef6d9982976c5 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 14 Aug 2024 18:47:18 +0100 Subject: [PATCH 22/30] chore: sanitize metadata comment --- src/plugin.ts | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/plugin.ts b/src/plugin.ts index 7c9b3650..2117cec1 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -32,9 +32,42 @@ export async function startStopTask(inputs: PluginInputs, env: Env) { } else { errorMessage = context.logger.error(`Failed to run comment evaluation. ${err}`, { err }); } - await addCommentToIssue(context, `${errorMessage?.logMessage.diff}\n`); + + await addCommentToIssue(context, `${sanitizeDiff(errorMessage?.logMessage.diff)}\n`); } } else { context.logger.error(`Unsupported event: ${context.eventName}`); } } + +function sanitizeDiff(diff?: LogReturn["logMessage"]["diff"]): string { + if (!diff) return ""; + // eslint-disable-next-line no-useless-escape + const backticks = diff.match(/\`\`\`/g); + if (!backticks) return diff; + + // we need two sets at least and one must be at the end + + if (backticks.length < 2 || backticks.length % 2 !== 0) { + return diff; + } + + // does it end with a set of backticks? + if (diff.endsWith("```") || diff.endsWith("```\n")) { + return diff; + } + + return diff + "```"; +} + +function sanitizeMetadata(obj: LogReturn["metadata"]): string { + return JSON.stringify(obj, null, 2) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/--/g, "--") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(/\\/g, "\") + .replace(/\//g, "/"); +} From 178847011e5651f09c8075b6306f520514cc1261 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 14 Aug 2024 18:54:36 +0100 Subject: [PATCH 23/30] chore: improve sanity checks --- src/plugin.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index 2117cec1..3a26800b 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -44,20 +44,34 @@ function sanitizeDiff(diff?: LogReturn["logMessage"]["diff"]): string { if (!diff) return ""; // eslint-disable-next-line no-useless-escape const backticks = diff.match(/\`\`\`/g); - if (!backticks) return diff; + if (!backticks) return "```\n" + diff + "\n```"; + + const endsWith = diff.endsWith("```") || diff.endsWith("```\n") || diff.endsWith("``` ") // we need two sets at least and one must be at the end - if (backticks.length < 2 || backticks.length % 2 !== 0) { + if ((backticks.length === 2 || backticks.length % 2 === 0) && endsWith) { return diff; } // does it end with a set of backticks? - if (diff.endsWith("```") || diff.endsWith("```\n")) { - return diff; + if (diff.startsWith("```") && !endsWith) { + return diff + "\n```"; + } + + // does it start with a set of backticks? + + if (!diff.startsWith("```") && endsWith) { + return "```\n" + diff; + } + + // does it have a set of backticks in the middle? + + if (!diff.startsWith("```") && !endsWith) { + return "```\n" + diff + "\n```"; } - return diff + "```"; + return diff; } function sanitizeMetadata(obj: LogReturn["metadata"]): string { From a39f93d593597a72aefa29e8fdf836c1d4526d78 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Fri, 16 Aug 2024 21:41:25 +0100 Subject: [PATCH 24/30] chore: correct default --- src/types/plugin-input.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/plugin-input.ts b/src/types/plugin-input.ts index 1f254d5d..a25b4510 100644 --- a/src/types/plugin-input.ts +++ b/src/types/plugin-input.ts @@ -13,7 +13,7 @@ export interface PluginInputs Date: Mon, 19 Aug 2024 20:36:40 +0100 Subject: [PATCH 25/30] chore: remove redundant check --- src/handlers/shared/check-task-stale.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/shared/check-task-stale.ts b/src/handlers/shared/check-task-stale.ts index 71b1ee03..d9103548 100644 --- a/src/handlers/shared/check-task-stale.ts +++ b/src/handlers/shared/check-task-stale.ts @@ -7,5 +7,5 @@ export function checkTaskStale(staleTaskMilliseconds: number, createdAt: string) const createdDate = new Date(createdAt); const millisecondsSinceCreation = currentDate.getTime() - createdDate.getTime(); - return millisecondsSinceCreation >= staleTaskMilliseconds && staleTaskMilliseconds > 0; + return millisecondsSinceCreation >= staleTaskMilliseconds } From 06127cb5b2739b126858b724add2ad823a6ed4e9 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 19 Aug 2024 21:04:51 +0100 Subject: [PATCH 26/30] chore: log message --- src/plugin.ts | 41 +---------------------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index 3a26800b..617a319f 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -33,55 +33,16 @@ export async function startStopTask(inputs: PluginInputs, env: Env) { errorMessage = context.logger.error(`Failed to run comment evaluation. ${err}`, { err }); } - await addCommentToIssue(context, `${sanitizeDiff(errorMessage?.logMessage.diff)}\n`); + await addCommentToIssue(context, `${errorMessage?.logMessage.diff}\n`); } } else { context.logger.error(`Unsupported event: ${context.eventName}`); } } -function sanitizeDiff(diff?: LogReturn["logMessage"]["diff"]): string { - if (!diff) return ""; - // eslint-disable-next-line no-useless-escape - const backticks = diff.match(/\`\`\`/g); - if (!backticks) return "```\n" + diff + "\n```"; - - const endsWith = diff.endsWith("```") || diff.endsWith("```\n") || diff.endsWith("``` ") - - // we need two sets at least and one must be at the end - - if ((backticks.length === 2 || backticks.length % 2 === 0) && endsWith) { - return diff; - } - - // does it end with a set of backticks? - if (diff.startsWith("```") && !endsWith) { - return diff + "\n```"; - } - - // does it start with a set of backticks? - - if (!diff.startsWith("```") && endsWith) { - return "```\n" + diff; - } - - // does it have a set of backticks in the middle? - - if (!diff.startsWith("```") && !endsWith) { - return "```\n" + diff + "\n```"; - } - - return diff; -} - function sanitizeMetadata(obj: LogReturn["metadata"]): string { return JSON.stringify(obj, null, 2) - .replace(/&/g, "&") .replace(//g, ">") .replace(/--/g, "--") - .replace(/"/g, """) - .replace(/'/g, "'") - .replace(/\\/g, "\") - .replace(/\//g, "/"); } From 072ca24fd6dd22508c372e6fde4ce4c1c77d53f1 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 21 Aug 2024 04:12:37 +0100 Subject: [PATCH 27/30] chore: bump logger and fix bubble-up error comment --- package.json | 2 +- src/handlers/shared/check-task-stale.ts | 2 +- src/plugin.ts | 7 ++++--- yarn.lock | 10 +++++----- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 879fce61..a52f1cf5 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@octokit/webhooks": "13.2.7", "@sinclair/typebox": "^0.32.5", "@supabase/supabase-js": "2.42.0", - "@ubiquity-dao/ubiquibot-logger": "^1.3.0", + "@ubiquity-dao/ubiquibot-logger": "^1.3.1", "dotenv": "^16.4.4", "ms": "^2.1.3", "typebox-validators": "^0.3.5" diff --git a/src/handlers/shared/check-task-stale.ts b/src/handlers/shared/check-task-stale.ts index d9103548..55be3f76 100644 --- a/src/handlers/shared/check-task-stale.ts +++ b/src/handlers/shared/check-task-stale.ts @@ -7,5 +7,5 @@ export function checkTaskStale(staleTaskMilliseconds: number, createdAt: string) const createdDate = new Date(createdAt); const millisecondsSinceCreation = currentDate.getTime() - createdDate.getTime(); - return millisecondsSinceCreation >= staleTaskMilliseconds + return millisecondsSinceCreation >= staleTaskMilliseconds; } diff --git a/src/plugin.ts b/src/plugin.ts index 68640b64..9511216e 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -28,11 +28,12 @@ export async function startStopTask(inputs: PluginInputs, env: Env) { } catch (err) { let errorMessage; if (err instanceof LogReturn) { - errorMessage = context.logger.error(`Failed to run comment evaluation. ${err.logMessage?.raw || err}`, { err }); + errorMessage = err; + } else if (err instanceof Error) { + errorMessage = context.logger.error(err.message, { error: err }); } else { - errorMessage = context.logger.error(`Failed to run comment evaluation. ${err}`, { err }); + errorMessage = context.logger.error("An error occurred", { err }); } - await addCommentToIssue(context, `${errorMessage?.logMessage.diff}\n`); } } else { diff --git a/yarn.lock b/yarn.lock index 3027fb95..41474912 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2787,7 +2787,7 @@ __metadata: "@types/jest": "npm:29.5.12" "@types/ms": "npm:^0.7.34" "@types/node": "npm:20.14.5" - "@ubiquity-dao/ubiquibot-logger": "npm:^1.3.0" + "@ubiquity-dao/ubiquibot-logger": "npm:^1.3.1" cspell: "npm:8.9.0" dotenv: "npm:^16.4.4" eslint: "npm:9.5.0" @@ -2813,10 +2813,10 @@ __metadata: languageName: unknown linkType: soft -"@ubiquity-dao/ubiquibot-logger@npm:^1.3.0": - version: 1.3.0 - resolution: "@ubiquity-dao/ubiquibot-logger@npm:1.3.0" - checksum: 10c0/9150b4a633c4f49b9a5d87dce8ada521ca1a98b4cb5129209abf8bfb0a99e2a035cf42705792b453081d9476931167c5cc352be7fc70ece8e94d2cfba9a00408 +"@ubiquity-dao/ubiquibot-logger@npm:^1.3.1": + version: 1.3.1 + resolution: "@ubiquity-dao/ubiquibot-logger@npm:1.3.1" + checksum: 10c0/b6a4f2171e70126c12f1f7c0f18fe670597bf31e9c0dc19ecfe442a9118401c799a35be32e1d4785f23d0e9c1c6c0be2548769b958a5ed198d849f11520d0c57 languageName: node linkType: hard From 98687bd155da0529ab3b05f2d4997c12ce4b1866 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 21 Aug 2024 04:13:56 +0100 Subject: [PATCH 28/30] chore: remove time conversion --- src/utils/issue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/issue.ts b/src/utils/issue.ts index 2551c2a1..93d6a395 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -207,7 +207,7 @@ export async function getAvailableOpenedPullRequests(context: Context, username: } } - if (reviews.length === 0 && (new Date().getTime() - new Date(openedPullRequest.created_at).getTime()) / ms("1h") >= getTimeValue(reviewDelayTolerance)) { + if (reviews.length === 0 && (new Date().getTime() - new Date(openedPullRequest.created_at).getTime()) >= getTimeValue(reviewDelayTolerance)) { result.push(openedPullRequest); } } From 28ee0609d8d206799f39365e0b515137f73fa28f Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Wed, 21 Aug 2024 04:18:10 +0100 Subject: [PATCH 29/30] fix: hide deadline if no time label --- src/handlers/shared/generate-assignment-comment.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/shared/generate-assignment-comment.ts b/src/handlers/shared/generate-assignment-comment.ts index a38477bd..194e73df 100644 --- a/src/handlers/shared/generate-assignment-comment.ts +++ b/src/handlers/shared/generate-assignment-comment.ts @@ -15,11 +15,11 @@ export async function generateAssignmentComment(context: Context, issueCreatedAt let endTime: null | Date = null; let deadline: null | string = null; endTime = new Date(startTime + duration * 1000); - deadline = endTime.toLocaleString("en-US", options); + deadline = endTime.toLocaleString("en-US", options) return { daysElapsedSinceTaskCreation: Math.floor((startTime - new Date(issueCreatedAt).getTime()) / 1000 / 60 / 60 / 24), - deadline, + deadline: duration > 0 ? deadline : null, registeredWallet: (await context.adapters.supabase.user.getWalletByUserId(senderId, issueNumber)) || "Register your wallet address using the following slash command: `/wallet 0x0000...0000`", From 1891d112a00797f223d114def5e64052bb108d7e Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Sat, 24 Aug 2024 12:10:47 +0100 Subject: [PATCH 30/30] chore: formatting --- src/handlers/shared/generate-assignment-comment.ts | 2 +- src/utils/issue.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/shared/generate-assignment-comment.ts b/src/handlers/shared/generate-assignment-comment.ts index 194e73df..7c35c6e9 100644 --- a/src/handlers/shared/generate-assignment-comment.ts +++ b/src/handlers/shared/generate-assignment-comment.ts @@ -15,7 +15,7 @@ export async function generateAssignmentComment(context: Context, issueCreatedAt let endTime: null | Date = null; let deadline: null | string = null; endTime = new Date(startTime + duration * 1000); - deadline = endTime.toLocaleString("en-US", options) + deadline = endTime.toLocaleString("en-US", options); return { daysElapsedSinceTaskCreation: Math.floor((startTime - new Date(issueCreatedAt).getTime()) / 1000 / 60 / 60 / 24), diff --git a/src/utils/issue.ts b/src/utils/issue.ts index 93d6a395..9c4e1610 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -207,7 +207,7 @@ export async function getAvailableOpenedPullRequests(context: Context, username: } } - if (reviews.length === 0 && (new Date().getTime() - new Date(openedPullRequest.created_at).getTime()) >= getTimeValue(reviewDelayTolerance)) { + if (reviews.length === 0 && new Date().getTime() - new Date(openedPullRequest.created_at).getTime() >= getTimeValue(reviewDelayTolerance)) { result.push(openedPullRequest); } }