From 07ce84510e9a6be16412e2a9534f0ddc8adeb472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Fri, 8 Dec 2023 17:36:23 +0900 Subject: [PATCH 01/21] fix: unassign handler but root cause not diagnosed --- src/handlers/wildcard/unassign/unassign.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/handlers/wildcard/unassign/unassign.ts b/src/handlers/wildcard/unassign/unassign.ts index c1bc15b56..336c789e1 100644 --- a/src/handlers/wildcard/unassign/unassign.ts +++ b/src/handlers/wildcard/unassign/unassign.ts @@ -72,12 +72,21 @@ async function checkTaskToUnassign(context: Context, assignedIssue: Issue) { return assignedEvent.assignee.login === login; } }); - // get latest assign event by checking created_at - const latestAssignEvent = assignEventsOfAssignee.reduce((latestEvent, currentEvent) => { - const latestEventTime = new Date(latestEvent.created_at).getTime(); - const currentEventTime = new Date(currentEvent.created_at).getTime(); - return currentEventTime > latestEventTime ? currentEvent : latestEvent; - }, assignEventsOfAssignee[0]); + let latestAssignEvent; + + if (assignEventsOfAssignee.length > 0) { + latestAssignEvent = assignEventsOfAssignee.reduce((latestEvent, currentEvent) => { + const latestEventTime = new Date(latestEvent.created_at).getTime(); + const currentEventTime = new Date(currentEvent.created_at).getTime(); + return currentEventTime > latestEventTime ? currentEvent : latestEvent; + }, assignEventsOfAssignee[0]); + } else { + // Handle the case where there are no assign events + // This could be setting latestAssignEvent to a default value or throwing an error + throw logger.debug("No assign events found when there are supposed to be assign events.", { + issueNumber: assignedIssue.number, + }); + } const latestAssignEventTime = new Date(latestAssignEvent.created_at).getTime(); const now = Date.now(); From 678dcb9bcb8247af67c959b29f29c66ced00f25b Mon Sep 17 00:00:00 2001 From: Korrrba Date: Fri, 8 Dec 2023 14:07:31 +0100 Subject: [PATCH 02/21] feat: switch logging to ubiquibot-logging NPM module --- package.json | 1 + src/adapters/adapters.ts | 2 +- src/adapters/supabase/helpers/pretty-logs.ts | 188 ---------- src/adapters/supabase/helpers/tables/logs.ts | 376 ------------------- src/bindings/bot-runtime.ts | 2 +- src/bindings/event.ts | 2 +- src/helpers/issue.ts | 2 +- src/types/context.ts | 2 +- src/types/handlers.ts | 2 +- yarn.lock | 17 +- 10 files changed, 23 insertions(+), 571 deletions(-) delete mode 100644 src/adapters/supabase/helpers/pretty-logs.ts delete mode 100644 src/adapters/supabase/helpers/tables/logs.ts diff --git a/package.json b/package.json index 7e426a83d..e2905b144 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "probot": "^12.2.4", "smee-client": "^2.0.0", "tsx": "^3.12.7", + "ubiquibot-logger": "^0.1.0", "yaml": "^2.2.2", "zlib": "^1.0.5" }, diff --git a/src/adapters/adapters.ts b/src/adapters/adapters.ts index c3857d53a..a0ccb9592 100644 --- a/src/adapters/adapters.ts +++ b/src/adapters/adapters.ts @@ -2,7 +2,7 @@ import { createClient } from "@supabase/supabase-js"; import { Access } from "./supabase/helpers/tables/access"; import { Label } from "./supabase/helpers/tables/label"; import { Locations } from "./supabase/helpers/tables/locations"; -import { Logs } from "./supabase/helpers/tables/logs"; +import { Logs } from "ubiquibot-logger"; import { Settlement } from "./supabase/helpers/tables/settlement"; import { Super } from "./supabase/helpers/tables/super"; import { User } from "./supabase/helpers/tables/user"; diff --git a/src/adapters/supabase/helpers/pretty-logs.ts b/src/adapters/supabase/helpers/pretty-logs.ts deleted file mode 100644 index 0e02046c8..000000000 --- a/src/adapters/supabase/helpers/pretty-logs.ts +++ /dev/null @@ -1,188 +0,0 @@ -import util from "util"; -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -type PrettyLogsWithOk = "ok" | LogLevel; -export class PrettyLogs { - constructor() { - this.ok = this.ok.bind(this); - this.info = this.info.bind(this); - this.error = this.error.bind(this); - this.fatal = this.fatal.bind(this); - this.debug = this.debug.bind(this); - this.verbose = this.verbose.bind(this); - } - public fatal(message: string, metadata?: any) { - this._logWithStack(LogLevel.FATAL, message, metadata); - } - - public error(message: string, metadata?: any) { - this._logWithStack(LogLevel.ERROR, message, metadata); - } - - public ok(message: string, metadata?: any) { - this._logWithStack("ok", message, metadata); - } - - public info(message: string, metadata?: any) { - this._logWithStack(LogLevel.INFO, message, metadata); - } - - public debug(message: string, metadata?: any) { - this._logWithStack(LogLevel.DEBUG, message, metadata); - } - - public verbose(message: string, metadata?: any) { - this._logWithStack(LogLevel.VERBOSE, message, metadata); - } - - private _logWithStack(type: "ok" | LogLevel, message: string, metadata?: Metadata | string) { - this._log(type, message); - if (typeof metadata === "string") { - this._log(type, metadata); - return; - } - if (metadata) { - let stack = metadata?.error?.stack || metadata?.stack; - if (!stack) { - // generate and remove the top four lines of the stack trace - const stackTrace = new Error().stack?.split("\n"); - if (stackTrace) { - stackTrace.splice(0, 4); - stack = stackTrace.filter((line) => line.includes(".ts:")).join("\n"); - } - } - const newMetadata = { ...metadata }; - delete newMetadata.message; - delete newMetadata.name; - delete newMetadata.stack; - - if (!this._isEmpty(newMetadata)) { - this._log(type, newMetadata); - } - - if (typeof stack == "string") { - const prettyStack = this._formatStackTrace(stack, 1); - const colorizedStack = this._colorizeText(prettyStack, Colors.dim); - this._log(type, colorizedStack); - } else if (stack) { - const prettyStack = this._formatStackTrace((stack as unknown as string[]).join("\n"), 1); - const colorizedStack = this._colorizeText(prettyStack, Colors.dim); - this._log(type, colorizedStack); - } else { - throw new Error("Stack is null"); - } - } - } - - private _colorizeText(text: string, color: Colors): string { - if (!color) { - throw new Error(`Invalid color: ${color}`); - } - return color.concat(text).concat(Colors.reset); - } - - private _formatStackTrace(stack: string, linesToRemove = 0, prefix = ""): string { - const lines = stack.split("\n"); - for (let i = 0; i < linesToRemove; i++) { - lines.shift(); // Remove the top line - } - return lines - .map((line) => `${prefix}${line.replace(/\s*at\s*/, " ↳ ")}`) // Replace 'at' and prefix every line - .join("\n"); - } - - private _isEmpty(obj: Record) { - return !Reflect.ownKeys(obj).some((key) => typeof obj[String(key)] !== "function"); - } - - private _log(type: PrettyLogsWithOk, message: any) { - const defaultSymbols: Record = { - fatal: "×", - ok: "✓", - error: "⚠", - info: "›", - debug: "››", - verbose: "💬", - }; - - const symbol = defaultSymbols[type]; - - // Formatting the message - const messageFormatted = - typeof message === "string" - ? message - : util.inspect(message, { showHidden: true, depth: null, breakLength: Infinity }); - // const messageFormatted = - // typeof message === "string" ? message : JSON.stringify(Logs.convertErrorsIntoObjects(message)); - - // Constructing the full log string with the prefix symbol - const lines = messageFormatted.split("\n"); - const logString = lines - .map((line, index) => { - // Add the symbol only to the first line and keep the indentation for the rest - const prefix = index === 0 ? `\t${symbol}` : `\t${" ".repeat(symbol.length)}`; - return `${prefix} ${line}`; - }) - .join("\n"); - - const fullLogString = logString; - - const colorMap: Record = { - fatal: ["error", Colors.fgRed], - ok: ["log", Colors.fgGreen], - error: ["warn", Colors.fgYellow], - info: ["info", Colors.dim], - debug: ["debug", Colors.fgMagenta], - verbose: ["debug", Colors.dim], - }; - - const _console = console[colorMap[type][0] as keyof typeof console] as (...args: string[]) => void; - if (typeof _console === "function") { - _console(this._colorizeText(fullLogString, colorMap[type][1])); - } else { - throw new Error(fullLogString); - } - } -} -interface Metadata { - error?: { stack?: string }; - stack?: string; - message?: string; - name?: string; - [key: string]: any; -} - -enum Colors { - reset = "\x1b[0m", - bright = "\x1b[1m", - dim = "\x1b[2m", - underscore = "\x1b[4m", - blink = "\x1b[5m", - reverse = "\x1b[7m", - hidden = "\x1b[8m", - - fgBlack = "\x1b[30m", - fgRed = "\x1b[31m", - fgGreen = "\x1b[32m", - fgYellow = "\x1b[33m", - fgBlue = "\x1b[34m", - fgMagenta = "\x1b[35m", - fgCyan = "\x1b[36m", - fgWhite = "\x1b[37m", - - bgBlack = "\x1b[40m", - bgRed = "\x1b[41m", - bgGreen = "\x1b[42m", - bgYellow = "\x1b[43m", - bgBlue = "\x1b[44m", - bgMagenta = "\x1b[45m", - bgCyan = "\x1b[46m", - bgWhite = "\x1b[47m", -} -export enum LogLevel { - FATAL = "fatal", - ERROR = "error", - INFO = "info", - VERBOSE = "verbose", - DEBUG = "debug", -} diff --git a/src/adapters/supabase/helpers/tables/logs.ts b/src/adapters/supabase/helpers/tables/logs.ts deleted file mode 100644 index 4ac3393a2..000000000 --- a/src/adapters/supabase/helpers/tables/logs.ts +++ /dev/null @@ -1,376 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -// This is disabled because logs should be able to log any type of data -// Normally this is forbidden - -import { SupabaseClient } from "@supabase/supabase-js"; -import { Context as ProbotContext } from "probot"; -import Runtime from "../../../../bindings/bot-runtime"; -import { COMMIT_HASH } from "../../../../commit-hash"; -import { Database } from "../../types/database"; - -import { LogLevel, PrettyLogs } from "../pretty-logs"; - -type LogFunction = (message: string, metadata?: any) => void; -type LogInsert = Database["public"]["Tables"]["logs"]["Insert"]; -type LogParams = { - level: LogLevel; - consoleLog: LogFunction; - logMessage: string; - metadata?: any; - postComment?: boolean; - type: PublicMethods; -}; -export class LogReturn { - logMessage: LogMessage; - metadata?: any; - - constructor(logMessage: LogMessage, metadata?: any) { - this.logMessage = logMessage; - this.metadata = metadata; - } -} - -type FunctionPropertyNames = { - [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; -}[keyof T]; - -type PublicMethods = Exclude, "constructor" | keyof object>; - -export type LogMessage = { raw: string; diff: string; level: LogLevel; type: PublicMethods }; - -export class Logs { - private _supabase: SupabaseClient; - private _context: ProbotContext | null = null; - - private _maxLevel = -1; - private _queue: LogInsert[] = []; // Your log queue - private _concurrency = 6; // Maximum concurrent requests - private _retryDelay = 1000; // Delay between retries in milliseconds - private _throttleCount = 0; - private _retryLimit = 0; // Retries disabled by default - - static console: PrettyLogs; - - private _log({ level, consoleLog, logMessage, metadata, postComment, type }: LogParams): LogReturn | null { - if (this._getNumericLevel(level) > this._maxLevel) return null; // filter out more verbose logs according to maxLevel set in config - - // needs to generate three versions of the information. - // they must all first serialize the error object if it exists - // - the comment to post on supabase (must be raw) - // - the comment to post on github (must include diff syntax) - // - the comment to post on the console (must be colorized) - - consoleLog(logMessage, metadata || undefined); - - if (this._context && postComment) { - const colorizedCommentMessage = this._diffColorCommentMessage(type, logMessage); - const commentMetaData = metadata ? Logs._commentMetaData(metadata, level) : null; - this._postComment(metadata ? [colorizedCommentMessage, commentMetaData].join("\n") : colorizedCommentMessage); - } - - const toSupabase = { log: logMessage, level, metadata } as LogInsert; - - this._save(toSupabase); - - return new LogReturn( - { - raw: logMessage, - diff: this._diffColorCommentMessage(type, logMessage), - type, - level, - }, - metadata - ); - } - private _addDiagnosticInformation(metadata: any) { - // this is a utility function to get the name of the function that called the log - // I have mixed feelings on this because it manipulates metadata later possibly without the developer understanding why and where, - // but seems useful for the metadata parser to understand where the comment originated from - - if (!metadata) { - metadata = {}; - } - if (typeof metadata == "string" || typeof metadata == "number") { - // TODO: think i need to support every data type - metadata = { message: metadata }; - } - - const stackLines = new Error().stack?.split("\n") || []; - if (stackLines.length > 3) { - const callerLine = stackLines[3]; // .replace(process.cwd(), ""); - const match = callerLine.match(/at (\S+)/); - if (match) { - metadata.caller = match[1]; - } - } - - const gitCommit = COMMIT_HASH?.substring(0, 7) ?? null; - metadata.revision = gitCommit; - - return metadata; - } - - public ok(log: string, metadata?: any, postComment?: boolean): LogReturn | null { - metadata = this._addDiagnosticInformation(metadata); - return this._log({ - level: LogLevel.INFO, - consoleLog: Logs.console.ok, - logMessage: log, - metadata, - postComment, - type: "ok", - }); - } - - public info(log: string, metadata?: any, postComment?: boolean): LogReturn | null { - metadata = this._addDiagnosticInformation(metadata); - return this._log({ - level: LogLevel.INFO, - consoleLog: Logs.console.info, - logMessage: log, - metadata, - postComment, - type: "info", - }); - } - - public error(log: string, metadata?: any, postComment?: boolean): LogReturn | null { - metadata = this._addDiagnosticInformation(metadata); - return this._log({ - level: LogLevel.ERROR, - consoleLog: Logs.console.error, - logMessage: log, - metadata, - postComment, - type: "error", - }); - } - - public debug(log: string, metadata?: any, postComment?: boolean): LogReturn | null { - metadata = this._addDiagnosticInformation(metadata); - return this._log({ - level: LogLevel.DEBUG, - consoleLog: Logs.console.debug, - logMessage: log, - metadata, - postComment, - type: "debug", - }); - } - - public fatal(log: string, metadata?: any, postComment?: boolean): LogReturn | null { - if (!metadata) { - metadata = Logs.convertErrorsIntoObjects(new Error(log)); - const stack = metadata.stack as string[]; - stack.splice(1, 1); - metadata.stack = stack; - } - if (metadata instanceof Error) { - metadata = Logs.convertErrorsIntoObjects(metadata); - const stack = metadata.stack as string[]; - stack.splice(1, 1); - metadata.stack = stack; - } - - metadata = this._addDiagnosticInformation(metadata); - return this._log({ - level: LogLevel.FATAL, - consoleLog: Logs.console.fatal, - logMessage: log, - metadata, - postComment, - type: "fatal", - }); - } - - verbose(log: string, metadata?: any, postComment?: boolean): LogReturn | null { - metadata = this._addDiagnosticInformation(metadata); - return this._log({ - level: LogLevel.VERBOSE, - consoleLog: Logs.console.verbose, - logMessage: log, - metadata, - postComment, - type: "verbose", - }); - } - - constructor(supabase: SupabaseClient, retryLimit: number, logLevel: LogLevel, context: ProbotContext | null) { - this._supabase = supabase; - this._context = context; - this._retryLimit = retryLimit; - this._maxLevel = this._getNumericLevel(logLevel); - Logs.console = new PrettyLogs(); - } - - private async _sendLogsToSupabase(log: LogInsert) { - const { error } = await this._supabase.from("logs").insert(log); - if (error) throw Logs.console.fatal("Error logging to Supabase:", error); - } - - private async _processLogs(log: LogInsert) { - try { - await this._sendLogsToSupabase(log); - } catch (error) { - Logs.console.fatal("Error sending log, retrying:", error); - return this._retryLimit > 0 ? await this._retryLog(log) : null; - } - } - - private async _retryLog(log: LogInsert, retryCount = 0) { - if (retryCount >= this._retryLimit) { - Logs.console.fatal("Max retry limit reached for log:", log); - return; - } - - await new Promise((resolve) => setTimeout(resolve, this._retryDelay)); - - try { - await this._sendLogsToSupabase(log); - } catch (error) { - Logs.console.fatal("Error sending log (after retry):", error); - await this._retryLog(log, retryCount + 1); - } - } - - private async _processLogQueue() { - while (this._queue.length > 0) { - const log = this._queue.shift(); - if (!log) { - continue; - } - await this._processLogs(log); - } - } - - private async _throttle() { - if (this._throttleCount >= this._concurrency) { - return; - } - - this._throttleCount++; - try { - await this._processLogQueue(); - } finally { - this._throttleCount--; - if (this._queue.length > 0) { - await this._throttle(); - } - } - } - - private async _addToQueue(log: LogInsert) { - this._queue.push(log); - if (this._throttleCount < this._concurrency) { - await this._throttle(); - } - } - - private _save(logInsert: LogInsert) { - this._addToQueue(logInsert) - .then(() => void 0) - .catch(() => Logs.console.fatal("Error adding logs to queue")); - - Logs.console.ok(logInsert.log, logInsert); - } - - static _commentMetaData(metadata: any, level: LogLevel) { - Runtime.getState().logger.debug("the main place that metadata is being serialized as an html comment"); - const prettySerialized = JSON.stringify(metadata, null, 2); - // first check if metadata is an error, then post it as a json comment - // otherwise post it as an html comment - if (level === LogLevel.FATAL) { - return ["```json", prettySerialized, "```"].join("\n"); - } else { - return [""].join("\n"); - } - } - - private _diffColorCommentMessage(type: string, message: string) { - const diffPrefix = { - fatal: "-", // - text in red - ok: "+", // + text in green - error: "!", // ! text in orange - // info: "#", // # text in gray - // debug: "@@@@",// @@ text in purple (and bold)@@ - // error: null, - // warn: null, - // info: null, - // verbose: "#", - // debug: "#", - }; - const selected = diffPrefix[type as keyof typeof diffPrefix]; - - if (selected) { - message = message - .trim() // Remove leading and trailing whitespace - .split("\n") - .map((line) => `${selected} ${line}`) - .join("\n"); - } else if (type === "debug") { - // debug has special formatting - message = message - .split("\n") - .map((line) => `@@ ${line} @@`) - .join("\n"); // debug: "@@@@", - } else { - // default to gray - message = message - .split("\n") - .map((line) => `# ${line}`) - .join("\n"); - } - - const diffHeader = "```diff"; - const diffFooter = "```"; - - return [diffHeader, message, diffFooter].join("\n"); - } - - private _postComment(message: string) { - // post on issue - if (!this._context) return; - this._context.octokit.issues - .createComment({ - owner: this._context.issue().owner, - repo: this._context.issue().repo, - issue_number: this._context.issue().issue_number, - body: message, - }) - // .then((x) => console.trace(x)) - .catch((x) => console.trace(x)); - } - - private _getNumericLevel(level: LogLevel) { - switch (level) { - case LogLevel.FATAL: - return 0; - case LogLevel.ERROR: - return 1; - case LogLevel.INFO: - return 2; - case LogLevel.VERBOSE: - return 4; - case LogLevel.DEBUG: - return 5; - default: - return -1; // Invalid level - } - } - static convertErrorsIntoObjects(obj: any): any { - // this is a utility function to render native errors in the console, the database, and on GitHub. - if (obj instanceof Error) { - return { - message: obj.message, - name: obj.name, - stack: obj.stack ? obj.stack.split("\n") : null, - }; - } else if (typeof obj === "object" && obj !== null) { - const keys = Object.keys(obj); - keys.forEach((key) => { - obj[key] = this.convertErrorsIntoObjects(obj[key]); - }); - } - return obj; - } -} diff --git a/src/bindings/bot-runtime.ts b/src/bindings/bot-runtime.ts index 650fc0925..8fadaba14 100644 --- a/src/bindings/bot-runtime.ts +++ b/src/bindings/bot-runtime.ts @@ -1,5 +1,5 @@ import { createAdapters } from "../adapters/adapters"; -import { Logs } from "../adapters/supabase/helpers/tables/logs"; +import { Logs } from "ubiquibot-logger"; class Runtime { private static _instance: Runtime; diff --git a/src/bindings/event.ts b/src/bindings/event.ts index 434299c77..d578e74b5 100644 --- a/src/bindings/event.ts +++ b/src/bindings/event.ts @@ -2,7 +2,7 @@ import OpenAI from "openai"; import { Context as ProbotContext } from "probot"; import zlib from "zlib"; import { createAdapters, supabaseClient } from "../adapters/adapters"; -import { LogMessage, LogReturn, Logs } from "../adapters/supabase/helpers/tables/logs"; +import { LogMessage, LogReturn, Logs } from "ubiquibot-logger"; import { processors, wildcardProcessors } from "../handlers/processors"; import { validateConfigChange } from "../handlers/push/push"; import structuredMetadata from "../handlers/shared/structured-metadata"; diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index 767d3dab7..c2d075166 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -1,4 +1,4 @@ -import { LogReturn } from "../adapters/supabase/helpers/tables/logs"; +import { LogReturn } from "ubiquibot-logger"; import { Context } from "../types/context"; import { HandlerReturnValuesNoVoid } from "../types/handlers"; import { StreamlinedComment } from "../types/openai"; diff --git a/src/types/context.ts b/src/types/context.ts index 6db26f5b1..e6f4bf3a5 100644 --- a/src/types/context.ts +++ b/src/types/context.ts @@ -2,7 +2,7 @@ import { Context as ProbotContext, ProbotOctokit } from "probot"; import OpenAI from "openai"; import { BotConfig } from "./configuration-types"; import { Payload } from "./payload"; -import { Logs } from "../adapters/supabase/helpers/tables/logs"; +import { Logs } from "ubiquibot-logger"; export interface Context { event: ProbotContext; diff --git a/src/types/handlers.ts b/src/types/handlers.ts index a8c6cfaa8..698f47e34 100644 --- a/src/types/handlers.ts +++ b/src/types/handlers.ts @@ -1,4 +1,4 @@ -import { LogReturn } from "../adapters/supabase/helpers/tables/logs"; +import { LogReturn } from "ubiquibot-logger"; import { Context } from "./context"; export type HandlerReturnValuesNoVoid = null | string | LogReturn; diff --git a/yarn.lock b/yarn.lock index d7b4cead2..7332c54a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4227,6 +4227,11 @@ dotenv@*, dotenv@^8.2.0: resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz" integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== +dotenv@^16.3.1: + version "16.3.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" + integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" @@ -7183,7 +7188,7 @@ pino-pretty@^6.0.0: split2 "^3.1.1" strip-json-comments "^3.1.1" -pino-std-serializers@*, pino-std-serializers@^6.0.0: +pino-std-serializers@*, pino-std-serializers@^6.0.0, pino-std-serializers@^6.2.2: version "6.2.2" resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz" integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== @@ -8295,6 +8300,16 @@ typescript@^4.9.5: resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +ubiquibot-logger@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ubiquibot-logger/-/ubiquibot-logger-0.1.0.tgz#946f0480e32968bdc076a83a7e29fb967b35ee1f" + integrity sha512-N7PAd8yqRUiLeiRy/Rdco3wyGLaoMjBqbXlUT1Ryp8vJ434l4sJXgy99ql41WEhFyiOBPfbFp5DESI6Fefl6QQ== + dependencies: + "@supabase/supabase-js" "^2.4.0" + dotenv "^16.3.1" + pino-std-serializers "^6.2.2" + probot "^12.2.4" + uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz" From c4c5986f20728d18461577e74ef966dbe7b74daa Mon Sep 17 00:00:00 2001 From: Korrrba Date: Fri, 8 Dec 2023 15:13:31 +0100 Subject: [PATCH 03/21] feat: enable pretty logs import with ubiquity-logger v0.3.0 --- package.json | 2 +- src/types/configuration-types.ts | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index e2905b144..c75775019 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "probot": "^12.2.4", "smee-client": "^2.0.0", "tsx": "^3.12.7", - "ubiquibot-logger": "^0.1.0", + "ubiquibot-logger": "^0.3.0", "yaml": "^2.2.2", "zlib": "^1.0.5" }, diff --git a/src/types/configuration-types.ts b/src/types/configuration-types.ts index 0246c7000..c1c417c57 100644 --- a/src/types/configuration-types.ts +++ b/src/types/configuration-types.ts @@ -1,6 +1,6 @@ import { ObjectOptions, Static, StaticDecode, StringOptions, TProperties, Type as T } from "@sinclair/typebox"; import ms from "ms"; -import { LogLevel } from "../adapters/supabase/helpers/pretty-logs"; +import { LogLevel } from "ubiquibot-logger/pretty-logs"; import { userCommands } from "../handlers/comment/handlers/comment-handler-main"; import { validHTMLElements } from "../handlers/comment/handlers/issue/valid-html-elements"; import { ajv } from "../utils/ajv"; diff --git a/yarn.lock b/yarn.lock index 7332c54a2..20d593683 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8300,10 +8300,10 @@ typescript@^4.9.5: resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -ubiquibot-logger@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ubiquibot-logger/-/ubiquibot-logger-0.1.0.tgz#946f0480e32968bdc076a83a7e29fb967b35ee1f" - integrity sha512-N7PAd8yqRUiLeiRy/Rdco3wyGLaoMjBqbXlUT1Ryp8vJ434l4sJXgy99ql41WEhFyiOBPfbFp5DESI6Fefl6QQ== +ubiquibot-logger@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/ubiquibot-logger/-/ubiquibot-logger-0.3.0.tgz#ae296958daae3169bfde2866379216621c7c41f5" + integrity sha512-F3BExAEPfQrHK2GvR/T+9F+F5Q2dlv2dwiSkVdww4sqtj25oOpeL/uT8mUSI6xGWoh4Oo41KvoECTw1tELcn0Q== dependencies: "@supabase/supabase-js" "^2.4.0" dotenv "^16.3.1" From db96aca83fc40a1b215c8eebfc938c31d8038076 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Mon, 11 Dec 2023 12:14:47 +0100 Subject: [PATCH 04/21] feat: nft rewards --- .../supabase/helpers/tables/settlement.ts | 8 +++++--- src/adapters/supabase/helpers/tables/wallet.ts | 10 ++++++---- src/handlers/pricing/pricing-label.ts | 16 ++++++++++------ src/types/configuration-types.ts | 1 + 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/adapters/supabase/helpers/tables/settlement.ts b/src/adapters/supabase/helpers/tables/settlement.ts index 98a30502b..8cc979535 100644 --- a/src/adapters/supabase/helpers/tables/settlement.ts +++ b/src/adapters/supabase/helpers/tables/settlement.ts @@ -60,7 +60,7 @@ export class Settlement extends Super { const { data: debitInsertData, error: debitError } = await this.supabase .from("debits") .insert(debitData) - .select("*") + .select() .maybeSingle(); if (debitError) throw new Error(debitError.message); @@ -77,6 +77,7 @@ export class Settlement extends Super { const { data: settlementInsertData, error: settlementError } = await this.supabase .from("settlements") .insert(settlementData) + .select() .maybeSingle(); if (settlementError) throw new Error(settlementError.message); @@ -99,7 +100,7 @@ export class Settlement extends Super { const { data: creditInsertData, error: creditError } = await this.supabase .from("credits") .insert(creditData) - .select("*") + .select() .maybeSingle(); if (creditError) throw new Error(creditError.message); @@ -121,7 +122,7 @@ export class Settlement extends Super { beneficiary_id: userId, }; - const permitResult = await this.supabase.from("permits").insert(permitData).select("*").maybeSingle(); + const permitResult = await this.supabase.from("permits").insert(permitData).select().maybeSingle(); if (permitResult.error) throw new Error(permitResult.error.message); if (!permitResult.data) throw new Error("Permit not inserted"); @@ -149,6 +150,7 @@ export class Settlement extends Super { const { data: settlementInsertData, error: settlementError } = await this.supabase .from("settlements") .insert(settlementData) + .select() .maybeSingle(); if (settlementError) throw new Error(settlementError.message); diff --git a/src/adapters/supabase/helpers/tables/wallet.ts b/src/adapters/supabase/helpers/tables/wallet.ts index d650c9a3c..9b0c4347a 100644 --- a/src/adapters/supabase/helpers/tables/wallet.ts +++ b/src/adapters/supabase/helpers/tables/wallet.ts @@ -32,17 +32,16 @@ export class Wallet extends Super { | ProbotContext<"issue_comment.edited">["payload"]; const userData = await this._getUserData(payload); - const registeredWalletData = await this._getRegisteredWalletData(userData); - const locationMetaData = this._getLocationMetaData(payload); - if (!registeredWalletData) { + if (!userData.wallet_id) { await this._registerNewWallet({ address, locationMetaData, payload, }); } else { + const registeredWalletData = await this._getRegisteredWalletData(userData); await this._updateExistingWallet({ address, locationMetaData, @@ -87,12 +86,13 @@ export class Wallet extends Super { const { data: locationData, error: locationError } = (await this.supabase .from("locations") .insert(locationMetaData) + .select() .single()) as { data: LocationRow; error: PostgrestError | null }; if (locationError) { throw new Error(locationError.message); } - + console.log(locationData); // Get the ID of the inserted location const locationId = locationData.id; @@ -100,6 +100,7 @@ export class Wallet extends Super { const { data: userData, error: userError } = await this.supabase .from("users") .insert([{ id: user.id, location_id: locationId /* other fields if necessary */ }]) + .select() .single(); if (userError) { @@ -167,6 +168,7 @@ export class Wallet extends Super { const { data: walletInsertData, error: walletInsertError } = await this.supabase .from("wallets") .insert(newWallet) + .select() .single(); if (walletInsertError) throw walletInsertError; diff --git a/src/handlers/pricing/pricing-label.ts b/src/handlers/pricing/pricing-label.ts index d371fdbce..2d001f88a 100644 --- a/src/handlers/pricing/pricing-label.ts +++ b/src/handlers/pricing/pricing-label.ts @@ -152,10 +152,14 @@ async function addPriceLabelToIssue(context: Context, targetPriceLabel: string, export async function labelExists(context: Context, name: string): Promise { const payload = context.event.payload as Payload; - const res = await context.event.octokit.rest.issues.getLabel({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - name, - }); - return res.status === 200; + try { + await context.event.octokit.rest.issues.getLabel({ + owner: payload.repository.owner.login, + repo: payload.repository.name, + name, + }); + return true; + } catch (error) { + return false; + } } diff --git a/src/types/configuration-types.ts b/src/types/configuration-types.ts index 0246c7000..4914e3c62 100644 --- a/src/types/configuration-types.ts +++ b/src/types/configuration-types.ts @@ -78,6 +78,7 @@ const botConfigSchema = strictObject( setLabel: T.Boolean({ default: true }), fundExternalClosedIssue: T.Boolean({ default: true }), }), + isNftRewardEnabled: T.Boolean({ default: false }), }), timers: strictObject({ From 06b3981f8bab2355d75412968dd66fdaa17e2444 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Mon, 11 Dec 2023 12:19:49 +0100 Subject: [PATCH 05/21] fix: remove console log --- src/adapters/supabase/helpers/tables/wallet.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/adapters/supabase/helpers/tables/wallet.ts b/src/adapters/supabase/helpers/tables/wallet.ts index 9b0c4347a..9f9bee7e8 100644 --- a/src/adapters/supabase/helpers/tables/wallet.ts +++ b/src/adapters/supabase/helpers/tables/wallet.ts @@ -92,7 +92,6 @@ export class Wallet extends Super { if (locationError) { throw new Error(locationError.message); } - console.log(locationData); // Get the ID of the inserted location const locationId = locationData.id; From a7034915869f1c0547427bbe081402908d7977d9 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sun, 31 Dec 2023 14:52:22 +0100 Subject: [PATCH 06/21] feat: update ubiquibot logger and fix start command --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c75775019..14502c798 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "start:serverless": "tsx src/adapters/github/github-actions.ts", "start:watch": "nodemon --exec 'yarn start'", "utils:cspell": "cspell --config .cspell.json 'src/**/*.{js,ts,json,md,yml}'", - "start": "probot run ./dist/index.js", + "start": "probot run ./dist/main.js", "prepare": "husky install", "test": "jest" }, @@ -64,7 +64,7 @@ "probot": "^12.2.4", "smee-client": "^2.0.0", "tsx": "^3.12.7", - "ubiquibot-logger": "^0.3.0", + "ubiquibot-logger": "^0.3.5", "yaml": "^2.2.2", "zlib": "^1.0.5" }, diff --git a/yarn.lock b/yarn.lock index 20d593683..aeab960b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8300,10 +8300,10 @@ typescript@^4.9.5: resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -ubiquibot-logger@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/ubiquibot-logger/-/ubiquibot-logger-0.3.0.tgz#ae296958daae3169bfde2866379216621c7c41f5" - integrity sha512-F3BExAEPfQrHK2GvR/T+9F+F5Q2dlv2dwiSkVdww4sqtj25oOpeL/uT8mUSI6xGWoh4Oo41KvoECTw1tELcn0Q== +ubiquibot-logger@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/ubiquibot-logger/-/ubiquibot-logger-0.3.5.tgz#8ecad7f159a72a6e71b4ba136dc00a120cffaf1b" + integrity sha512-1zk/CRjUp2hZvJi+0mN6JOJhqyY66nio0TfNRAANhBHUbkMpYKV70plAHo9O9iCfdOjFGFtRhT9sHmcv1W7ElA== dependencies: "@supabase/supabase-js" "^2.4.0" dotenv "^16.3.1" From 3014776a0566ab95d73fd4ba20ca97352673bb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Tue, 16 Jan 2024 18:48:37 +0900 Subject: [PATCH 07/21] chore: merge --- src/handlers/pricing/pricing-label.ts | 12 +- src/handlers/wildcard/unassign/unassign.ts | 404 --------------------- 2 files changed, 1 insertion(+), 415 deletions(-) diff --git a/src/handlers/pricing/pricing-label.ts b/src/handlers/pricing/pricing-label.ts index a388821f5..37c0e2fbc 100644 --- a/src/handlers/pricing/pricing-label.ts +++ b/src/handlers/pricing/pricing-label.ts @@ -1,5 +1,6 @@ import { Context } from "../../types/context"; +import { addLabelToIssue, clearAllPriceLabelsOnIssue } from "../../helpers/issue"; import { createLabel } from "../../helpers/label"; import { BotConfig } from "../../types/configuration-types"; import { Label } from "../../types/label"; @@ -7,7 +8,6 @@ import { GitHubPayload, UserType } from "../../types/payload"; import { labelAccessPermissionsCheck } from "../access/labels-access"; import { setPrice } from "../shared/pricing"; import { handleParentIssue, isParentIssue, sortLabelsByValue } from "./handle-parent-issue"; -import { clearAllPriceLabelsOnIssue, addLabelToIssue } from "../../helpers/issue"; export async function onLabelChangeSetPricing(context: Context): Promise { const config = context.config; @@ -142,16 +142,7 @@ async function addPriceLabelToIssue(context: Context, targetPriceLabel: string) } export async function labelExists(context: Context, name: string): Promise { -<<<<<<< HEAD const payload = context.event.payload as GitHubPayload; - const res = await context.event.octokit.rest.issues.getLabel({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - name, - }); - return res.status === 200; -======= - const payload = context.event.payload as Payload; try { await context.event.octokit.rest.issues.getLabel({ owner: payload.repository.owner.login, @@ -162,7 +153,6 @@ export async function labelExists(context: Context, name: string): Promise>>>>>> a7034915869f1c0547427bbe081402908d7977d9 } async function getAllLabeledEvents(context: Context) { diff --git a/src/handlers/wildcard/unassign/unassign.ts b/src/handlers/wildcard/unassign/unassign.ts index 87806726d..0f493c018 100644 --- a/src/handlers/wildcard/unassign/unassign.ts +++ b/src/handlers/wildcard/unassign/unassign.ts @@ -1,407 +1,3 @@ import { RestEndpointMethodTypes } from "@octokit/rest"; -<<<<<<< HEAD export type IssuesListEventsResponseData = RestEndpointMethodTypes["issues"]["listEvents"]["response"]["data"]; -======= -type IssuesListEventsResponseData = RestEndpointMethodTypes["issues"]["listEvents"]["response"]["data"]; -// type Commit[] = Commit[]; // RestEndpointMethodTypes["pulls"]["listCommits"]["response"]["data"]; - -export async function checkTasksToUnassign(context: Context) { - const logger = context.logger; - const issuesAndPullsOpened = await listAllIssuesAndPullsForRepo(context, IssueType.OPEN); - const assignedIssues = issuesAndPullsOpened.filter((issue) => issue.assignee); - - const tasksToUnassign = await Promise.all( - assignedIssues.map(async (assignedIssue: Issue) => checkTaskToUnassign(context, assignedIssue)) - ); - logger.ok("Checked all the tasks to unassign", { - tasksToUnassign: tasksToUnassign.filter(Boolean).map((task) => task?.metadata), - }); -} - -async function checkTaskToUnassign(context: Context, assignedIssue: Issue) { - const logger = context.logger; - const payload = context.event.payload as Payload; - const { - timers: { taskDisqualifyDuration, taskFollowUpDuration }, - } = context.config; - - logger.info("Checking for neglected tasks", { issueNumber: assignedIssue.number }); - - if (!assignedIssue.assignees) { - throw logger.error("No assignees found when there are supposed to be assignees.", { - issueNumber: assignedIssue.number, - }); - } - const assignees = assignedIssue.assignees.filter((item): item is User => item !== null); - - const assigneeLoginsOnly = assignees.map((assignee) => assignee.login); - - const login = payload.repository.owner.login; - const name = payload.repository.name; - const number = assignedIssue.number; - - // DONE: check events - e.g. https://api.github.com/repos/ubiquity/ubiquibot/issues/644/events?per_page=100 - - const { assigneeEvents, assigneeCommits } = await aggregateAssigneeActivity({ - context, - login, - name, - number, - assignees: assigneeLoginsOnly, - }); - - // Check if the assignee did any "event activity" or commit within the timeout window - const { activeAssigneesInDisqualifyDuration, activeAssigneesInFollowUpDuration } = getActiveAssignees( - assigneeLoginsOnly, - assigneeEvents, - taskDisqualifyDuration, - assigneeCommits, - taskFollowUpDuration - ); - - // assigneeEvents - - const assignEventsOfAssignee = assigneeEvents.filter((event) => { - // check if the event is an assign event and if the assignee is the same as the assignee we're checking - if (event.event == "assigned") { - const assignedEvent = event as AssignedEvent; - return assignedEvent.assignee.login === login; - } - }); - let latestAssignEvent; - - if (assignEventsOfAssignee.length > 0) { - latestAssignEvent = assignEventsOfAssignee.reduce((latestEvent, currentEvent) => { - const latestEventTime = new Date(latestEvent.created_at).getTime(); - const currentEventTime = new Date(currentEvent.created_at).getTime(); - return currentEventTime > latestEventTime ? currentEvent : latestEvent; - }, assignEventsOfAssignee[0]); - } else { - // Handle the case where there are no assign events - // This could be setting latestAssignEvent to a default value or throwing an error - throw logger.debug("No assign events found when there are supposed to be assign events.", { - issueNumber: assignedIssue.number, - }); - } - - const latestAssignEventTime = new Date(latestAssignEvent.created_at).getTime(); - const now = Date.now(); - - const assigneesWithinGracePeriod = assignees.filter(() => now - latestAssignEventTime < taskDisqualifyDuration); - - const assigneesOutsideGracePeriod = assignees.filter((assignee) => !assigneesWithinGracePeriod.includes(assignee)); - - const disqualifiedAssignees = await disqualifyIdleAssignees(context, { - assignees: assigneesOutsideGracePeriod.map((assignee) => assignee.login), - activeAssigneesInDisqualifyDuration, - login, - name, - number, - }); - - // DONE: follow up with those who are in `assignees` and not inside of `disqualifiedAssignees` or `activeAssigneesInFollowUpDuration` - await followUpWithTheRest(context, { - assignees: assigneesOutsideGracePeriod.map((assignee) => assignee.login), - disqualifiedAssignees, - activeAssigneesInFollowUpDuration, - login, - name, - number, - taskDisqualifyDuration, - }); - - return logger.ok("Checked task to unassign", { - issueNumber: assignedIssue.number, - disqualifiedAssignees, - }); -} - -async function followUpWithTheRest( - context: Context, - { - assignees, - disqualifiedAssignees, - activeAssigneesInFollowUpDuration, - login, - name, - number, - taskDisqualifyDuration, - }: FollowUpWithTheRest -) { - const followUpAssignees = assignees.filter( - (assignee) => !disqualifiedAssignees.includes(assignee) && !activeAssigneesInFollowUpDuration.includes(assignee) - ); - - if (followUpAssignees.length > 0) { - const followUpMessage = `@${followUpAssignees.join( - ", @" - )}, this task has been idle for a while. Please provide an update.`; - - // Fetch recent comments - const hasRecentFollowUp = await checkIfFollowUpAlreadyPosted( - context, - login, - name, - number, - followUpMessage, - taskDisqualifyDuration - ); - - if (!hasRecentFollowUp) { - try { - await context.event.octokit.rest.issues.createComment({ - owner: login, - repo: name, - issue_number: number, - body: followUpMessage, - }); - context.logger.info("Followed up with idle assignees", { followUpAssignees }); - } catch (e: unknown) { - context.logger.error("Failed to follow up with idle assignees", e); - } - } - } -} - -async function checkIfFollowUpAlreadyPosted( - context: Context, - login: string, - name: string, - number: number, - followUpMessage: string, - disqualificationPeriod: number -) { - const comments = await context.event.octokit.rest.issues.listComments({ - owner: login, - repo: name, - issue_number: number, - }); - - // Get the current time - const now = new Date().getTime(); - - // Check if a similar comment has already been posted within the disqualification period - const hasRecentFollowUp = comments.data.some( - (comment) => - comment.body === followUpMessage && - comment?.user?.type === "Bot" && - now - new Date(comment.created_at).getTime() <= disqualificationPeriod - ); - return hasRecentFollowUp; -} - -async function aggregateAssigneeActivity({ context, login, name, number, assignees }: AggregateAssigneeActivity) { - const allEvents = await getAllEvents({ context, owner: login, repo: name, issueNumber: number }); - const assigneeEvents = allEvents.filter((event) => assignees.includes(event.actor.login)); // Filter all events by assignees - - // check the linked pull request and then check that pull request's commits - - const linkedPullRequests = await getLinkedPullRequests(context, { owner: login, repository: name, issue: number }); - - const allCommits = [] as Commit[]; - for (const pullRequest of linkedPullRequests) { - try { - const commits = await getAllCommitsFromPullRequest({ - context, - owner: login, - repo: name, - pullNumber: pullRequest.number, - }); - allCommits.push(...commits); - } catch (error) { - console.trace({ error }); - // return []; - } - } - - // DONE: check commits - e.g. https://api.github.com/repos/ubiquity/ubiquibot/pulls/644/commits?per_page=100 - - // Filter all commits by assignees - const assigneeCommits = allCommits.filter((commit) => { - const name = commit.author?.login || commit.commit.committer?.name; - if (!name) { - return false; - } - assignees.includes(name); - }); - return { assigneeEvents, assigneeCommits }; -} - -async function disqualifyIdleAssignees( - context: Context, - { assignees, activeAssigneesInDisqualifyDuration, login, name, number }: DisqualifyIdleAssignees -) { - const idleAssignees = assignees.filter((assignee) => !activeAssigneesInDisqualifyDuration.includes(assignee)); - - if (idleAssignees.length > 0) { - try { - await context.event.octokit.rest.issues.removeAssignees({ - owner: login, - repo: name, - issue_number: number, - assignees: idleAssignees, - }); - context.logger.info("Unassigned idle assignees", { idleAssignees }); - } catch (e: unknown) { - context.logger.error("Failed to unassign idle assignees", e); - } - } - return idleAssignees; -} - -function getActiveAssignees( - assignees: string[], - assigneeEvents: IssuesListEventsResponseData, - taskDisqualifyDuration: number, - assigneeCommits: Commit[], - taskFollowUpDuration: number -) { - const activeAssigneesInDisqualifyDuration = getActiveAssigneesInDisqualifyDuration( - assignees, - assigneeEvents, - taskDisqualifyDuration, - assigneeCommits - ); - - const activeAssigneesInFollowUpDuration = getActiveAssigneesInFollowUpDuration( - assignees, - assigneeEvents, - taskFollowUpDuration, - assigneeCommits, - taskDisqualifyDuration - ); - - return { - activeAssigneesInDisqualifyDuration, - activeAssigneesInFollowUpDuration, - }; -} - -function getActiveAssigneesInFollowUpDuration( - assignees: string[], - assigneeEvents: IssuesListEventsResponseData, - taskFollowUpDuration: number, - assigneeCommits: Commit[], - taskDisqualifyDuration: number -) { - return assignees.filter(() => { - const assigneeEventsWithinDuration = assigneeEvents.filter( - (event) => new Date().getTime() - new Date(event.created_at).getTime() <= taskFollowUpDuration - ); - const assigneeCommitsWithinDuration = assigneeCommits.filter((commit) => { - const date = commit.commit.author?.date || commit.commit.committer?.date || ""; - return date && new Date().getTime() - new Date(date).getTime() <= taskDisqualifyDuration; - }); - return assigneeEventsWithinDuration.length === 0 && assigneeCommitsWithinDuration.length === 0; - }); -} - -function getActiveAssigneesInDisqualifyDuration( - assignees: string[], - assigneeEvents: IssuesListEventsResponseData, - taskDisqualifyDuration: number, - assigneeCommits: Commit[] -) { - return assignees.filter(() => { - const assigneeEventsWithinDuration = assigneeEvents.filter( - (event) => new Date().getTime() - new Date(event.created_at).getTime() <= taskDisqualifyDuration - ); - - const assigneeCommitsWithinDuration = assigneeCommits.filter((commit) => { - const date = commit.commit.author?.date || commit.commit.committer?.date || ""; - return date && new Date().getTime() - new Date(date).getTime() <= taskDisqualifyDuration; - }); - return assigneeEventsWithinDuration.length === 0 && assigneeCommitsWithinDuration.length === 0; - }); -} - -async function getAllEvents({ context, owner, repo, issueNumber }: GetAllEvents) { - try { - const events = (await context.octokit.paginate( - context.octokit.rest.issues.listEvents, - { - owner, - repo, - issue_number: issueNumber, - per_page: 100, - }, - (response) => response.data.filter((event) => isCorrectType(event as IssuesListEventsResponseData[0])) - )) as IssuesListEventsResponseData; - return events; - } catch (err: unknown) { - context.logger.error("Failed to fetch lists of events", err); - return []; - } -} - -async function getAllCommitsFromPullRequest({ context, owner, repo, pullNumber }: GetAllCommits) { - try { - const commits = (await context.octokit.paginate(context.octokit.pulls.listCommits, { - owner, - repo, - pull_number: pullNumber, - per_page: 100, - })) as Commit[]; - return commits; - } catch (err: unknown) { - context.logger.error("Failed to fetch lists of commits", err); - return []; - } -} - -function isCorrectType(event: IssuesListEventsResponseData[0]) { - return event && typeof event.id === "number"; -} - -interface DisqualifyIdleAssignees { - assignees: string[]; - activeAssigneesInDisqualifyDuration: string[]; - login: string; - name: string; - number: number; -} - -interface FollowUpWithTheRest { - assignees: string[]; - disqualifiedAssignees: string[]; - activeAssigneesInFollowUpDuration: string[]; - login: string; - name: string; - number: number; - taskDisqualifyDuration: number; -} - -interface AggregateAssigneeActivity { - context: Context; - login: string; - name: string; - number: number; - assignees: string[]; -} -interface GetAllEvents { - context: Context; - owner: string; - repo: string; - issueNumber: number; -} -interface GetAllCommits { - context: Context; - owner: string; - repo: string; - pullNumber: number; -} -type AssignedEvent = { - id: number; - node_id: string; - url: string; - actor: User; - event: "assigned"; - commit_id: null; - commit_url: null; - created_at: string; - assignee: User; - assigner: User; - performed_via_github_app: null; -}; ->>>>>>> a7034915869f1c0547427bbe081402908d7977d9 From eefbee925896ff367e2ae14307281d28a7105613 Mon Sep 17 00:00:00 2001 From: HARALD Date: Fri, 19 Jan 2024 04:23:37 -0500 Subject: [PATCH 08/21] feat: fix duplication price label issue --- src/handlers/pricing/pricing-label.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/handlers/pricing/pricing-label.ts b/src/handlers/pricing/pricing-label.ts index 37c0e2fbc..0dd16371b 100644 --- a/src/handlers/pricing/pricing-label.ts +++ b/src/handlers/pricing/pricing-label.ts @@ -1,7 +1,7 @@ import { Context } from "../../types/context"; import { addLabelToIssue, clearAllPriceLabelsOnIssue } from "../../helpers/issue"; -import { createLabel } from "../../helpers/label"; +import { createLabel, listLabelsForRepo } from "../../helpers/label"; import { BotConfig } from "../../types/configuration-types"; import { Label } from "../../types/label"; import { GitHubPayload, UserType } from "../../types/payload"; @@ -116,7 +116,10 @@ async function handleTargetPriceLabel(context: Context, targetPriceLabel: string if (_targetPriceLabel) { await handleExistingPriceLabel(context, targetPriceLabel); } else { - await createLabel(context, targetPriceLabel, "price"); + const allLabels = await listLabelsForRepo(context); + if (allLabels.filter((i) => i.name.includes(targetPriceLabel)).length === 0) { + await createLabel(context, targetPriceLabel, "price"); + } await addPriceLabelToIssue(context, targetPriceLabel); } } From 8aaf055c091cc3e7f2b42c9f1cf88f45b1d86d17 Mon Sep 17 00:00:00 2001 From: HARALD Date: Mon, 22 Jan 2024 14:19:50 -0500 Subject: [PATCH 09/21] feat: fix build errors --- src/handlers/shared/structured-metadata.ts | 2 +- src/handlers/wildcard/unassign/assign-event-found.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/shared/structured-metadata.ts b/src/handlers/shared/structured-metadata.ts index 38c8b9842..548bbd4c6 100644 --- a/src/handlers/shared/structured-metadata.ts +++ b/src/handlers/shared/structured-metadata.ts @@ -1,4 +1,4 @@ -import { LogLevel } from "../../adapters/supabase/helpers/pretty-logs"; +import { LogLevel } from "ubiquibot-logger/pretty-logs"; import { COMMIT_HASH } from "../../commit-hash"; function createStructuredMetadata(className: string, metadata: any) { diff --git a/src/handlers/wildcard/unassign/assign-event-found.ts b/src/handlers/wildcard/unassign/assign-event-found.ts index 94ed15b29..acf51154e 100644 --- a/src/handlers/wildcard/unassign/assign-event-found.ts +++ b/src/handlers/wildcard/unassign/assign-event-found.ts @@ -1,4 +1,4 @@ -import { Logs } from "../../../adapters/supabase/helpers/tables/logs"; +import { Logs } from "ubiquibot-logger"; import { Context } from "../../../types/context"; import { GitHubAssignEvent, GitHubUser } from "../../../types/payload"; import { disqualifyIdleAssignees } from "./disqualify-idle-assignees"; From 19ec41c02ee2f7692c4ce243b4c7c64221fbfca7 Mon Sep 17 00:00:00 2001 From: HARALD Date: Mon, 22 Jan 2024 22:51:01 -0500 Subject: [PATCH 10/21] feat: fix CI erros --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2952dc26..74786cc0b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,10 @@ jobs: uses: actions/setup-node@v3 with: node-version: "20.10.0" + cache: "yarn" + + - name: Install Yarn 4.0.2 + run: npm install -g yarn@4.0.2 - name: Install run: yarn install From f87a8960b92503329d54287c643fa729db961898 Mon Sep 17 00:00:00 2001 From: FUTURE Date: Mon, 22 Jan 2024 22:52:45 -0500 Subject: [PATCH 11/21] Update build.yml --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2952dc26..74786cc0b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,10 @@ jobs: uses: actions/setup-node@v3 with: node-version: "20.10.0" + cache: "yarn" + + - name: Install Yarn 4.0.2 + run: npm install -g yarn@4.0.2 - name: Install run: yarn install From 136b11daed20f527453e059e205acd0c0f679c80 Mon Sep 17 00:00:00 2001 From: FUTURE Date: Mon, 22 Jan 2024 22:55:17 -0500 Subject: [PATCH 12/21] Update build.yml --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 74786cc0b..37977a039 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,8 +17,8 @@ jobs: node-version: "20.10.0" cache: "yarn" - - name: Install Yarn 4.0.2 - run: npm install -g yarn@4.0.2 + - name: Enable Corepack + run: corepack enable - name: Install run: yarn install From 73c57819868e139283892ffe16bd439201ab78ad Mon Sep 17 00:00:00 2001 From: HARALD Date: Mon, 22 Jan 2024 22:57:29 -0500 Subject: [PATCH 13/21] feat: minor change --- .github/workflows/build.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 74786cc0b..c2952dc26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,10 +15,6 @@ jobs: uses: actions/setup-node@v3 with: node-version: "20.10.0" - cache: "yarn" - - - name: Install Yarn 4.0.2 - run: npm install -g yarn@4.0.2 - name: Install run: yarn install From fa4c9b908e8509aee320f98bfa80512079b555fc Mon Sep 17 00:00:00 2001 From: FUTURE Date: Mon, 22 Jan 2024 23:00:37 -0500 Subject: [PATCH 14/21] Update build.yml --- .github/workflows/build.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37977a039..e008eef54 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,15 +11,16 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Pre-configure Yarn Version + run: | + npm install -g yarn@4.0.2 + - name: Setup Node uses: actions/setup-node@v3 with: node-version: "20.10.0" cache: "yarn" - - name: Enable Corepack - run: corepack enable - - name: Install run: yarn install From f16bf6d25d84a6d29d8152074a2ce3bed9bee2b4 Mon Sep 17 00:00:00 2001 From: HARALD Date: Mon, 22 Jan 2024 23:01:48 -0500 Subject: [PATCH 15/21] feat: update build.yml --- .github/workflows/build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2952dc26..e008eef54 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,10 +11,15 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Pre-configure Yarn Version + run: | + npm install -g yarn@4.0.2 + - name: Setup Node uses: actions/setup-node@v3 with: node-version: "20.10.0" + cache: "yarn" - name: Install run: yarn install From 7fbd28046e2da0eb2596a3115d7985619d1dc599 Mon Sep 17 00:00:00 2001 From: FUTURE Date: Mon, 22 Jan 2024 23:03:54 -0500 Subject: [PATCH 16/21] Update build.yml --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e008eef54..04844d727 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,9 +11,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Pre-configure Yarn Version + - name: Enable Corepack and Install Correct Yarn Version run: | - npm install -g yarn@4.0.2 + corepack enable + yarn set version 4.0.2 - name: Setup Node uses: actions/setup-node@v3 From d0d4230de8ececc38ccf05c5ff9c5dafef8036c7 Mon Sep 17 00:00:00 2001 From: HARALD Date: Mon, 22 Jan 2024 23:04:05 -0500 Subject: [PATCH 17/21] feat: update build.yml --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e008eef54..04844d727 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,9 +11,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Pre-configure Yarn Version + - name: Enable Corepack and Install Correct Yarn Version run: | - npm install -g yarn@4.0.2 + corepack enable + yarn set version 4.0.2 - name: Setup Node uses: actions/setup-node@v3 From 6b2912586fad8ec6b9544bda8dae0d3677c87093 Mon Sep 17 00:00:00 2001 From: FUTURE Date: Mon, 22 Jan 2024 23:05:54 -0500 Subject: [PATCH 18/21] Update build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 04844d727..a78595890 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: run: yarn build - name: Lint - run: yarn lint + run: yarn format run-migration: runs-on: ubuntu-latest From 9d3c91ccbe13b5eb09f5c76d59e1e2d34551746d Mon Sep 17 00:00:00 2001 From: HARALD Date: Mon, 22 Jan 2024 23:08:01 -0500 Subject: [PATCH 19/21] feat: update --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 04844d727..a78595890 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: run: yarn build - name: Lint - run: yarn lint + run: yarn format run-migration: runs-on: ubuntu-latest From 663138cf0093b61fa5343082da087655767655ee Mon Sep 17 00:00:00 2001 From: HARALD Date: Mon, 29 Jan 2024 12:37:53 -0500 Subject: [PATCH 20/21] feat: remove axios call --- src/handlers/assign/check-pull-requests.ts | 33 +++++++++---- src/helpers/get-linked-pull-requests.ts | 55 ++++++++++------------ 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/src/handlers/assign/check-pull-requests.ts b/src/handlers/assign/check-pull-requests.ts index 4625aedd4..f5f0b3c14 100644 --- a/src/handlers/assign/check-pull-requests.ts +++ b/src/handlers/assign/check-pull-requests.ts @@ -1,5 +1,3 @@ -import axios from "axios"; -import { HTMLElement, parse } from "node-html-parser"; import { getAllPullRequests, addAssignees } from "../../helpers/issue"; import { Context } from "../../types/context"; @@ -15,6 +13,7 @@ export async function checkPullRequests(context: Context) { // Loop through the pull requests and assign them to their respective issues if needed for (const pull of pulls) { const linkedIssue = await getLinkedIssues({ + context, owner: payload.repository.owner.login, repository: payload.repository.name, pull: pull.number, @@ -60,18 +59,31 @@ export async function checkPullRequests(context: Context) { return logger.debug(`Checking pull requests done!`); } -export async function getLinkedIssues({ owner, repository, pull }: GetLinkedParams) { - const { data } = await axios.get(`https://github.com/${owner}/${repository}/pull/${pull}`); - const dom = parse(data); - const devForm = dom.querySelector("[data-target='create-branch.developmentForm']") as HTMLElement; - const linkedIssues = devForm.querySelectorAll(".my-1"); +export async function getLinkedIssues({ context, owner, repository, pull }: GetLinkedParams) { + if (!pull || !context) return null; + const { data } = await context.octokit.pulls.get({ + owner, + repo: repository, + pull_number: pull, + }); - if (linkedIssues.length === 0) { + const body = data.body; + if (!body) return null; + + const match = body.match(/#(\d+)/); + const issueNumber = match ? match[1] : null; + + if (!issueNumber) { return null; } - const issueUrl = linkedIssues[0].querySelector("a")?.attrs?.href || null; - return issueUrl; + const issue = await context.octokit.issues.get({ + owner, + repo: repository, + issue_number: Number(issueNumber), + }); + + return issue.data.html_url; } export async function getPullByNumber(context: Context, pull: number) { @@ -105,6 +117,7 @@ export async function getIssueByNumber(context: Context, issueNumber: number) { } } export interface GetLinkedParams { + context?: Context; owner: string; repository: string; issue?: number; diff --git a/src/helpers/get-linked-pull-requests.ts b/src/helpers/get-linked-pull-requests.ts index c265d81c8..7c9d89b8b 100644 --- a/src/helpers/get-linked-pull-requests.ts +++ b/src/helpers/get-linked-pull-requests.ts @@ -1,7 +1,6 @@ -import axios from "axios"; -import { HTMLElement, parse } from "node-html-parser"; -import { GetLinkedParams } from "../handlers/assign/check-pull-requests"; +import { GetLinkedParams, getLinkedIssues } from "../handlers/assign/check-pull-requests"; import { Context } from "../types/context"; +import { getAllPullRequests } from "./issue"; interface GetLinkedResults { organization: string; repository: string; @@ -12,37 +11,31 @@ export async function getLinkedPullRequests( context: Context, { owner, repository, issue }: GetLinkedParams ): Promise { - const logger = context.logger; + if (!issue) return []; + // const logger = context.logger; const collection = [] as GetLinkedResults[]; - const { data } = await axios.get(`https://github.com/${owner}/${repository}/issues/${issue}`); - const dom = parse(data); - const devForm = dom.querySelector("[data-target='create-branch.developmentForm']") as HTMLElement; - const linkedList = devForm.querySelectorAll(".my-1"); - if (linkedList.length === 0) { - context.logger.info(`No linked pull requests found`); - return []; - } - - for (const linked of linkedList) { - const relativeHref = linked.querySelector("a")?.attrs?.href; - if (!relativeHref) continue; - const parts = relativeHref.split("/"); - - // check if array size is at least 4 - if (parts.length < 4) continue; + const pulls = await getAllPullRequests(context); + const currentIssue = await context.octokit.issues.get({ + owner, + repo: repository, + issue_number: issue, + }); + for (const pull of pulls) { + const linkedIssue = await getLinkedIssues({ + context, + owner: owner, + repository: repository, + pull: pull.number, + }); - // extract the organization name and repo name from the link:(e.g. " - const organization = parts[parts.length - 4]; - const repository = parts[parts.length - 3]; - const number = Number(parts[parts.length - 1]); - const href = `https://github.com${relativeHref}`; - - if (`${organization}/${repository}` !== `${owner}/${repository}`) { - logger.info("Skipping linked pull request from another repository", href); - continue; + if (linkedIssue === currentIssue.data.html_url) { + collection.push({ + organization: owner, + repository, + number: pull.number, + href: pull.html_url, + }); } - - collection.push({ organization, repository, number, href }); } return collection; From 9bc158340dbd1e232ac7fe7d511c22d82407df59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= <4975670+pavlovcik@users.noreply.github.com> Date: Fri, 2 Feb 2024 19:47:18 +0900 Subject: [PATCH 21/21] Revert "feat: remove axios call" --- src/handlers/assign/check-pull-requests.ts | 33 ++++--------- src/helpers/get-linked-pull-requests.ts | 55 ++++++++++++---------- 2 files changed, 41 insertions(+), 47 deletions(-) diff --git a/src/handlers/assign/check-pull-requests.ts b/src/handlers/assign/check-pull-requests.ts index f5f0b3c14..4625aedd4 100644 --- a/src/handlers/assign/check-pull-requests.ts +++ b/src/handlers/assign/check-pull-requests.ts @@ -1,3 +1,5 @@ +import axios from "axios"; +import { HTMLElement, parse } from "node-html-parser"; import { getAllPullRequests, addAssignees } from "../../helpers/issue"; import { Context } from "../../types/context"; @@ -13,7 +15,6 @@ export async function checkPullRequests(context: Context) { // Loop through the pull requests and assign them to their respective issues if needed for (const pull of pulls) { const linkedIssue = await getLinkedIssues({ - context, owner: payload.repository.owner.login, repository: payload.repository.name, pull: pull.number, @@ -59,31 +60,18 @@ export async function checkPullRequests(context: Context) { return logger.debug(`Checking pull requests done!`); } -export async function getLinkedIssues({ context, owner, repository, pull }: GetLinkedParams) { - if (!pull || !context) return null; - const { data } = await context.octokit.pulls.get({ - owner, - repo: repository, - pull_number: pull, - }); +export async function getLinkedIssues({ owner, repository, pull }: GetLinkedParams) { + const { data } = await axios.get(`https://github.com/${owner}/${repository}/pull/${pull}`); + const dom = parse(data); + const devForm = dom.querySelector("[data-target='create-branch.developmentForm']") as HTMLElement; + const linkedIssues = devForm.querySelectorAll(".my-1"); - const body = data.body; - if (!body) return null; - - const match = body.match(/#(\d+)/); - const issueNumber = match ? match[1] : null; - - if (!issueNumber) { + if (linkedIssues.length === 0) { return null; } - const issue = await context.octokit.issues.get({ - owner, - repo: repository, - issue_number: Number(issueNumber), - }); - - return issue.data.html_url; + const issueUrl = linkedIssues[0].querySelector("a")?.attrs?.href || null; + return issueUrl; } export async function getPullByNumber(context: Context, pull: number) { @@ -117,7 +105,6 @@ export async function getIssueByNumber(context: Context, issueNumber: number) { } } export interface GetLinkedParams { - context?: Context; owner: string; repository: string; issue?: number; diff --git a/src/helpers/get-linked-pull-requests.ts b/src/helpers/get-linked-pull-requests.ts index 7c9d89b8b..c265d81c8 100644 --- a/src/helpers/get-linked-pull-requests.ts +++ b/src/helpers/get-linked-pull-requests.ts @@ -1,6 +1,7 @@ -import { GetLinkedParams, getLinkedIssues } from "../handlers/assign/check-pull-requests"; +import axios from "axios"; +import { HTMLElement, parse } from "node-html-parser"; +import { GetLinkedParams } from "../handlers/assign/check-pull-requests"; import { Context } from "../types/context"; -import { getAllPullRequests } from "./issue"; interface GetLinkedResults { organization: string; repository: string; @@ -11,31 +12,37 @@ export async function getLinkedPullRequests( context: Context, { owner, repository, issue }: GetLinkedParams ): Promise { - if (!issue) return []; - // const logger = context.logger; + const logger = context.logger; const collection = [] as GetLinkedResults[]; - const pulls = await getAllPullRequests(context); - const currentIssue = await context.octokit.issues.get({ - owner, - repo: repository, - issue_number: issue, - }); - for (const pull of pulls) { - const linkedIssue = await getLinkedIssues({ - context, - owner: owner, - repository: repository, - pull: pull.number, - }); + const { data } = await axios.get(`https://github.com/${owner}/${repository}/issues/${issue}`); + const dom = parse(data); + const devForm = dom.querySelector("[data-target='create-branch.developmentForm']") as HTMLElement; + const linkedList = devForm.querySelectorAll(".my-1"); + if (linkedList.length === 0) { + context.logger.info(`No linked pull requests found`); + return []; + } + + for (const linked of linkedList) { + const relativeHref = linked.querySelector("a")?.attrs?.href; + if (!relativeHref) continue; + const parts = relativeHref.split("/"); + + // check if array size is at least 4 + if (parts.length < 4) continue; - if (linkedIssue === currentIssue.data.html_url) { - collection.push({ - organization: owner, - repository, - number: pull.number, - href: pull.html_url, - }); + // extract the organization name and repo name from the link:(e.g. " + const organization = parts[parts.length - 4]; + const repository = parts[parts.length - 3]; + const number = Number(parts[parts.length - 1]); + const href = `https://github.com${relativeHref}`; + + if (`${organization}/${repository}` !== `${owner}/${repository}`) { + logger.info("Skipping linked pull request from another repository", href); + continue; } + + collection.push({ organization, repository, number, href }); } return collection;