diff --git a/src/adapters/index.ts b/src/adapters/index.ts index 752fd6b83..6242fdfa8 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -1,5 +1,5 @@ import { createClient } from "@supabase/supabase-js"; -import { BotConfig } from "../types"; +import { Context } from "../types"; import { Access } from "./supabase/helpers/tables/access"; import { Label } from "./supabase/helpers/tables/label"; import { Locations } from "./supabase/helpers/tables/locations"; @@ -10,19 +10,19 @@ import { User } from "./supabase/helpers/tables/user"; import { Wallet } from "./supabase/helpers/tables/wallet"; import { Database } from "./supabase/types"; -export function createAdapters(config: BotConfig) { - const client = generateSupabase(config.supabase.url, config.supabase.key); +export function createAdapters(context: Context) { + const client = generateSupabase(context.config.supabase.url, context.config.supabase.key); return { supabase: { - access: new Access(client), - wallet: new Wallet(client), - user: new User(client), - debit: new Settlement(client), - settlement: new Settlement(client), - label: new Label(client), - logs: new Logs(client), - locations: new Locations(client), - super: new Super(client), + access: new Access(client, context), + wallet: new Wallet(client, context), + user: new User(client, context), + debit: new Settlement(client, context), + settlement: new Settlement(client, context), + label: new Label(client, context), + logs: new Logs(client, context), + locations: new Locations(client, context), + super: new Super(client, context), }, }; } diff --git a/src/adapters/supabase/helpers/tables/access.ts b/src/adapters/supabase/helpers/tables/access.ts index 73eb81025..b64b93340 100644 --- a/src/adapters/supabase/helpers/tables/access.ts +++ b/src/adapters/supabase/helpers/tables/access.ts @@ -4,6 +4,7 @@ import { Database } from "../../types/database"; import { GitHubNode } from "../client"; import { Super } from "./super"; import { UserRow } from "./user"; +import { Context } from "../../../../types"; type AccessRow = Database["public"]["Tables"]["access"]["Row"]; type AccessInsert = Database["public"]["Tables"]["access"]["Insert"]; type UserWithAccess = (UserRow & { access: AccessRow | null })[]; @@ -18,8 +19,8 @@ type _Access = { }; export class Access extends Super { - constructor(supabase: SupabaseClient) { - super(supabase); + constructor(supabase: SupabaseClient, context: Context) { + super(supabase, context); } private async _getUserWithAccess(id: number): Promise { diff --git a/src/adapters/supabase/helpers/tables/label.ts b/src/adapters/supabase/helpers/tables/label.ts index af9f6318a..4738d2b32 100644 --- a/src/adapters/supabase/helpers/tables/label.ts +++ b/src/adapters/supabase/helpers/tables/label.ts @@ -2,10 +2,11 @@ import { SupabaseClient } from "@supabase/supabase-js"; import { Repository } from "../../../../types/payload"; import { Database } from "../../types"; import { Super } from "./super"; +import { Context } from "../../../../types"; type LabelRow = Database["public"]["Tables"]["labels"]["Row"]; export class Label extends Super { - constructor(supabase: SupabaseClient) { - super(supabase); + constructor(supabase: SupabaseClient, context: Context) { + super(supabase, context); } async saveLabelChange({ diff --git a/src/adapters/supabase/helpers/tables/locations.ts b/src/adapters/supabase/helpers/tables/locations.ts index ccc7c492f..bd09507df 100644 --- a/src/adapters/supabase/helpers/tables/locations.ts +++ b/src/adapters/supabase/helpers/tables/locations.ts @@ -1,6 +1,7 @@ import { SupabaseClient } from "@supabase/supabase-js"; import { Super } from "./super"; import { Database } from "../../types/database"; +import { Context } from "../../../../types"; // currently trying to save all of the location metadata of the event. // seems that focusing on the IssueComments will provide the most value @@ -16,8 +17,8 @@ export class Locations extends Super { node_id: string | undefined; node_type: string | undefined; - constructor(supabase: SupabaseClient) { - super(supabase); + constructor(supabase: SupabaseClient, context: Context) { + super(supabase, context); } public async getLocationsFromRepo(repositoryId: number) { @@ -61,7 +62,7 @@ export class Locations extends Super { } `; - this.locationResponse = (await this.runtime.latestEventContext.octokit.graphql(graphQlQuery)) as LocationResponse; + this.locationResponse = (await this.context.event.octokit.graphql(graphQlQuery)) as LocationResponse; console.trace(this.locationResponse); this.user_id = this.locationResponse.data.node.author.id; diff --git a/src/adapters/supabase/helpers/tables/logs.ts b/src/adapters/supabase/helpers/tables/logs.ts index 2c3c0224b..7b117be48 100644 --- a/src/adapters/supabase/helpers/tables/logs.ts +++ b/src/adapters/supabase/helpers/tables/logs.ts @@ -3,11 +3,11 @@ // Normally this is forbidden import { SupabaseClient } from "@supabase/supabase-js"; -import Runtime from "../../../../bindings/bot-runtime"; import { Database } from "../../types"; import { prettyLogs } from "../pretty-logs"; import { Super } from "./super"; import { execSync } from "child_process"; +import { Context } from "../../../../types"; type LogFunction = (message: string, metadata?: any) => void; type LogInsert = Database["public"]["Tables"]["logs"]["Insert"]; @@ -220,10 +220,9 @@ export class Logs extends Super { }); } - constructor(supabase: SupabaseClient) { - super(supabase); - const runtime = Runtime.getState(); - const logConfig = runtime.botConfig.log; + constructor(supabase: SupabaseClient, context: Context) { + super(supabase, context); + const logConfig = this.context.config.log; this.environment = logConfig.logEnvironment; this.retryLimit = logConfig.retryLimit; @@ -362,11 +361,11 @@ export class Logs extends Super { } private _postComment(message: string) { - this.runtime.latestEventContext.octokit.issues + this.context.event.octokit.issues .createComment({ - owner: this.runtime.latestEventContext.issue().owner, - repo: this.runtime.latestEventContext.issue().repo, - issue_number: this.runtime.latestEventContext.issue().issue_number, + owner: this.context.event.issue().owner, + repo: this.context.event.issue().repo, + issue_number: this.context.event.issue().issue_number, body: message, }) // .then((x) => console.trace(x)) diff --git a/src/adapters/supabase/helpers/tables/settlement.ts b/src/adapters/supabase/helpers/tables/settlement.ts index 050630db2..9c23e52e5 100644 --- a/src/adapters/supabase/helpers/tables/settlement.ts +++ b/src/adapters/supabase/helpers/tables/settlement.ts @@ -4,6 +4,7 @@ import { GeneratedPermit } from "../../../../helpers/permit"; import { Comment, Payload } from "../../../../types/payload"; import { Database } from "../../types/database"; import { Super } from "./super"; +import { Context } from "../../../../types"; type DebitInsert = Database["public"]["Tables"]["debits"]["Insert"]; type CreditInsert = Database["public"]["Tables"]["credits"]["Insert"]; @@ -26,8 +27,8 @@ type AddCreditWithPermit = { }; export class Settlement extends Super { - constructor(supabase: SupabaseClient) { - super(supabase); + constructor(supabase: SupabaseClient, context: Context) { + super(supabase, context); } private async _lookupTokenId(networkId: number, address: string): Promise { diff --git a/src/adapters/supabase/helpers/tables/super.ts b/src/adapters/supabase/helpers/tables/super.ts index ae961fcba..409f02ed6 100644 --- a/src/adapters/supabase/helpers/tables/super.ts +++ b/src/adapters/supabase/helpers/tables/super.ts @@ -1,12 +1,15 @@ import { SupabaseClient } from "@supabase/supabase-js"; import Runtime from "../../../../bindings/bot-runtime"; +import { Context } from "../../../../types"; export class Super { - public supabase: SupabaseClient; - public runtime: Runtime; // convenience accessor + protected supabase: SupabaseClient; + protected runtime: Runtime; // convenience accessor + protected context: Context; - constructor(supabase: SupabaseClient) { + constructor(supabase: SupabaseClient, context: Context) { this.supabase = supabase; this.runtime = Runtime.getState(); + this.context = context; } } diff --git a/src/adapters/supabase/helpers/tables/user.ts b/src/adapters/supabase/helpers/tables/user.ts index effa6135b..bc5c2cf17 100644 --- a/src/adapters/supabase/helpers/tables/user.ts +++ b/src/adapters/supabase/helpers/tables/user.ts @@ -1,15 +1,16 @@ import { SupabaseClient } from "@supabase/supabase-js"; import { Database } from "../../types/database"; import { Super } from "./super"; +import { Context } from "../../../../types"; export type UserRow = Database["public"]["Tables"]["users"]["Row"]; export class User extends Super { - constructor(supabase: SupabaseClient) { - super(supabase); + constructor(supabase: SupabaseClient, context: Context) { + super(supabase, context); } public async getUserId(username: string): Promise { - const octokit = this.runtime.latestEventContext.octokit; + const octokit = this.context.event.octokit; const { data } = await octokit.rest.users.getByUsername({ username }); return data.id; } diff --git a/src/adapters/supabase/helpers/tables/wallet.ts b/src/adapters/supabase/helpers/tables/wallet.ts index ce79abf62..b8537eb13 100644 --- a/src/adapters/supabase/helpers/tables/wallet.ts +++ b/src/adapters/supabase/helpers/tables/wallet.ts @@ -1,7 +1,7 @@ import { PostgrestError, SupabaseClient } from "@supabase/supabase-js"; -import { Context } from "probot/lib/context"; +import { Context as ProbotContext } from "probot/lib/context"; import Runtime from "../../../../bindings/bot-runtime"; -import { User } from "../../../../types"; +import { Context, User } from "../../../../types"; import { Database } from "../../types/database"; import { Super } from "./super"; import { UserRow } from "./user"; @@ -11,23 +11,24 @@ type WalletRow = Database["public"]["Tables"]["wallets"]["Row"]; type WalletInsert = Database["public"]["Tables"]["wallets"]["Insert"]; type UserWithWallet = (UserRow & { wallets: WalletRow | null })[]; -type IssueCommentPayload = Context<"issue_comment.created">["payload"] | Context<"issue_comment.edited">["payload"]; +type IssueCommentPayload = + | ProbotContext<"issue_comment.created">["payload"] + | ProbotContext<"issue_comment.edited">["payload"]; export class Wallet extends Super { - constructor(supabase: SupabaseClient) { - super(supabase); + constructor(supabase: SupabaseClient, context: Context) { + super(supabase, context); } + public async getAddress(id: number): Promise { const userWithWallet = await this._getUserWithWallet(id); return this._validateAndGetWalletAddress(userWithWallet); } public async upsertWalletAddress(address: string) { - const runtime = Runtime.getState(); - const eventContext = runtime.latestEventContext; - const payload = eventContext.payload as - | Context<"issue_comment.created">["payload"] - | Context<"issue_comment.edited">["payload"]; + const payload = this.context.event.payload as + | ProbotContext<"issue_comment.created">["payload"] + | ProbotContext<"issue_comment.edited">["payload"]; const userData = await this._getUserData(payload); const registeredWalletData = await this._getRegisteredWalletData(userData); @@ -69,6 +70,7 @@ export class Wallet extends Super { if (error) throw error; return data as UserRow; } + private async _getUserData(payload: IssueCommentPayload): Promise { const user = await this._checkIfUserExists(payload.sender.id); let userData = user; @@ -78,6 +80,7 @@ export class Wallet extends Super { } return userData; } + private async _registerNewUser(user: User, locationMetaData: LocationMetaData): Promise { // Insert the location metadata into the locations table const { data: locationData, error: locationError } = (await this.supabase @@ -104,6 +107,7 @@ export class Wallet extends Super { return userData as UserRow; } + private async _checkIfWalletExists( userData: UserRow ): Promise<{ data: WalletRow | null; error: PostgrestError | null }> { @@ -111,6 +115,7 @@ export class Wallet extends Super { return { data: data as WalletRow, error }; } + private async _updateWalletId(walletId: number, userId: number) { const { error } = await this.supabase.from("users").update({ wallet_id: walletId }).eq("id", userId); @@ -118,6 +123,7 @@ export class Wallet extends Super { throw error; } } + private async _getRegisteredWalletData(userData: UserRow): Promise { const walletResponse = await this._checkIfWalletExists(userData); const walletData = walletResponse.data as WalletRow; diff --git a/src/bindings/bot-runtime.ts b/src/bindings/bot-runtime.ts index ded7f5d27..b0ff4ff15 100644 --- a/src/bindings/bot-runtime.ts +++ b/src/bindings/bot-runtime.ts @@ -1,19 +1,12 @@ -import { Context } from "probot"; -import { BotConfig } from "../types"; import { createAdapters } from "../adapters"; import { Logs } from "../adapters/supabase"; -import { GitHubEvent } from "../types/payload"; class Runtime { private static instance: Runtime; - private _eventContext: Context[]; - private _botConfig: BotConfig; private _adapters: ReturnType; private _logger: Logs; private constructor() { - this._eventContext = [] as Context[]; - this._botConfig = {} as BotConfig; this._adapters = {} as ReturnType; this._logger = {} as Logs; } @@ -25,34 +18,6 @@ class Runtime { return Runtime.instance; } - // public eventContextByKeyPair(keyPair: { [key: string]: string }) { - // const [key, value] = Object.entries(keyPair)[0]; - // return this._eventContext.find((context) => context[key] === value); - // } - public eventContextByType(name: GitHubEvent) { - return this._eventContext.find((context) => context.name === name); - } - // public eventContextById(id: string) { - // return this._eventContext.find((context) => context.id === id); - // } - - public get latestEventContext() { - const latestContext = this._eventContext[this._eventContext.length - 1]; - return latestContext; - } - - public set latestEventContext(context: Context) { - this._eventContext.push(context); - } - - public get botConfig(): BotConfig { - return this._botConfig; - } - - public set botConfig(config: BotConfig) { - this._botConfig = config; - } - public get adapters(): ReturnType { return this._adapters; } diff --git a/src/bindings/event.ts b/src/bindings/event.ts index aee10a752..943d9ed78 100644 --- a/src/bindings/event.ts +++ b/src/bindings/event.ts @@ -1,4 +1,4 @@ -import { Context } from "probot"; +import { Context as ProbotContext } from "probot"; import { createAdapters } from "../adapters"; import { LogReturn } from "../adapters/supabase"; import { LogMessage } from "../adapters/supabase/helpers/tables/logs"; @@ -17,6 +17,8 @@ import { Payload } from "../types/payload"; import { ajv } from "../utils"; import Runtime from "./bot-runtime"; import { loadConfig } from "./config"; +import { Context } from "../types"; + const NO_VALIDATION = [GitHubEvent.INSTALLATION_ADDED_EVENT, GitHubEvent.PUSH_EVENT] as string[]; type PreHandlerWithType = { type: string; actions: PreActionHandler[] }; type HandlerWithType = { type: string; actions: MainActionHandler[] }; @@ -24,15 +26,20 @@ type WildCardHandlerWithType = { type: string; actions: WildCardHandler[] }; type PostHandlerWithType = { type: string; actions: PostActionHandler[] }; type AllHandlersWithTypes = PreHandlerWithType | HandlerWithType | PostHandlerWithType; type AllHandlers = PreActionHandler | MainActionHandler | PostActionHandler; -export async function bindEvents(eventContext: Context) { + +export async function bindEvents(eventContext: ProbotContext) { const runtime = Runtime.getState(); - runtime.latestEventContext = eventContext; - runtime.botConfig = await loadConfig(eventContext); - runtime.adapters = createAdapters(runtime.botConfig); + const botConfig = await loadConfig(eventContext); + const context: Context = { + event: eventContext, + config: botConfig, + }; + + runtime.adapters = createAdapters(context); runtime.logger = runtime.adapters.supabase.logs; - if (!runtime.botConfig.payout.privateKey) { + if (!context.config.payout.privateKey) { runtime.logger.warn("No EVM private key found"); } @@ -40,7 +47,7 @@ export async function bindEvents(eventContext: Context) { const allowedEvents = Object.values(GitHubEvent) as string[]; const eventName = payload?.action ? `${eventContext.name}.${payload?.action}` : eventContext.name; // some events wont have actions as this grows if (eventName === GitHubEvent.PUSH_EVENT) { - await validateConfigChange(); + await validateConfigChange(context); } if (!runtime.logger) { @@ -68,7 +75,7 @@ export async function bindEvents(eventContext: Context) { } // Check if we should skip the event - const should = shouldSkip(); + const should = shouldSkip(context); if (should.stop) { return runtime.logger.info("Skipping the event.", { reason: should.reason }); } @@ -98,7 +105,7 @@ export async function bindEvents(eventContext: Context) { handlers: "${functionNames.join(", ")}"` ); - await logAnyReturnFromHandlers(handlerWithType); + await logAnyReturnFromHandlers(context, handlerWithType); } // Skip wildcard handlers for installation event and push event @@ -109,20 +116,20 @@ export async function bindEvents(eventContext: Context) { const functionNames = wildcardProcessors.map((action) => action?.name); runtime.logger.info(`Running wildcard handlers: "${functionNames.join(", ")}"`); const wildCardHandlerType: WildCardHandlerWithType = { type: "wildcard", actions: wildcardProcessors }; - await logAnyReturnFromHandlers(wildCardHandlerType); + await logAnyReturnFromHandlers(context, wildCardHandlerType); } } -async function logAnyReturnFromHandlers(handlerType: AllHandlersWithTypes) { +async function logAnyReturnFromHandlers(context: Context, handlerType: AllHandlersWithTypes) { for (const action of handlerType.actions) { - const renderCatchAllWithContext = createRenderCatchAll(handlerType, action); + const renderCatchAllWithContext = createRenderCatchAll(context, handlerType, action); try { // checkHandler(action); - const response = await action(); + const response = await action(context); if (handlerType.type === "main") { // only log main handler results - await renderMainActionOutput(response, action); + await renderMainActionOutput(context, response, action); } else { const runtime = Runtime.getState(); runtime.logger.ok("Completed", { action: action.name, type: handlerType.type }); @@ -133,9 +140,9 @@ async function logAnyReturnFromHandlers(handlerType: AllHandlersWithTypes) { } } -async function renderMainActionOutput(response: string | void | LogReturn, action: AllHandlers) { +async function renderMainActionOutput(context: Context, response: string | void | LogReturn, action: AllHandlers) { const runtime = Runtime.getState(); - const payload = runtime.latestEventContext.payload as Payload; + const payload = context.event.payload as Payload; const issueNumber = payload.issue?.number; if (!issueNumber) { throw new Error("No issue number found"); @@ -152,10 +159,10 @@ async function renderMainActionOutput(response: string | void | LogReturn, actio serializedComment = response.logMessage.diff; } - await addCommentToIssue(serializedComment, issueNumber); + await addCommentToIssue(context, serializedComment, issueNumber); // runtime.logger[response.logMessage.type as LogMessage["type"]](response.logMessage.raw, response.metadata, true); } else if (typeof response == "string") { - await addCommentToIssue(response, issueNumber); + await addCommentToIssue(context, response, issueNumber); // runtime.logger.debug(response, null, true); } else { runtime.logger.error( @@ -166,10 +173,10 @@ async function renderMainActionOutput(response: string | void | LogReturn, actio } } -function createRenderCatchAll(handlerType: AllHandlersWithTypes, activeHandler: AllHandlers) { +function createRenderCatchAll(context: Context, handlerType: AllHandlersWithTypes, activeHandler: AllHandlers) { return async function renderCatchAll(logReturn: LogReturn | Error | unknown) { const runtime = Runtime.getState(); - const payload = runtime.latestEventContext.payload as Payload; + const payload = context.event.payload as Payload; const issue = payload.issue; if (!issue) return runtime.logger.error("Issue is null. Skipping", { issue }); @@ -189,9 +196,9 @@ function createRenderCatchAll(handlerType: AllHandlersWithTypes, activeHandler: metadataSerialized = [""].join("\n"); } - return await addCommentToIssue([logMessage.diff, metadataSerialized].join("\n"), issue.number); + return await addCommentToIssue(context, [logMessage.diff, metadataSerialized].join("\n"), issue.number); } else { - return await addCommentToIssue(logMessage.diff, issue.number); + return await addCommentToIssue(context, logMessage.diff, issue.number); } } else if (logReturn instanceof Error) { return runtime.logger.error( diff --git a/src/handlers/access/labels-access.ts b/src/handlers/access/labels-access.ts index c4204f7ed..9f627ea3e 100644 --- a/src/handlers/access/labels-access.ts +++ b/src/handlers/access/labels-access.ts @@ -1,23 +1,21 @@ import Runtime from "../../bindings/bot-runtime"; import { addCommentToIssue, isUserAdminOrBillingManager, removeLabel, addLabelToIssue } from "../../helpers"; -import { Payload, UserType } from "../../types"; +import { Context, Payload, UserType } from "../../types"; -export async function handleLabelsAccess() { +export async function handleLabelsAccess(context: Context) { const runtime = Runtime.getState(); - const { publicAccessControl } = runtime.botConfig; - if (!publicAccessControl.setLabel) return true; - - const eventContext = runtime.latestEventContext; const logger = runtime.logger; + const { publicAccessControl } = context.config; + if (!publicAccessControl.setLabel) return true; - const payload = eventContext.payload as Payload; + const payload = context.event.payload as Payload; if (!payload.issue) return; if (!payload.label?.name) return; if (payload.sender.type === UserType.Bot) return true; const sender = payload.sender.login; const repo = payload.repository; - const sufficientPrivileges = await isUserAdminOrBillingManager(sender, eventContext); + const sufficientPrivileges = await isUserAdminOrBillingManager(context, sender); // event in plain english const eventName = payload.action === "labeled" ? "add" : "remove"; const labelName = payload.label.name; @@ -45,12 +43,16 @@ export async function handleLabelsAccess() { if (payload.action === "labeled") { // remove the label - await removeLabel(labelName); + await removeLabel(context, labelName); } else if (payload.action === "unlabeled") { // add the label - await addLabelToIssue(labelName); + await addLabelToIssue(context, labelName); } - await addCommentToIssue(`@${sender}, You are not allowed to ${eventName} ${labelName}`, payload.issue.number); + await addCommentToIssue( + context, + `@${sender}, You are not allowed to ${eventName} ${labelName}`, + payload.issue.number + ); logger.info("No access to edit label", { sender, label: labelName }); return false; } diff --git a/src/handlers/assign/action.ts b/src/handlers/assign/action.ts index d9f19f29d..53ebbf054 100644 --- a/src/handlers/assign/action.ts +++ b/src/handlers/assign/action.ts @@ -1,14 +1,13 @@ import Runtime from "../../bindings/bot-runtime"; import { calculateDurations, calculateLabelValue, closePullRequest } from "../../helpers"; import { getLinkedPullRequests } from "../../helpers/parser"; -import { Label, Payload } from "../../types"; +import { Context, Label, Payload } from "../../types"; -export async function startCommandHandler() { +export async function startCommandHandler(context: Context) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const config = runtime.botConfig; + const config = context.config; const logger = runtime.logger; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; if (!payload.issue) { return logger.error("Issue is not defined"); } @@ -76,11 +75,10 @@ export async function startCommandHandler() { return logger.info(commitMessage); } -export async function closePullRequestForAnIssue() { +export async function closePullRequestForAnIssue(context: Context) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; const logger = runtime.logger; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; if (!payload.issue?.number) { throw logger.error("Issue is not defined"); } @@ -98,7 +96,7 @@ export async function closePullRequestForAnIssue() { logger.info(`Opened prs`, linkedPullRequests); let comment = `These linked pull requests are closed: `; for (let i = 0; i < linkedPullRequests.length; i++) { - await closePullRequest(linkedPullRequests[i].number); + await closePullRequest(context, linkedPullRequests[i].number); comment += ` #${linkedPullRequests[i].number} `; } return logger.info(comment); diff --git a/src/handlers/assign/auto.ts b/src/handlers/assign/auto.ts index f9feb281b..06db07b37 100644 --- a/src/handlers/assign/auto.ts +++ b/src/handlers/assign/auto.ts @@ -1,12 +1,11 @@ import Runtime from "../../bindings/bot-runtime"; import { addAssignees, getAllPullRequests, getIssueByNumber, getPullByNumber } from "../../helpers"; import { getLinkedIssues } from "../../helpers/parser"; -import { Payload } from "../../types"; +import { Context, Payload } from "../../types"; // Check for pull requests linked to their respective issues but not assigned to them -export async function checkPullRequests() { +export async function checkPullRequests(context: Context) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; const logger = runtime.logger; const pulls = await getAllPullRequests(context); @@ -14,7 +13,7 @@ export async function checkPullRequests() { return logger.debug(`No pull requests found at this time`); } - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; // Loop through the pull requests and assign them to their respective issues if needed for (const pull of pulls) { const linkedIssue = await getLinkedIssues({ @@ -52,7 +51,7 @@ export async function checkPullRequests() { const assignedUsernames = issue.assignees.map((assignee) => assignee.login); if (!assignedUsernames.includes(opener)) { - await addAssignees(+linkedIssueNumber, [opener]); + await addAssignees(context, +linkedIssueNumber, [opener]); logger.debug("Assigned pull request opener to issue", { pullRequest: pull.number, issue: linkedIssueNumber, diff --git a/src/handlers/comment/action.ts b/src/handlers/comment/action.ts index a34507ea6..f054292ff 100644 --- a/src/handlers/comment/action.ts +++ b/src/handlers/comment/action.ts @@ -1,19 +1,18 @@ import Runtime from "../../bindings/bot-runtime"; -import { Comment, Payload } from "../../types"; +import { Comment, Payload, Context } from "../../types"; import { commentParser, userCommands } from "./handlers"; import { verifyFirstCommentInRepository } from "./handlers/first"; -export async function commentCreatedOrEdited() { +export async function commentCreatedOrEdited(context: Context) { const runtime = Runtime.getState(), - config = runtime.botConfig, + config = context.config, logger = runtime.logger, - context = runtime.latestEventContext, - payload = context.payload as Payload; + payload = context.event.payload as Payload; const comment = payload.comment as Comment; const body = comment.body; - const commentedCommand = commentParser(body); + const commentedCommand = commentParser(context, body); if (!comment) { logger.info(`Comment is null. Skipping`); @@ -24,10 +23,10 @@ export async function commentCreatedOrEdited() { } if (commentedCommand) { - await verifyFirstCommentInRepository(); + await verifyFirstCommentInRepository(context); } - const allCommands = userCommands(); + const allCommands = userCommands(context); const userCommand = allCommands.find((i) => i.id == commentedCommand); if (userCommand) { @@ -40,7 +39,7 @@ export async function commentCreatedOrEdited() { return logger.warn("Skipping because it is disabled on this repo.", { id }); } - return await handler(body); + return await handler(context, body); } else { return logger.verbose("I do not understand how to respond to that command", { body }); } diff --git a/src/handlers/comment/handlers/ask.ts b/src/handlers/comment/handlers/ask.ts index 82c4fb104..fc63460c7 100644 --- a/src/handlers/comment/handlers/ask.ts +++ b/src/handlers/comment/handlers/ask.ts @@ -1,16 +1,15 @@ import Runtime from "../../../bindings/bot-runtime"; -import { Payload, StreamlinedComment, UserType } from "../../../types"; +import { Context, Payload, StreamlinedComment, UserType } from "../../../types"; import { getAllIssueComments, getAllLinkedIssuesAndPullsInBody } from "../../../helpers"; import { CreateChatCompletionRequestMessage } from "openai/resources/chat"; import { askGPT, decideContextGPT, sysMsg } from "../../../helpers/gpt"; -export async function ask(body: string) { +export async function ask(context: Context, body: string) { // The question to ask const runtime = Runtime.getState(); - const context = runtime.latestEventContext; const logger = runtime.logger; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const sender = payload.sender.login; const issue = payload.issue; @@ -34,9 +33,9 @@ export async function ask(body: string) { const [, body] = matches; // standard comments - const comments = await getAllIssueComments(issue.number); + const comments = await getAllIssueComments(context, issue.number); // raw so we can grab the tag - const commentsRaw = await getAllIssueComments(issue.number, "raw"); + const commentsRaw = await getAllIssueComments(context, issue.number, "raw"); if (!comments) { throw logger.error(`Error getting issue comments`); @@ -59,7 +58,7 @@ export async function ask(body: string) { }); // returns the conversational context from all linked issues and prs - const links = await getAllLinkedIssuesAndPullsInBody(issue.number); + const links = await getAllLinkedIssuesAndPullsInBody(context, issue.number); if (typeof links === "string") { logger.info("Error getting linked issues or prs: ", links); @@ -70,6 +69,7 @@ export async function ask(body: string) { // let chatgpt deduce what is the most relevant context const gptDecidedContext = await decideContextGPT( + context, chatHistory, streamlined, linkedPRStreamlined, @@ -110,7 +110,7 @@ export async function ask(body: string) { ); } - const gptResponse = await askGPT(chatHistory); + const gptResponse = await askGPT(context, chatHistory); if (typeof gptResponse === "string") { return gptResponse; diff --git a/src/handlers/comment/handlers/assign/get-multiplier-info-to-display.ts b/src/handlers/comment/handlers/assign/get-multiplier-info-to-display.ts index cd77cbf45..4a9213dbc 100644 --- a/src/handlers/comment/handlers/assign/get-multiplier-info-to-display.ts +++ b/src/handlers/comment/handlers/assign/get-multiplier-info-to-display.ts @@ -1,8 +1,8 @@ -import { Issue } from "../../../../types"; +import { Context, Issue } from "../../../../types"; import { taskInfo } from "../../../wildcard"; import { getUserMultiplier } from "./get-user-multiplier"; -export async function getMultiplierInfoToDisplay(senderId: number, repoId: number, issue: Issue) { +export async function getMultiplierInfoToDisplay(context: Context, senderId: number, repoId: number, issue: Issue) { const userMultiplier = await getUserMultiplier(senderId, repoId); const value = userMultiplier?.value || null; const reason = userMultiplier?.reason || null; @@ -12,7 +12,7 @@ export async function getMultiplierInfoToDisplay(senderId: number, repoId: numbe if (value && value != 1) { totalPriceOfTask = `Permit generation disabled because price label is not set.`; - const issueDetailed = taskInfo(issue); + const issueDetailed = taskInfo(context, issue); const priceLabel = issueDetailed.priceLabel; console.trace(issueDetailed); diff --git a/src/handlers/comment/handlers/assign/index.ts b/src/handlers/comment/handlers/assign/index.ts index d5287ba14..bb81a3473 100644 --- a/src/handlers/comment/handlers/assign/index.ts +++ b/src/handlers/comment/handlers/assign/index.ts @@ -6,7 +6,7 @@ import { getAssignedIssues, getAvailableOpenedPullRequests, } from "../../../../helpers"; -import { Comment, IssueType, Payload, User } from "../../../../types"; +import { Comment, IssueType, Payload, User, Context } from "../../../../types"; import { isParentIssue } from "../../../pricing"; import { assignTableComment } from "../table"; import { checkTaskStale } from "./check-task-stale"; @@ -14,11 +14,11 @@ import { generateAssignmentComment } from "./generate-assignment-comment"; import { getMultiplierInfoToDisplay } from "./get-multiplier-info-to-display"; import { getTimeLabelsAssigned } from "./get-time-labels-assigned"; -export async function assign(body: string) { +export async function assign(context: Context, body: string) { const runtime = Runtime.getState(); const logger = runtime.logger; - const config = runtime.botConfig; - const payload = runtime.latestEventContext.payload as Payload; + const config = context.config; + const payload = context.event.payload as Payload; const issue = payload.issue; const staleTask = config.assign.staleTaskTime; @@ -40,14 +40,14 @@ export async function assign(body: string) { ); } - const openedPullRequests = await getAvailableOpenedPullRequests(payload.sender.login); + const openedPullRequests = await getAvailableOpenedPullRequests(context, payload.sender.login); logger.info( `Opened Pull Requests with approved reviews or with no reviews but over 24 hours have passed: ${JSON.stringify( openedPullRequests )}` ); - const assignedIssues = await getAssignedIssues(payload.sender.login); + const assignedIssues = await getAssignedIssues(context, payload.sender.login); logger.info("Max issue allowed is", config.assign.maxConcurrentTasks); // check for max and enforce max @@ -86,14 +86,14 @@ export async function assign(body: string) { if (!assignees.map((i) => i.login).includes(payload.sender.login)) { logger.info("Adding the assignee", { assignee: payload.sender.login }); - await addAssignees(issue.number, [payload.sender.login]); + await addAssignees(context, issue.number, [payload.sender.login]); } const isTaskStale = checkTaskStale(staleTask, issue); // double check whether the assign message has been already posted or not logger.info("Creating an issue comment", { comment }); - const issueComments = await getAllIssueComments(issue.number); + const issueComments = await getAllIssueComments(context, issue.number); const comments = issueComments.sort( (a: Comment, b: Comment) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime() ); @@ -103,7 +103,7 @@ export async function assign(body: string) { multiplierAmount: multiplierAmount, multiplierReason: multiplierReason, totalPriceOfTask: totalPriceOfTask, - } = await getMultiplierInfoToDisplay(payload.sender.id, payload.repository.id, issue); + } = await getMultiplierInfoToDisplay(context, payload.sender.id, payload.repository.id, issue); return ( assignTableComment({ multiplierAmount, diff --git a/src/handlers/comment/handlers/authorize.ts b/src/handlers/comment/handlers/authorize.ts index ca9db875b..6bb7d7b90 100644 --- a/src/handlers/comment/handlers/authorize.ts +++ b/src/handlers/comment/handlers/authorize.ts @@ -1,14 +1,13 @@ import Runtime from "../../../bindings/bot-runtime"; import { isUserAdminOrBillingManager } from "../../../helpers"; -import { Payload } from "../../../types"; +import { Context, Payload } from "../../../types"; import { taskInfo } from "../../wildcard"; -export async function authorizeLabelChanges() { +export async function authorizeLabelChanges(context: Context) { const runtime = Runtime.getState(); const { label } = runtime.adapters.supabase; - const context = runtime.latestEventContext; const logger = runtime.logger; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const sender = payload.sender.login; logger.info("Running '/authorize' command handler", { sender }); @@ -20,7 +19,7 @@ export async function authorizeLabelChanges() { // check if sender is admin // passing in context so we don't have to make another request to get the user - const sufficientPrivileges = await isUserAdminOrBillingManager(sender, context); + const sufficientPrivileges = await isUserAdminOrBillingManager(context, sender); // if sender is not admin, return if (sufficientPrivileges) { @@ -30,7 +29,7 @@ export async function authorizeLabelChanges() { ); } - const issueDetailed = taskInfo(issue); + const issueDetailed = taskInfo(context, issue); if (!issueDetailed.priceLabel || !issueDetailed.priorityLabel || !issueDetailed.timeLabel) { throw runtime.logger.error("No valid task label on this issue", { issueDetailed }); diff --git a/src/handlers/comment/handlers/first.ts b/src/handlers/comment/handlers/first.ts index a43091f6b..f266f4eec 100644 --- a/src/handlers/comment/handlers/first.ts +++ b/src/handlers/comment/handlers/first.ts @@ -1,29 +1,28 @@ import Runtime from "../../../bindings/bot-runtime"; -import { Payload } from "../../../types"; +import { Context, Payload } from "../../../types"; import { generateHelpMenu } from "./help"; -export async function verifyFirstCommentInRepository() { +export async function verifyFirstCommentInRepository(context: Context) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = runtime.latestEventContext.payload as Payload; + const payload = context.event.payload as Payload; if (!payload.issue) { throw runtime.logger.error("Issue is null. Skipping", { issue: payload.issue }, true); } const { newContributorGreeting: { header, footer, enabled }, - } = Runtime.getState().botConfig; - const response_issue = await context.octokit.rest.search.issuesAndPullRequests({ + } = context.config; + const response_issue = await context.event.octokit.rest.search.issuesAndPullRequests({ q: `is:issue repo:${payload.repository.owner.login}/${payload.repository.name} commenter:${payload.sender.login}`, per_page: 2, }); - const response_pr = await context.octokit.rest.search.issuesAndPullRequests({ + const response_pr = await context.event.octokit.rest.search.issuesAndPullRequests({ q: `is:pull-request repo:${payload.repository.owner.login}/${payload.repository.name} commenter:${payload.sender.login}`, per_page: 2, }); if (response_issue.data.total_count + response_pr.data.total_count === 1) { //continue_first_search const data = response_issue.data.total_count > 0 ? response_issue.data : response_pr.data; - const resp = await context.octokit.rest.issues.listComments({ + const resp = await context.event.octokit.rest.issues.listComments({ issue_number: data.items[0].number, owner: payload.repository.owner.login, repo: payload.repository.name, @@ -31,7 +30,7 @@ export async function verifyFirstCommentInRepository() { }); const isFirstComment = resp.data.filter((item) => item.user?.login === payload.sender.login).length === 1; if (isFirstComment && enabled) { - return [header, generateHelpMenu(), `@${payload.sender.login}`, footer].join("\n"); + return [header, generateHelpMenu(context), `@${payload.sender.login}`, footer].join("\n"); // await upsertCommentToIssue(payload.issue.number, msg, payload.action, payload.comment); } } diff --git a/src/handlers/comment/handlers/help.ts b/src/handlers/comment/handlers/help.ts index 4c4b16ce7..203ee05f0 100644 --- a/src/handlers/comment/handlers/help.ts +++ b/src/handlers/comment/handlers/help.ts @@ -1,16 +1,15 @@ import { userCommands } from "."; import Runtime from "../../../bindings/bot-runtime"; -import { IssueType, Payload } from "../../../types"; +import { Context, IssueType, Payload } from "../../../types"; -export async function listAvailableCommands(body: string) { +export async function listAvailableCommands(context: Context, body: string) { const runtime = Runtime.getState(); - const { payload: _payload } = runtime.latestEventContext; const logger = runtime.logger; if (body != "/help") { return logger.info("Skipping to list available commands.", { body }); } - const payload = _payload as Payload; + const payload = context.event.payload as Payload; const issue = payload.issue; if (!issue) { @@ -21,14 +20,14 @@ export async function listAvailableCommands(body: string) { return logger.info("Skipping '/start', reason: closed "); } - return generateHelpMenu(); + return generateHelpMenu(context); } -export function generateHelpMenu() { - const config = Runtime.getState().botConfig; +export function generateHelpMenu(context: Context) { + const config = context.config; const startEnabled = config.command.find((command) => command.name === "start"); let helpMenu = "### Available Commands\n\n| Command | Description | Example |\n| --- | --- | --- |\n"; - const commands = userCommands(); + const commands = userCommands(context); commands.map( (command) => diff --git a/src/handlers/comment/handlers/index.ts b/src/handlers/comment/handlers/index.ts index 56522a291..9e5d221e2 100644 --- a/src/handlers/comment/handlers/index.ts +++ b/src/handlers/comment/handlers/index.ts @@ -1,4 +1,4 @@ -import { UserCommands } from "../../../types"; +import { Context, UserCommands } from "../../../types"; import { assign } from "./assign"; import { listAvailableCommands } from "./help"; // Commented out until Gnosis Safe is integrated (https://github.com/ubiquity/ubiquibot/issues/353) @@ -13,7 +13,6 @@ import { registerWallet } from "./wallet"; import { autoPay } from "./payout"; import { query } from "./query"; -import Runtime from "../../../bindings/bot-runtime"; export * from "./ask"; export * from "./assign"; @@ -26,8 +25,8 @@ export * from "./unassign"; export * from "./wallet"; // Parses the comment body and figure out the command name a user wants -export function commentParser(body: string): null | string { - const userCommandIds = userCommands().map((cmd) => cmd.id); +export function commentParser(context: Context, body: string): null | string { + const userCommandIds = userCommands(context).map((cmd) => cmd.id); const regex = new RegExp(`^(${userCommandIds.join("|")})\\b`); // Regex pattern to match any command at the beginning of the body const matches = regex.exec(body); @@ -41,8 +40,8 @@ export function commentParser(body: string): null | string { return null; } -export function userCommands(): UserCommands[] { - const accountForWalletVerification = walletVerificationDetails(); +export function userCommands(context: Context): UserCommands[] { + const accountForWalletVerification = walletVerificationDetails(context); return [ { id: "/start", @@ -113,7 +112,7 @@ export function userCommands(): UserCommands[] { ]; } -function walletVerificationDetails() { +function walletVerificationDetails(context: Context) { const base = { description: "Register your wallet address for payments.", example: "/wallet ubq.eth", @@ -126,7 +125,7 @@ function walletVerificationDetails() { "0xe2a3e34a63f3def2c29605de82225b79e1398190b542be917ef88a8e93ff9dc91bdc3ef9b12ed711550f6d2cbbb50671aa3f14a665b709ec391f3e603d0899a41b", }; - const walletVerificationEnabled = Runtime.getState().botConfig.wallet.registerWalletWithVerification; + const walletVerificationEnabled = context.config.wallet.registerWalletWithVerification; if (walletVerificationEnabled) { return { description: `${base.description} ${withVerification.description}`, diff --git a/src/handlers/comment/handlers/issue/issue-closed.ts b/src/handlers/comment/handlers/issue/issue-closed.ts index 79964e48d..fac822762 100644 --- a/src/handlers/comment/handlers/issue/issue-closed.ts +++ b/src/handlers/comment/handlers/issue/issue-closed.ts @@ -1,5 +1,5 @@ import Runtime from "../../../../bindings/bot-runtime"; -import { Payload } from "../../../../types/payload"; +import { Payload, Context } from "../../../../types"; import { calculateIssueAssigneeReward } from "../../../payout/calculate-issue-assignee-reward"; import { calculateIssueConversationReward } from "../../../payout/calculate-issue-conversation-reward"; import { calculateIssueCreatorReward } from "../../../payout/calculate-issue-creator-reward"; @@ -7,8 +7,8 @@ import { calculateReviewContributorRewards } from "../../../payout/calculate-rev import { handleIssueClosed } from "../../../payout/handle-issue-closed"; import { incentivesCalculation } from "../../../payout/incentives-calculation"; -export async function issueClosed() { - const { organization, logger, owner } = getEssentials(); +export async function issueClosed(context: Context) { + const { organization, logger, owner } = getEssentials(context); if (!organization) { logger.warn("No organization found in payload, falling back to `owner`"); @@ -18,13 +18,14 @@ export async function issueClosed() { } // assign function incentivesCalculation to a variable - const calculateIncentives = await incentivesCalculation(); - const creatorReward = await calculateIssueCreatorReward(calculateIncentives); + const calculateIncentives = await incentivesCalculation(context); + const creatorReward = await calculateIssueCreatorReward(context, calculateIncentives); const assigneeReward = await calculateIssueAssigneeReward(calculateIncentives); - const conversationRewards = await calculateIssueConversationReward(calculateIncentives); - const pullRequestReviewersReward = await calculateReviewContributorRewards(calculateIncentives); + const conversationRewards = await calculateIssueConversationReward(context, calculateIncentives); + const pullRequestReviewersReward = await calculateReviewContributorRewards(context, calculateIncentives); await handleIssueClosed({ + context, creatorReward, assigneeReward, conversationRewards, @@ -35,10 +36,9 @@ export async function issueClosed() { return logger.ok("Issue closed successfully"); } -function getEssentials() { +function getEssentials(context: Context) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const issue = payload.issue; if (!issue) throw runtime.logger.error("Missing issue in payload"); return { diff --git a/src/handlers/comment/handlers/issue/issue-reopened.ts b/src/handlers/comment/handlers/issue/issue-reopened.ts index 9bab74694..7746a68e2 100644 --- a/src/handlers/comment/handlers/issue/issue-reopened.ts +++ b/src/handlers/comment/handlers/issue/issue-reopened.ts @@ -8,24 +8,24 @@ import { getTokenSymbol, } from "../../../../helpers"; // import { Payload } from "../../../../types"; -import { Payload } from "../../../../types/payload"; +import { Payload, Context } from "../../../../types"; // type IssuePayload = Context<"issues.reopened">; // ["payload"] -export async function issueReopened() { +export async function issueReopened(context: Context) { const runtime = Runtime.getState(); const { logger } = runtime; // if (!eventContext) { // throw new Error("No event context found"); // } - const payload = runtime.latestEventContext.payload as Payload; + const payload = context.event.payload as Payload; const issue = payload.issue; if (!issue) throw logger.error("No issue found in payload", payload); - const comments = await getAllIssueComments(issue.number); - const permitBaseUrl = runtime.botConfig.payout.permitBaseUrl; + const comments = await getAllIssueComments(context, issue.number); + const permitBaseUrl = context.config.payout.permitBaseUrl; const claimUrlRegex = new RegExp(`\\((${permitBaseUrl}\\?claim=\\S+)\\)`); const permitComment = comments.find((e) => e.user.type === "Bot" && e.body.match(claimUrlRegex)); @@ -50,7 +50,7 @@ export async function issueReopened() { const tokenAddress = claim.permit.permitted.token; const tokenSymbol = await getTokenSymbol(tokenAddress, rpc); - const events = await getAllIssueAssignEvents(issue.number); + const events = await getAllIssueAssignEvents(context, issue.number); if (events.length === 0) { return logger.warn(`No assignment found`); } diff --git a/src/handlers/comment/handlers/labels.ts b/src/handlers/comment/handlers/labels.ts index d31d5c686..6b4a893e7 100644 --- a/src/handlers/comment/handlers/labels.ts +++ b/src/handlers/comment/handlers/labels.ts @@ -1,15 +1,14 @@ import Runtime from "../../../bindings/bot-runtime"; import { isUserAdminOrBillingManager } from "../../../helpers"; -import { Payload } from "../../../types"; +import { Context, Payload } from "../../../types"; -export async function setLabels(body: string) { +export async function setLabels(context: Context, body: string) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; const logger = runtime.logger; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const sender = payload.sender.login; - const sufficientPrivileges = await isUserAdminOrBillingManager(sender, context); + const sufficientPrivileges = await isUserAdminOrBillingManager(context, sender); if (!sufficientPrivileges) return logger.info(`You are not an admin and do not have the required permissions to access this function.`); // if sender is not admin, return diff --git a/src/handlers/comment/handlers/multiplier.ts b/src/handlers/comment/handlers/multiplier.ts index bfd6f5955..122afeb97 100644 --- a/src/handlers/comment/handlers/multiplier.ts +++ b/src/handlers/comment/handlers/multiplier.ts @@ -1,6 +1,6 @@ import Runtime from "../../../bindings/bot-runtime"; import { isUserAdminOrBillingManager } from "../../../helpers"; -import { Payload } from "../../../types"; +import { Context, Payload } from "../../../types"; /** * You can use this command to set a multiplier for a user. * It will accept arguments in any order. @@ -12,11 +12,10 @@ import { Payload } from "../../../types"; * /multiplier 0.5 "Multiplier reason" @user * /multiplier @user "Multiplier reason" 0.5 **/ -export async function multiplier(body: string) { +export async function multiplier(context: Context, body: string) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; const logger = runtime.logger; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const sender = payload.sender.login; const repo = payload.repository; const comment = payload.comment; @@ -45,7 +44,7 @@ export async function multiplier(body: string) { username = username || sender; // check if sender is admin or billing_manager // passing in context so we don't have to make another request to get the user - const sufficientPrivileges = await isUserAdminOrBillingManager(sender, context); + const sufficientPrivileges = await isUserAdminOrBillingManager(context, sender); // if sender is not admin or billing_manager, check db for access if (sufficientPrivileges) { diff --git a/src/handlers/comment/handlers/payout.ts b/src/handlers/comment/handlers/payout.ts index afdeeccb9..c4ae4ff8e 100644 --- a/src/handlers/comment/handlers/payout.ts +++ b/src/handlers/comment/handlers/payout.ts @@ -1,11 +1,10 @@ import Runtime from "../../../bindings/bot-runtime"; -import { Payload } from "../../../types"; +import { Context, Payload } from "../../../types"; import { isUserAdminOrBillingManager } from "../../../helpers/issue"; -export async function autoPay(body: string) { +export async function autoPay(context: Context, body: string) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const logger = runtime.logger; logger.info("Running '/autopay' command handler", { sender: payload.sender.login }); @@ -14,7 +13,7 @@ export async function autoPay(body: string) { const autopayCommand = body.match(pattern); if (autopayCommand) { - const hasSufficientPrivileges = await isUserAdminOrBillingManager(payload.sender.login, context); + const hasSufficientPrivileges = await isUserAdminOrBillingManager(context, payload.sender.login); if (!hasSufficientPrivileges) { return logger.warn( "You must be an 'admin' or 'billing_manager' to toggle automatic payments for completed issues." diff --git a/src/handlers/comment/handlers/query.ts b/src/handlers/comment/handlers/query.ts index 12e842e9a..ff783dbd0 100644 --- a/src/handlers/comment/handlers/query.ts +++ b/src/handlers/comment/handlers/query.ts @@ -1,12 +1,11 @@ import Runtime from "../../../bindings/bot-runtime"; -import { Payload } from "../../../types"; +import { Context, Payload } from "../../../types"; import _ from "lodash"; -export async function query(body: string) { +export async function query(context: Context, body: string) { const runtime = Runtime.getState(), - context = runtime.latestEventContext, logger = runtime.logger, - payload = context.payload as Payload, + payload = context.event.payload as Payload, sender = payload.sender.login; logger.info("Running '/query' command handler", { sender }); @@ -23,7 +22,7 @@ export async function query(body: string) { } const database = runtime.adapters.supabase; - const usernameResponse = await context.octokit.users.getByUsername({ username }); + const usernameResponse = await context.event.octokit.users.getByUsername({ username }); const user = usernameResponse.data; if (!user) { throw logger.error("User not found", { username }); diff --git a/src/handlers/comment/handlers/unassign.ts b/src/handlers/comment/handlers/unassign.ts index ba372947e..984e55451 100644 --- a/src/handlers/comment/handlers/unassign.ts +++ b/src/handlers/comment/handlers/unassign.ts @@ -1,19 +1,18 @@ import { removeAssignees } from "../../../helpers"; import Runtime from "../../../bindings/bot-runtime"; -import { Payload } from "../../../types"; +import { Context, Payload } from "../../../types"; import { closePullRequestForAnIssue } from "../../assign/index"; -export async function unassign(body: string) { +export async function unassign(context: Context, body: string) { const runtime = Runtime.getState(); - const { payload: _payload } = runtime.latestEventContext; const logger = runtime.logger; if (!body.startsWith("/stop")) { return logger.error("Skipping to unassign", { body }); } - const payload = _payload as Payload; + const payload = context.event.payload as Payload; logger.info("Running '/stop' command handler", { sender: payload.sender.login }); - const issue = (_payload as Payload).issue; + const issue = payload.issue; if (!issue) { return logger.info(`Skipping '/stop' because of no issue instance`); } @@ -32,8 +31,9 @@ export async function unassign(body: string) { }); if (shouldUnassign) { - await closePullRequestForAnIssue(); + await closePullRequestForAnIssue(context); await removeAssignees( + context, issueNumber, assignees.map((i) => i.login) ); diff --git a/src/handlers/comment/handlers/wallet.ts b/src/handlers/comment/handlers/wallet.ts index 329060485..fbbb0de0b 100644 --- a/src/handlers/comment/handlers/wallet.ts +++ b/src/handlers/comment/handlers/wallet.ts @@ -2,7 +2,7 @@ import { constants, ethers } from "ethers"; import { Logs } from "../../../adapters/supabase"; import Runtime from "../../../bindings/bot-runtime"; import { resolveAddress } from "../../../helpers"; -import { Payload } from "../../../types"; +import { Context, Payload } from "../../../types"; // Extracts ensname from raw text. function extractEnsName(text: string) { // Define a regular expression to match ENS names @@ -17,10 +17,10 @@ function extractEnsName(text: string) { } } -export async function registerWallet(body: string) { +export async function registerWallet(context: Context, body: string) { const runtime = Runtime.getState(); - const payload = runtime.latestEventContext.payload as Payload; - const config = runtime.botConfig; + const payload = context.event.payload as Payload; + const config = context.config; const logger = runtime.logger; const sender = payload.sender.login; @@ -60,6 +60,7 @@ export async function registerWallet(body: string) { throw new Error("Payload comment is undefined"); } } + function _registerWalletWithVerification(body: string, address: string, logger: Logs) { const regexForSigHash = /(0x[a-fA-F0-9]{130})/g; const sigHashMatches = body.match(regexForSigHash); diff --git a/src/handlers/label/index.ts b/src/handlers/label/index.ts index cb3125fe9..932bf08bc 100644 --- a/src/handlers/label/index.ts +++ b/src/handlers/label/index.ts @@ -1,13 +1,12 @@ import Runtime from "../../bindings/bot-runtime"; import { hasLabelEditPermission } from "../../helpers"; -import { Payload } from "../../types"; +import { Context, Payload } from "../../types"; -export async function watchLabelChange() { +export async function watchLabelChange(context: Context) { const runtime = Runtime.getState(); const logger = runtime.logger; - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const { label, changes, sender } = payload; const previousLabel = changes?.name?.from; @@ -22,7 +21,7 @@ export async function watchLabelChange() { } // check if user is authorized to make the change - const hasAccess = await hasLabelEditPermission(currentLabel, triggerUser); + const hasAccess = await hasLabelEditPermission(context, currentLabel, triggerUser); const { supabase } = Runtime.getState().adapters; diff --git a/src/handlers/payout/calculate-issue-conversation-reward.ts b/src/handlers/payout/calculate-issue-conversation-reward.ts index 3a039ed09..478576258 100644 --- a/src/handlers/payout/calculate-issue-conversation-reward.ts +++ b/src/handlers/payout/calculate-issue-conversation-reward.ts @@ -1,7 +1,7 @@ import Decimal from "decimal.js"; import Runtime from "../../bindings/bot-runtime"; import { getAllIssueComments, parseComments } from "../../helpers"; -import { Comment, Payload, UserType } from "../../types"; +import { Comment, Context, Payload, UserType } from "../../types"; import { calculateRewardValue } from "./calculate-reward-value"; import { IncentivesCalculationResult } from "./incentives-calculation"; @@ -12,14 +12,14 @@ import { RewardsResponse } from "./handle-issue-closed"; // Incentivize the contributors based on their contribution. // The default formula has been defined in https://github.com/ubiquity/ubiquibot/issues/272 export async function calculateIssueConversationReward( + context: Context, calculateIncentives: IncentivesCalculationResult ): Promise { const title = `Conversation`; const runtime = Runtime.getState(); const logger = runtime.logger; - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const issue = payload.issue; const user = payload.sender; @@ -50,11 +50,11 @@ export async function calculateIssueConversationReward( const assignee = assignees.length > 0 ? assignees[0] : undefined; if (!assignee) throw logger.info("Skipping payment permit generation because `assignee` is `undefined`."); - const issueComments = await getAllIssueComments(calculateIncentives.issue.number, "raw"); + const issueComments = await getAllIssueComments(context, calculateIncentives.issue.number, "raw"); logger.info("Getting the issue comments done.", { issueComments }); const issueCommentsByUser: Record = {}; - walkComments({ issueComments, assignee, logger, issueCommentsByUser }); + walkComments({ context, issueComments, assignee, logger, issueCommentsByUser }); logger.info("Filtering by user type...", { issueCommentsByUser }); diff --git a/src/handlers/payout/calculate-issue-creator-reward.ts b/src/handlers/payout/calculate-issue-creator-reward.ts index 854d4cc0c..c78a10dc3 100644 --- a/src/handlers/payout/calculate-issue-creator-reward.ts +++ b/src/handlers/payout/calculate-issue-creator-reward.ts @@ -1,25 +1,26 @@ import Decimal from "decimal.js"; import Runtime from "../../bindings/bot-runtime"; import { getAllIssueComments, getIssueDescription } from "../../helpers"; -import { UserType } from "../../types"; +import { Context, UserType } from "../../types"; import { taskInfo } from "../wildcard"; import { generatePermitForComment } from "./generate-permit-for-comment"; import { IncentivesCalculationResult } from "./incentives-calculation"; import { RewardsResponse } from "./handle-issue-closed"; export async function calculateIssueCreatorReward( + context: Context, incentivesCalculation: IncentivesCalculationResult ): Promise { const title = `Task Creator`; const runtime = Runtime.getState(); const logger = runtime.logger; - const issueDetailed = taskInfo(incentivesCalculation.issue); + const issueDetailed = taskInfo(context, incentivesCalculation.issue); if (!issueDetailed.isTask) { throw logger.error("its not a funded task"); } - const comments = await getAllIssueComments(incentivesCalculation.issue.number); + const comments = await getAllIssueComments(context, incentivesCalculation.issue.number); const permitComments = comments.filter( (content) => content.body.includes(title) && @@ -37,7 +38,7 @@ export async function calculateIssueCreatorReward( throw logger.error("skipping payment permit generation because `assignee` is `undefined`."); } - const description = await getIssueDescription(incentivesCalculation.issue.number, "html"); + const description = await getIssueDescription(context, incentivesCalculation.issue.number, "html"); if (!description) { throw logger.error( `Skipping to generate a permit url because issue description is empty. description: ${description}` diff --git a/src/handlers/payout/calculate-review-contributor-rewards.ts b/src/handlers/payout/calculate-review-contributor-rewards.ts index a7e248eee..f15f955a7 100644 --- a/src/handlers/payout/calculate-review-contributor-rewards.ts +++ b/src/handlers/payout/calculate-review-contributor-rewards.ts @@ -2,7 +2,7 @@ import Decimal from "decimal.js"; import Runtime from "../../bindings/bot-runtime"; import { getAllIssueComments, getAllPullRequestReviews, parseComments } from "../../helpers"; import { getLatestMergedPullRequest, getLinkedPullRequests } from "../../helpers/parser"; -import { UserType } from "../../types"; +import { Context, UserType } from "../../types"; import { calculateRewardValue } from "./calculate-reward-value"; import { IncentivesCalculationResult } from "./incentives-calculation"; @@ -10,11 +10,11 @@ import { ItemsToExclude } from "./post"; import { RewardsResponse } from "./handle-issue-closed"; export async function calculateReviewContributorRewards( + context: Context, incentivesCalculation: IncentivesCalculationResult ): Promise { const runtime = Runtime.getState(); const logger = runtime.logger; - const context = runtime.latestEventContext; const title = "Reviewer"; const user = incentivesCalculation.issue.user; @@ -24,13 +24,13 @@ export async function calculateReviewContributorRewards( issue: incentivesCalculation.issue.number, }); - const latestLinkedPullRequest = await getLatestMergedPullRequest(linkedPullRequest); + const latestLinkedPullRequest = await getLatestMergedPullRequest(context, linkedPullRequest); if (!latestLinkedPullRequest) { throw logger.info(`No linked pull requests found`); } - const comments = await getAllIssueComments(incentivesCalculation.issue.number); + const comments = await getAllIssueComments(context, incentivesCalculation.issue.number); const permitComments = comments.filter( (content) => content.body.includes(title) && @@ -48,7 +48,7 @@ export async function calculateReviewContributorRewards( } const prReviews = await getAllPullRequestReviews(context, latestLinkedPullRequest.number, "full"); - const prComments = await getAllIssueComments(latestLinkedPullRequest.number, "full"); + const prComments = await getAllIssueComments(context, latestLinkedPullRequest.number, "full"); logger.info(`Getting the pull request reviews done.`, { prReviews }); // I put the brackets around the object to make it easier to read in the logs (you see the variable name) diff --git a/src/handlers/payout/handle-issue-closed.ts b/src/handlers/payout/handle-issue-closed.ts index 0b8b16a5b..0616467d3 100644 --- a/src/handlers/payout/handle-issue-closed.ts +++ b/src/handlers/payout/handle-issue-closed.ts @@ -10,6 +10,7 @@ import { import { IncentivesCalculationResult } from "./incentives-calculation"; interface HandleIssueClosed { + context: Context; creatorReward: RewardsResponse; assigneeReward: RewardsResponse; conversationRewards: RewardsResponse; @@ -27,6 +28,7 @@ interface RewardByUser { debug?: Record; } export async function handleIssueClosed({ + context, creatorReward, assigneeReward, conversationRewards, @@ -35,7 +37,7 @@ export async function handleIssueClosed({ }: HandleIssueClosed) { const runtime = Runtime.getState(); const logger = runtime.logger; - const { comments } = runtime.botConfig; + const { comments } = context.config; const issueNumber = incentivesCalculation.issue.number; const title = ["Issue-Assignee"]; @@ -209,6 +211,7 @@ export async function handleIssueClosed({ } const { payoutUrl, permit } = await generatePermit2Signature( + context, reward.account, reward.priceInDecimal, reward.issueId, @@ -226,15 +229,15 @@ export async function handleIssueClosed({ if (!org) { throw logger.error("org is undefined", incentivesCalculation.payload); } - await savePermitToDB(Number(reward.userId), permit, incentivesCalculation.evmNetworkId, org); + await savePermitToDB(context, Number(reward.userId), permit, incentivesCalculation.evmNetworkId, org); permitComment = comment; logger.warn("Skipping to generate a permit url for missing accounts.", conversationRewards.fallbackReward); } - if (permitComment) await addCommentToIssue(permitComment.trim() + comments.promotionComment, issueNumber); + if (permitComment) await addCommentToIssue(context, permitComment.trim() + comments.promotionComment, issueNumber); - await deleteLabel(incentivesCalculation.issueDetailed.priceLabel); + await deleteLabel(context, incentivesCalculation.issueDetailed.priceLabel); } export interface RewardsResponse { @@ -256,7 +259,7 @@ export interface RewardsResponse { // -import { Comment } from "../../types"; +import { Comment, Context } from "../../types"; interface RemovePenalty { userId: number; amount: Decimal; diff --git a/src/handlers/payout/incentives-calculation.ts b/src/handlers/payout/incentives-calculation.ts index 186423d3b..ec9b7a792 100644 --- a/src/handlers/payout/incentives-calculation.ts +++ b/src/handlers/payout/incentives-calculation.ts @@ -7,7 +7,7 @@ import { wasIssueReopened, getAllIssueAssignEvents, } from "../../helpers"; -import { UserType, Payload, StateReason, Comment, Incentives, Issue, User } from "../../types"; +import { UserType, Payload, StateReason, Comment, Incentives, Issue, User, Context } from "../../types"; import { taskInfo } from "../wildcard"; import { isParentIssue } from "../pricing"; import Decimal from "decimal.js"; @@ -41,17 +41,16 @@ export interface IncentivesCalculationResult { claimUrlRegex: RegExp; } -export async function incentivesCalculation(): Promise { +export async function incentivesCalculation(context: Context): Promise { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; const { payout: { paymentToken, rpc, permitBaseUrl, evmNetworkId, privateKey }, mode: { incentiveMode, permitMaxPrice }, price: { incentives, issueCreatorMultiplier, priceMultiplier }, publicAccessControl: accessControl, - } = runtime.botConfig; + } = context.config; const logger = runtime.logger; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const issue = payload.issue; const { repository, organization } = payload; const id = organization?.id || repository?.id; // repository?.id as fallback @@ -59,13 +58,13 @@ export async function incentivesCalculation(): Promise e.user.type === UserType.Bot && e.body.match(claimUrlRegex)); if (wasReopened && permitCommentIndex !== -1) { @@ -89,7 +88,7 @@ export async function incentivesCalculation(): Promise; } -export function walkComments({ issueComments, assignee, logger, issueCommentsByUser }: WalkComments) { +export function walkComments({ context, issueComments, assignee, logger, issueCommentsByUser }: WalkComments) { for (const issueComment of issueComments) { const user = issueComment.user; if (user.type == UserType.Bot || user.login == assignee.login) continue; - const command = commentParser(issueComment.body); + const command = commentParser(context, issueComment.body); if (command) { logger.info(`Skipping to parse the comment because it contains commands.`, { issueComment }); continue; diff --git a/src/handlers/pricing/action.ts b/src/handlers/pricing/action.ts index d89b7beb7..a6931def2 100644 --- a/src/handlers/pricing/action.ts +++ b/src/handlers/pricing/action.ts @@ -7,13 +7,13 @@ import { getAllLabeledEvents, getLabel, } from "../../helpers"; -import { Label, UserType } from "../../types"; +import { Label, UserType, Context } from "../../types"; -export async function handleParentIssue(labels: Label[]) { +export async function handleParentIssue(context: Context, labels: Label[]) { const runtime = Runtime.getState(); const issuePrices = labels.filter((label) => label.name.toString().startsWith("Price:")); if (issuePrices.length) { - await clearAllPriceLabelsOnIssue(); + await clearAllPriceLabelsOnIssue(context); } throw runtime.logger.warn("Pricing is disabled on parent issues."); } @@ -23,6 +23,7 @@ export function sortLabelsByValue(labels: Label[]) { } export async function handleTargetPriceLabel( + context: Context, targetPriceLabel: string, labelNames: string[], assistivePricing: boolean @@ -30,15 +31,15 @@ export async function handleTargetPriceLabel( const _targetPriceLabel = labelNames.find((name) => name.includes("Price") && name.includes(targetPriceLabel)); if (_targetPriceLabel) { - await handleExistingPriceLabel(targetPriceLabel, assistivePricing); + await handleExistingPriceLabel(context, targetPriceLabel, assistivePricing); } else { - await handleNewPriceLabel(targetPriceLabel, assistivePricing); + await handleNewPriceLabel(context, targetPriceLabel, assistivePricing); } } -async function handleExistingPriceLabel(targetPriceLabel: string, assistivePricing: boolean) { +async function handleExistingPriceLabel(context: Context, targetPriceLabel: string, assistivePricing: boolean) { const logger = Runtime.getState().logger; - let labeledEvents = await getAllLabeledEvents(); + let labeledEvents = await getAllLabeledEvents(context); if (!labeledEvents) return logger.warn("No labeled events found"); labeledEvents = labeledEvents.filter((event) => event.label?.name.includes("Price")); @@ -47,26 +48,26 @@ async function handleExistingPriceLabel(targetPriceLabel: string, assistivePrici if (labeledEvents[labeledEvents.length - 1].actor?.type == UserType.User) { logger.info(`Skipping... already exists`); } else { - await addPriceLabelToIssue(targetPriceLabel, assistivePricing); + await addPriceLabelToIssue(context, targetPriceLabel, assistivePricing); } } -async function handleNewPriceLabel(targetPriceLabel: string, assistivePricing: boolean) { - await addPriceLabelToIssue(targetPriceLabel, assistivePricing); +async function handleNewPriceLabel(context: Context, targetPriceLabel: string, assistivePricing: boolean) { + await addPriceLabelToIssue(context, targetPriceLabel, assistivePricing); } -async function addPriceLabelToIssue(targetPriceLabel: string, assistivePricing: boolean) { +async function addPriceLabelToIssue(context: Context, targetPriceLabel: string, assistivePricing: boolean) { const logger = Runtime.getState().logger; - await clearAllPriceLabelsOnIssue(); + await clearAllPriceLabelsOnIssue(context); - const exist = await getLabel(targetPriceLabel); + const exist = await getLabel(context, targetPriceLabel); console.trace({ exist, assistivePricing }); if (assistivePricing && !exist) { logger.info("Assistive pricing is enabled, creating label...", { targetPriceLabel }); - await createLabel(targetPriceLabel, "price"); + await createLabel(context, targetPriceLabel, "price"); } - await addLabelToIssue(targetPriceLabel); + await addLabelToIssue(context, targetPriceLabel); } export function isParentIssue(body: string) { diff --git a/src/handlers/pricing/pre.ts b/src/handlers/pricing/pre.ts index a4ee66802..434d1db5c 100644 --- a/src/handlers/pricing/pre.ts +++ b/src/handlers/pricing/pre.ts @@ -1,13 +1,14 @@ import Runtime from "../../bindings/bot-runtime"; import { calculateLabelValue, createLabel, listLabelsForRepo } from "../../helpers"; +import { Context } from "../../types"; import { calculateTaskPrice } from "../shared/pricing"; // This just checks all the labels in the config have been set in gh issue // If there's something missing, they will be added -export async function syncPriceLabelsToConfig() { +export async function syncPriceLabelsToConfig(context: Context) { const runtime = Runtime.getState(); - const config = runtime.botConfig; + const config = context.config; const logger = runtime.logger; const { assistivePricing } = config.mode; @@ -23,6 +24,7 @@ export async function syncPriceLabelsToConfig() { for (const timeLabel of config.price.timeLabels) { for (const priorityLabel of config.price.priorityLabels) { const targetPrice = calculateTaskPrice( + context, calculateLabelValue(timeLabel), calculateLabelValue(priorityLabel), config.price.priceMultiplier @@ -36,7 +38,7 @@ export async function syncPriceLabelsToConfig() { logger.debug("Got needed labels for setting up price ", { neededLabels }); // List all the labels for a repository - const repoLabels = await listLabelsForRepo(); + const repoLabels = await listLabelsForRepo(context); // Get the missing labels const missingLabels = neededLabels.filter((label) => !repoLabels.map((i) => i.name).includes(label)); @@ -44,7 +46,7 @@ export async function syncPriceLabelsToConfig() { // Create missing labels if (missingLabels.length > 0) { logger.info("Missing labels found, creating them", { missingLabels }); - await Promise.all(missingLabels.map((label) => createLabel(label))); + await Promise.all(missingLabels.map((label) => createLabel(context, label))); logger.info(`Creating missing labels done`); } } diff --git a/src/handlers/pricing/pricing-label.ts b/src/handlers/pricing/pricing-label.ts index c2b78d238..a8ce46c6e 100644 --- a/src/handlers/pricing/pricing-label.ts +++ b/src/handlers/pricing/pricing-label.ts @@ -1,24 +1,23 @@ import Runtime from "../../bindings/bot-runtime"; import { clearAllPriceLabelsOnIssue } from "../../helpers"; -import { Label, LabelFromConfig, Payload } from "../../types"; +import { Context, Label, LabelFromConfig, Payload } from "../../types"; import { handleLabelsAccess } from "../access"; import { isParentIssue, handleParentIssue, sortLabelsByValue, handleTargetPriceLabel } from "./action"; import { setPrice } from "../shared/pricing"; -export async function pricingLabel() { +export async function pricingLabel(context: Context) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const config = Runtime.getState().botConfig; + const config = context.config; const logger = runtime.logger; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; if (!payload.issue) throw logger.error("Issue is not defined"); const labels = payload.issue.labels; const labelNames = labels.map((i) => i.name); - if (payload.issue.body && isParentIssue(payload.issue.body)) return await handleParentIssue(labels); + if (payload.issue.body && isParentIssue(payload.issue.body)) return await handleParentIssue(context, labels); - if (!(await handleLabelsAccess()) && config.publicAccessControl.setLabel) + if (!(await handleLabelsAccess(context)) && config.publicAccessControl.setLabel) throw logger.warn("No access to set labels"); const { assistivePricing } = config.mode; @@ -39,11 +38,11 @@ export async function pricingLabel() { if (!recognizedTimeLabels.length) { logger.warn("No recognized time labels to calculate price"); - await clearAllPriceLabelsOnIssue(); + await clearAllPriceLabelsOnIssue(context); } if (!recognizedPriorityLabels.length) { logger.warn("No recognized priority labels to calculate price"); - await clearAllPriceLabelsOnIssue(); + await clearAllPriceLabelsOnIssue(context); } const minTimeLabel = sortLabelsByValue(recognizedTimeLabels).shift(); @@ -52,12 +51,12 @@ export async function pricingLabel() { if (!minTimeLabel) return logger.warn(`No time label to calculate price`); if (!minPriorityLabel) return logger.warn("No priority label to calculate price"); - const targetPriceLabel = setPrice(minTimeLabel, minPriorityLabel); + const targetPriceLabel = setPrice(context, minTimeLabel, minPriorityLabel); if (targetPriceLabel) { - await handleTargetPriceLabel(targetPriceLabel, labelNames, assistivePricing); + await handleTargetPriceLabel(context, targetPriceLabel, labelNames, assistivePricing); } else { - await clearAllPriceLabelsOnIssue(); + await clearAllPriceLabelsOnIssue(context); logger.info(`Skipping action...`); } } diff --git a/src/handlers/pull-request/create-devpool-pr.ts b/src/handlers/pull-request/create-devpool-pr.ts index 6e1518f3e..ad5df1774 100644 --- a/src/handlers/pull-request/create-devpool-pr.ts +++ b/src/handlers/pull-request/create-devpool-pr.ts @@ -1,12 +1,11 @@ import Runtime from "../../bindings/bot-runtime"; -import { GithubContent, Payload } from "../../types"; +import { Context, GithubContent, Payload } from "../../types"; -export async function createDevPoolPR() { +export async function createDevPoolPR(context: Context) { const runtime = Runtime.getState(); const logger = runtime.logger; - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const devPoolOwner = "ubiquity"; const devPoolRepo = "devpool-directory"; @@ -25,14 +24,14 @@ export async function createDevPoolPR() { const baseRef = "development"; const path = "projects.json"; - const { data: branch } = await context.octokit.repos.getBranch({ + const { data: branch } = await context.event.octokit.repos.getBranch({ owner: devPoolOwner, repo: devPoolRepo, branch: "development", }); // Get the current projects json file - const { data: file } = await context.octokit.repos.getContent({ + const { data: file } = await context.event.octokit.repos.getContent({ owner: devPoolOwner, repo: devPoolRepo, path, @@ -52,7 +51,7 @@ export async function createDevPoolPR() { const mainSha = branch.commit.sha; // create branch from sha - await context.octokit.git.createRef({ + await context.event.octokit.git.createRef({ owner: devPoolOwner, repo: devPoolRepo, ref: `refs/heads/add-${owner}-${repo}`, @@ -61,7 +60,7 @@ export async function createDevPoolPR() { logger.info("Branch created on DevPool Directory"); - await context.octokit.repos.createOrUpdateFileContents({ + await context.event.octokit.repos.createOrUpdateFileContents({ owner: devPoolOwner, repo: devPoolRepo, path, @@ -72,7 +71,7 @@ export async function createDevPoolPR() { }); // create the pull request - await context.octokit.pulls.create({ + await context.event.octokit.pulls.create({ owner: devPoolOwner, repo: devPoolRepo, title: `Add ${repository.full_name} to repo`, diff --git a/src/handlers/push/check-modified-base-rate.ts b/src/handlers/push/check-modified-base-rate.ts index d7dcc5696..2b6cfb571 100644 --- a/src/handlers/push/check-modified-base-rate.ts +++ b/src/handlers/push/check-modified-base-rate.ts @@ -1,14 +1,13 @@ import Runtime from "../../bindings/bot-runtime"; -import { PushPayload } from "../../types"; +import { PushPayload, Context } from "../../types"; import { updateBaseRate } from "./update-base-rate"; import { ZERO_SHA, getCommitChanges, BASE_RATE_FILE } from "./index"; -export async function checkModifiedBaseRate() { +export async function checkModifiedBaseRate(context: Context) { const runtime = Runtime.getState(); const logger = runtime.logger; - const context = runtime.latestEventContext; - const payload = context.payload as PushPayload; + const payload = context.event.payload as PushPayload; // if zero sha, push is a pr change if (payload.before === ZERO_SHA) { @@ -25,7 +24,7 @@ export async function checkModifiedBaseRate() { // check for modified or added files and check for specified file if (changes.includes(BASE_RATE_FILE)) { // update base rate - await updateBaseRate(context, payload, BASE_RATE_FILE); + await updateBaseRate(context, BASE_RATE_FILE); } logger.debug("Skipping push events, file change empty 2"); } diff --git a/src/handlers/push/index.ts b/src/handlers/push/index.ts index 86b6408f4..a5cf9756b 100644 --- a/src/handlers/push/index.ts +++ b/src/handlers/push/index.ts @@ -1,6 +1,6 @@ import Runtime from "../../bindings/bot-runtime"; import { createCommitComment, getFileContent } from "../../helpers"; -import { CommitsPayload, PushPayload, ConfigSchema } from "../../types"; +import { CommitsPayload, PushPayload, ConfigSchema, Context } from "../../types"; import { parseYAML } from "../../utils/private"; import { validate } from "../../utils/ajv"; @@ -21,12 +21,11 @@ export function getCommitChanges(commits: CommitsPayload[]) { return changes; } -export async function validateConfigChange() { +export async function validateConfigChange(context: Context) { const runtime = Runtime.getState(); const logger = runtime.logger; - const context = runtime.latestEventContext; - const payload = context.payload as PushPayload; + const payload = context.event.payload as PushPayload; if (!payload.ref.startsWith("refs/heads/")) { logger.debug("Skipping push events, not a branch"); @@ -52,6 +51,7 @@ export async function validateConfigChange() { } const configFileContent = await getFileContent( + context, payload.repository.owner.login, payload.repository.name, payload.ref.split("refs/heads/")[1], @@ -65,6 +65,7 @@ export async function validateConfigChange() { const { valid, error } = validate(ConfigSchema, config); if (!valid) { await createCommitComment( + context, `@${payload.sender.login} Config validation failed! ${error}`, commitSha, BASE_RATE_FILE diff --git a/src/handlers/push/update-base-rate.ts b/src/handlers/push/update-base-rate.ts index a24fe07d4..4fd742524 100644 --- a/src/handlers/push/update-base-rate.ts +++ b/src/handlers/push/update-base-rate.ts @@ -1,20 +1,20 @@ -import { Context } from "probot"; import Runtime from "../../bindings/bot-runtime"; import { getPreviousFileContent, listLabelsForRepo, updateLabelsFromBaseRate } from "../../helpers"; -import { Label, PushPayload } from "../../types"; +import { Label, PushPayload, Context } from "../../types"; import { parseYAML } from "../../utils/private"; -export async function updateBaseRate(context: Context, payload: PushPayload, filePath: string) { +export async function updateBaseRate(context: Context, filePath: string) { const runtime = Runtime.getState(); const logger = runtime.logger; // Get default branch from ref + const payload = context.event.payload as PushPayload; const branch = payload.ref?.split("refs/heads/")[1]; const owner = payload.repository.owner.login; const repo = payload.repository.name; // get previous config - const preFileContent = await getPreviousFileContent(owner, repo, branch, filePath); + const preFileContent = await getPreviousFileContent(context, owner, repo, branch, filePath); if (!preFileContent) { logger.debug("Getting previous file content failed"); @@ -31,11 +31,11 @@ export async function updateBaseRate(context: Context, payload: PushPayload, fil const previousBaseRate = previousConfig["priceMultiplier"]; // fetch all labels - const repoLabels = await listLabelsForRepo(); + const repoLabels = await listLabelsForRepo(context); if (repoLabels.length === 0) { return logger.debug("No labels on this repo"); } - return await updateLabelsFromBaseRate(owner, repo, context, repoLabels as Label[], previousBaseRate); + return await updateLabelsFromBaseRate(context, owner, repo, repoLabels as Label[], previousBaseRate); } diff --git a/src/handlers/shared/pricing.ts b/src/handlers/shared/pricing.ts index 1991210c8..80c7f32dc 100644 --- a/src/handlers/shared/pricing.ts +++ b/src/handlers/shared/pricing.ts @@ -1,27 +1,30 @@ import Runtime from "../../bindings/bot-runtime"; import { calculateLabelValue } from "../../helpers"; -import { Label } from "../../types/label"; - -export function calculateTaskPrice(timeValue: number, priorityValue: number, baseValue?: number): number { - const runtime = Runtime.getState(); - const base = baseValue ?? runtime.botConfig.price.priceMultiplier; +import { Label, Context } from "../../types"; + +export function calculateTaskPrice( + context: Context, + timeValue: number, + priorityValue: number, + baseValue?: number +): number { + const base = baseValue ?? context.config.price.priceMultiplier; const priority = priorityValue / 10; // floats cause bad math const price = 1000 * base * timeValue * priority; return price; } -export function setPrice(timeLabel: Label, priorityLabel: Label) { +export function setPrice(context: Context, timeLabel: Label, priorityLabel: Label) { const runtime = Runtime.getState(); const logger = runtime.logger; + const { price } = context.config; if (!timeLabel || !priorityLabel) throw logger.warn("Time or priority label is not defined"); - const recognizedTimeLabels = runtime.botConfig.price.timeLabels.find((item) => item.name === timeLabel.name); + const recognizedTimeLabels = price.timeLabels.find((item) => item.name === timeLabel.name); if (!recognizedTimeLabels) throw logger.warn("Time label is not recognized"); - const recognizedPriorityLabels = runtime.botConfig.price.priorityLabels.find( - (item) => item.name === priorityLabel.name - ); + const recognizedPriorityLabels = price.priorityLabels.find((item) => item.name === priorityLabel.name); if (!recognizedPriorityLabels) throw logger.warn("Priority label is not recognized"); const timeValue = calculateLabelValue(recognizedTimeLabels); @@ -30,6 +33,6 @@ export function setPrice(timeLabel: Label, priorityLabel: Label) { const priorityValue = calculateLabelValue(recognizedPriorityLabels); if (!priorityValue) throw logger.warn("Priority value is not defined"); - const taskPrice = calculateTaskPrice(timeValue, priorityValue); + const taskPrice = calculateTaskPrice(context, timeValue, priorityValue); return `Price: ${taskPrice} USD`; } diff --git a/src/handlers/wildcard/analytics.ts b/src/handlers/wildcard/analytics.ts index e081c0672..39e827b0e 100644 --- a/src/handlers/wildcard/analytics.ts +++ b/src/handlers/wildcard/analytics.ts @@ -1,19 +1,21 @@ -import Runtime from "../../bindings/bot-runtime"; import { calculateLabelValue } from "../../helpers"; -import { Issue } from "../../types"; +import { Issue, Context } from "../../types"; // Checks the issue whether it's an open task for public self assignment -export function taskInfo(issue: Issue): { +export function taskInfo( + context: Context, + issue: Issue +): { isTask: boolean; timeLabel?: string; priorityLabel?: string; priceLabel?: string; } { - const config = Runtime.getState().botConfig; + const { price } = context.config; const labels = issue.labels; - const timeLabels = config.price.timeLabels.filter((item) => labels.map((i) => i.name).includes(item.name)); - const priorityLabels = config.price.priorityLabels.filter((item) => labels.map((i) => i.name).includes(item.name)); + const timeLabels = price.timeLabels.filter((item) => labels.map((i) => i.name).includes(item.name)); + const priorityLabels = price.priorityLabels.filter((item) => labels.map((i) => i.name).includes(item.name)); const isTask = timeLabels.length > 0 && priorityLabels.length > 0; diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index 39a3eeeb8..b958a3a23 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -7,11 +7,11 @@ import { listAllIssuesForRepo, removeAssignees, } from "../../helpers"; -import { Comment, Issue, IssueType, Payload, UserType } from "../../types"; +import { Comment, Context, Issue, IssueType, Payload, UserType } from "../../types"; const requestContributorUpdate = "Do you have any updates"; -export async function checkTasksToUnassign() { +export async function checkTasksToUnassign(context: Context) { // Check out the tasks which haven't been completed within the initial timeline // and release the task back to dev pool const runtime = Runtime.getState(); @@ -20,26 +20,26 @@ export async function checkTasksToUnassign() { // List all the issues in the repository. It may include `pull_request` // because GitHub's REST API v3 considers every pull request an issue - const issues_opened = await listAllIssuesForRepo(IssueType.OPEN); + const issues_opened = await listAllIssuesForRepo(context, IssueType.OPEN); const assigned_issues = issues_opened.filter((issue) => issue.assignee); // Checking the tasks in parallel - const res = await Promise.all(assigned_issues.map(async (issue: Issue) => checkTaskToUnassign(issue))); + const res = await Promise.all(assigned_issues.map(async (issue: Issue) => checkTaskToUnassign(context, issue))); logger.info("Checking expired tasks done!", { total: res.length, unassigned: res.filter((i) => i).length }); } -async function checkTaskToUnassign(issue: Issue) { +async function checkTaskToUnassign(context: Context, issue: Issue) { const runtime = Runtime.getState(); const logger = runtime.logger; - const context = runtime.latestEventContext; - const payload = context.payload as Payload; - const unassign = runtime.botConfig.unassign; - const { disqualifyTime, followUpTime } = unassign; + const payload = context.event.payload as Payload; + const { + unassign: { disqualifyTime, followUpTime }, + } = context.config; logger.info("Checking the task to unassign...", { issue_number: issue.number }); const assignees = issue.assignees.map((i) => i.login); - const comments = await getAllIssueComments(issue.number); + const comments = await getAllIssueComments(context, issue.number); if (!comments || comments.length == 0) return false; const askUpdateComments = comments @@ -51,9 +51,9 @@ async function checkTaskToUnassign(issue: Issue) { ? new Date(askUpdateComments[0].created_at).getTime() : new Date(issue.created_at).getTime(); const curTimestamp = new Date().getTime(); - const lastActivity = await lastActivityTime(issue, comments); + const lastActivity = await lastActivityTime(context, issue, comments); const passedDuration = curTimestamp - lastActivity.getTime(); - const pullRequest = await getOpenedPullRequestsForAnIssue(issue.number, issue.assignee.login); + const pullRequest = await getOpenedPullRequestsForAnIssue(context, issue.number, issue.assignee.login); if (pullRequest.length > 0) { const reviewRequests = await getReviewRequests( @@ -70,7 +70,7 @@ async function checkTaskToUnassign(issue: Issue) { if (passedDuration >= disqualifyTime || passedDuration >= followUpTime) { if (passedDuration >= disqualifyTime) { // remove assignees from the issue - await removeAssignees(issue.number, assignees); + await removeAssignees(context, issue.number, assignees); return logger.warn("The task has been unassigned due to lack of updates", { issue_number: issue.number, @@ -104,7 +104,7 @@ async function checkTaskToUnassign(issue: Issue) { } } -async function lastActivityTime(issue: Issue, comments: Comment[]): Promise { +async function lastActivityTime(context: Context, issue: Issue, comments: Comment[]): Promise { const runtime = Runtime.getState(); const logger = runtime.logger; logger.info("Checking the latest activity for the issue...", { issue_number: issue.number }); @@ -128,16 +128,16 @@ async function lastActivityTime(issue: Issue, comments: Comment[]): Promise 0) activities.push(new Date(lastCommentsOfHunterForIssue[0].created_at)); - const openedPrsForIssue = await getOpenedPullRequestsForAnIssue(issue.number, assignees[0]); + const openedPrsForIssue = await getOpenedPullRequestsForAnIssue(context, issue.number, assignees[0]); const pr = openedPrsForIssue.length > 0 ? openedPrsForIssue[0] : undefined; // get last commit and last comment on the linked pr if (pr) { - const commits = (await getCommitsOnPullRequest(pr.number)) + const commits = (await getCommitsOnPullRequest(context, pr.number)) .filter((it) => it.commit.committer?.date) .sort( (a, b) => new Date(b.commit.committer?.date ?? 0).getTime() - new Date(a.commit.committer?.date ?? 0).getTime() ); - const prComments = (await getAllIssueComments(pr.number)) + const prComments = (await getAllIssueComments(context, pr.number)) .filter((comment) => comment.user.login === assignees[0]) .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); diff --git a/src/helpers/commit.ts b/src/helpers/commit.ts index 82e42a5ad..0c64945be 100644 --- a/src/helpers/commit.ts +++ b/src/helpers/commit.ts @@ -1,16 +1,14 @@ -import Runtime from "../bindings/bot-runtime"; -import { Payload } from "../types"; +import { Context, Payload } from "../types"; export async function createCommitComment( + context: Context, body: string, commitSha: string, path?: string, owner?: string, repo?: string ) { - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; if (!owner) { owner = payload.repository.owner.login; } @@ -18,7 +16,7 @@ export async function createCommitComment( repo = payload.repository.name; } - await context.octokit.rest.repos.createCommitComment({ + await context.event.octokit.rest.repos.createCommitComment({ owner: owner, repo: repo, commit_sha: commitSha, diff --git a/src/helpers/file.ts b/src/helpers/file.ts index d7d9ddae5..5dfa7f0fc 100644 --- a/src/helpers/file.ts +++ b/src/helpers/file.ts @@ -1,14 +1,20 @@ import Runtime from "../bindings/bot-runtime"; +import { Context } from "../types"; // Get the previous file content -export async function getPreviousFileContent(owner: string, repo: string, branch: string, filePath: string) { +export async function getPreviousFileContent( + context: Context, + owner: string, + repo: string, + branch: string, + filePath: string +) { const runtime = Runtime.getState(); const logger = runtime.logger; - const context = runtime.latestEventContext; try { // Get the latest commit of the branch - const branchData = await context.octokit.repos.getBranch({ + const branchData = await context.event.octokit.repos.getBranch({ owner, repo, branch, @@ -16,7 +22,7 @@ export async function getPreviousFileContent(owner: string, repo: string, branch const latestCommitSha = branchData.data.commit.sha; // Get the commit details - const commitData = await context.octokit.repos.getCommit({ + const commitData = await context.event.octokit.repos.getCommit({ owner, repo, ref: latestCommitSha, @@ -27,7 +33,7 @@ export async function getPreviousFileContent(owner: string, repo: string, branch if (file) { // Retrieve the previous file content from the commit's parent const previousCommitSha = commitData.data.parents[0].sha; - const previousCommit = await context.octokit.git.getCommit({ + const previousCommit = await context.event.octokit.git.getCommit({ owner, repo, commit_sha: previousCommitSha, @@ -35,7 +41,7 @@ export async function getPreviousFileContent(owner: string, repo: string, branch // Retrieve the previous file tree const previousTreeSha = previousCommit.data.tree.sha; - const previousTree = await context.octokit.git.getTree({ + const previousTree = await context.event.octokit.git.getTree({ owner, repo, tree_sha: previousTreeSha, @@ -46,7 +52,7 @@ export async function getPreviousFileContent(owner: string, repo: string, branch const previousFile = previousTree.data.tree.find((item) => item.path === filePath); if (previousFile && previousFile.sha) { // Get the previous file content - const previousFileContent = await context.octokit.git.getBlob({ + const previousFileContent = await context.event.octokit.git.getBlob({ owner, repo, file_sha: previousFile.sha, @@ -62,6 +68,7 @@ export async function getPreviousFileContent(owner: string, repo: string, branch } export async function getFileContent( + context: Context, owner: string, repo: string, branch: string, @@ -70,12 +77,11 @@ export async function getFileContent( ): Promise { const runtime = Runtime.getState(); const logger = runtime.logger; - const context = runtime.latestEventContext; try { if (!commitSha) { // Get the latest commit of the branch - const branchData = await context.octokit.repos.getBranch({ + const branchData = await context.event.octokit.repos.getBranch({ owner, repo, branch, @@ -84,7 +90,7 @@ export async function getFileContent( } // Get the commit details - const commitData = await context.octokit.repos.getCommit({ + const commitData = await context.event.octokit.repos.getCommit({ owner, repo, ref: commitSha, @@ -94,7 +100,7 @@ export async function getFileContent( const file = commitData.data.files ? commitData.data.files.find((file) => file.filename === filePath) : undefined; if (file) { // Retrieve the file tree - const tree = await context.octokit.git.getTree({ + const tree = await context.event.octokit.git.getTree({ owner, repo, tree_sha: commitData.data.commit.tree.sha, @@ -105,7 +111,7 @@ export async function getFileContent( const file = tree.data.tree.find((item) => item.path === filePath); if (file && file.sha) { // Get the previous file content - const fileContent = await context.octokit.git.getBlob({ + const fileContent = await context.event.octokit.git.getBlob({ owner, repo, file_sha: file.sha, diff --git a/src/helpers/gpt.ts b/src/helpers/gpt.ts index 920adf505..bc1e3a6bb 100644 --- a/src/helpers/gpt.ts +++ b/src/helpers/gpt.ts @@ -1,5 +1,5 @@ import Runtime from "../bindings/bot-runtime"; -import { Payload, StreamlinedComment, UserType } from "../types"; +import { Context, Payload, StreamlinedComment, UserType } from "../types"; import { getAllIssueComments, getAllLinkedIssuesAndPullsInBody } from "../helpers"; import OpenAI from "openai"; import { CreateChatCompletionRequestMessage } from "openai/resources/chat"; @@ -55,16 +55,16 @@ All replies MUST end with "\n\n ".\n // best used alongside getAllLinkedIssuesAndPullsInBody() in helpers/issue export async function decideContextGPT( + context: Context, chatHistory: CreateChatCompletionRequestMessage[], streamlined: StreamlinedComment[], linkedPRStreamlined: StreamlinedComment[], linkedIssueStreamlined: StreamlinedComment[] ) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; const logger = runtime.logger; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const issue = payload.issue; if (!issue) { @@ -72,9 +72,9 @@ export async function decideContextGPT( } // standard comments - const comments = await getAllIssueComments(issue.number); + const comments = await getAllIssueComments(context, issue.number); // raw so we can grab the tag - const commentsRaw = await getAllIssueComments(issue.number, "raw"); + const commentsRaw = await getAllIssueComments(context, issue.number, "raw"); if (!comments) { logger.info(`Error getting issue comments`); @@ -98,7 +98,7 @@ export async function decideContextGPT( }); // returns the conversational context from all linked issues and prs - const links = await getAllLinkedIssuesAndPullsInBody(issue.number); + const links = await getAllLinkedIssuesAndPullsInBody(context, issue.number); if (typeof links === "string") { return logger.info("Error getting linked issues or prs: ", { links }); @@ -126,16 +126,16 @@ export async function decideContextGPT( ); // we'll use the first response to determine the context of future calls - const res = await askGPT(chatHistory); + const res = await askGPT(context, chatHistory); return res; } -export async function askGPT(chatHistory: CreateChatCompletionRequestMessage[]) { +export async function askGPT(context: Context, chatHistory: CreateChatCompletionRequestMessage[]) { // base askGPT function const runtime = Runtime.getState(); const logger = runtime.logger; - const config = runtime.botConfig; + const config = context.config; if (!config.ask.apiKey) { throw logger.error( diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index 727d2b35a..a943ba679 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -1,4 +1,3 @@ -import { Context } from "probot"; import { AssignEvent, Comment, @@ -11,16 +10,14 @@ import { import { checkRateLimitGit } from "../utils"; import Runtime from "../bindings/bot-runtime"; import { LogReturn } from "../adapters/supabase"; -import { Payload } from "../types/payload"; +import { Payload, Context } from "../types"; type PromiseType = T extends Promise ? U : never; -async function getAllIssueEvents() { - type Event = PromiseType>["data"][0]; - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; +async function getAllIssueEvents(context: Context) { + type Event = PromiseType>["data"][0]; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; if (!payload.issue) return; let shouldFetch = true; @@ -34,7 +31,7 @@ async function getAllIssueEvents() { const repo = payload.repository.name; const owner = payload.repository.owner.login; - const response = await context.octokit.issues.listEvents({ + const response = await context.event.octokit.issues.listEvents({ owner: owner, repo: repo, issue_number: payload.issue.number, @@ -55,17 +52,15 @@ async function getAllIssueEvents() { return events; } -export async function getAllLabeledEvents() { - const events = await getAllIssueEvents(); +export async function getAllLabeledEvents(context: Context) { + const events = await getAllIssueEvents(context); if (!events) return null; return events.filter((event) => event.event === "labeled"); } -export async function clearAllPriceLabelsOnIssue() { +export async function clearAllPriceLabelsOnIssue(context: Context) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; if (!payload.issue) return; const labels = payload.issue.labels; @@ -74,7 +69,7 @@ export async function clearAllPriceLabelsOnIssue() { if (!issuePrices.length) return; try { - await context.octokit.issues.removeLabel({ + await context.event.octokit.issues.removeLabel({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: payload.issue.number, @@ -85,17 +80,16 @@ export async function clearAllPriceLabelsOnIssue() { } } -export async function addLabelToIssue(labelName: string) { +export async function addLabelToIssue(context: Context, labelName: string) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; if (!payload.issue) { throw runtime.logger.error("Issue object is null"); } try { - await context.octokit.issues.addLabels({ + await context.event.octokit.issues.addLabels({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: payload.issue.number, @@ -107,16 +101,15 @@ export async function addLabelToIssue(labelName: string) { } async function listIssuesForRepo( + context: Context, state: "open" | "closed" | "all" = "open", per_page = 100, page = 1, sort: "created" | "updated" | "comments" = "created", direction: "desc" | "asc" = "desc" ) { - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; - const response = await context.octokit.issues.listForRepo({ + const payload = context.event.payload as Payload; + const response = await context.event.octokit.issues.listForRepo({ owner: payload.repository.owner.login, repo: payload.repository.name, state, @@ -135,13 +128,13 @@ async function listIssuesForRepo( } } -export async function listAllIssuesForRepo(state: "open" | "closed" | "all" = "open") { +export async function listAllIssuesForRepo(context: Context, state: "open" | "closed" | "all" = "open") { const issuesArr = [] as Issue[]; const perPage = 100; let fetchDone = false; let curPage = 1; while (!fetchDone) { - const issues = (await listIssuesForRepo(state, perPage, curPage)) as Issue[]; + const issues = (await listIssuesForRepo(context, state, perPage, curPage)) as Issue[]; // push the objects to array issuesArr.push(...issues); @@ -153,7 +146,7 @@ export async function listAllIssuesForRepo(state: "open" | "closed" | "all" = "o return issuesArr; } -export async function addCommentToIssue(message: HandlerReturnValuesNoVoid, issueNumber: number) { +export async function addCommentToIssue(context: Context, message: HandlerReturnValuesNoVoid, issueNumber: number) { let comment = message as string; const runtime = Runtime.getState(); if (message instanceof LogReturn) { @@ -165,11 +158,10 @@ export async function addCommentToIssue(message: HandlerReturnValuesNoVoid, issu const metadataSerializedAsComment = ``; comment = comment.concat(metadataSerializedAsComment); } - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; try { - await context.octokit.issues.createComment({ + await context.event.octokit.issues.createComment({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNumber, @@ -180,28 +172,28 @@ export async function addCommentToIssue(message: HandlerReturnValuesNoVoid, issu } } -export async function upsertLastCommentToIssue(issue_number: number, commentBody: string) { +export async function upsertLastCommentToIssue(context: Context, issue_number: number, commentBody: string) { const runtime = Runtime.getState(); try { - const comments = await getAllIssueComments(issue_number); + const comments = await getAllIssueComments(context, issue_number); if (comments.length > 0 && comments[comments.length - 1].body !== commentBody) - await addCommentToIssue(commentBody, issue_number); + await addCommentToIssue(context, commentBody, issue_number); } catch (e: unknown) { runtime.logger.debug("Upserting last comment failed!", e); } } export async function getIssueDescription( + context: Context, issueNumber: number, format: "raw" | "html" | "text" = "raw" ): Promise { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; - const response = await context.octokit.rest.issues.get({ + const payload = context.event.payload as Payload; + const response = await context.event.octokit.rest.issues.get({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNumber, @@ -228,18 +220,17 @@ export async function getIssueDescription( } export async function getAllIssueComments( + context: Context, issueNumber: number, format: "raw" | "html" | "text" | "full" = "raw" ): Promise { - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const result: Comment[] = []; let shouldFetch = true; let page_number = 1; try { while (shouldFetch) { - const response = await context.octokit.rest.issues.listComments({ + const response = await context.event.octokit.rest.issues.listComments({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNumber, @@ -269,16 +260,14 @@ export async function getAllIssueComments( return result; } -export async function getAllIssueAssignEvents(issueNumber: number): Promise { - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; +export async function getAllIssueAssignEvents(context: Context, issueNumber: number): Promise { + const payload = context.event.payload as Payload; const result: AssignEvent[] = []; let shouldFetch = true; let page_number = 1; try { while (shouldFetch) { - const response = await context.octokit.rest.issues.listEvents({ + const response = await context.event.octokit.rest.issues.listEvents({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNumber, @@ -303,15 +292,13 @@ export async function getAllIssueAssignEvents(issueNumber: number): Promise (new Date(a.created_at) > new Date(b.created_at) ? -1 : 1)); } -export async function wasIssueReopened(issueNumber: number): Promise { - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; +export async function wasIssueReopened(context: Context, issueNumber: number): Promise { + const payload = context.event.payload as Payload; let shouldFetch = true; let page_number = 1; try { while (shouldFetch) { - const response = await context.octokit.rest.issues.listEvents({ + const response = await context.event.octokit.rest.issues.listEvents({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNumber, @@ -336,13 +323,12 @@ export async function wasIssueReopened(issueNumber: number): Promise { return false; } -export async function removeAssignees(issueNumber: number, assignees: string[]) { +export async function removeAssignees(context: Context, issueNumber: number, assignees: string[]) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; try { - await context.octokit.rest.issues.removeAssignees({ + await context.event.octokit.rest.issues.removeAssignees({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNumber, @@ -353,20 +339,20 @@ export async function removeAssignees(issueNumber: number, assignees: string[]) } } -export async function checkUserPermissionForRepoAndOrg(username: string, context: Context): Promise { - const permissionForRepo = await checkUserPermissionForRepo(username, context); - const permissionForOrg = await checkUserPermissionForOrg(username, context); - const userPermission = await isUserAdminOrBillingManager(username, context); +export async function checkUserPermissionForRepoAndOrg(context: Context, username: string): Promise { + const permissionForRepo = await checkUserPermissionForRepo(context, username); + const permissionForOrg = await checkUserPermissionForOrg(context, username); + const userPermission = await isUserAdminOrBillingManager(context, username); return permissionForOrg || permissionForRepo || userPermission === "admin"; } -async function checkUserPermissionForRepo(username: string, context: Context): Promise { +async function checkUserPermissionForRepo(context: Context, username: string): Promise { const runtime = Runtime.getState(); - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; try { - const res = await context.octokit.rest.repos.checkCollaborator({ + const res = await context.event.octokit.rest.repos.checkCollaborator({ owner: payload.repository.owner.login, repo: payload.repository.name, username, @@ -379,14 +365,14 @@ async function checkUserPermissionForRepo(username: string, context: Context): P } } -async function checkUserPermissionForOrg(username: string, context: Context): Promise { +async function checkUserPermissionForOrg(context: Context, username: string): Promise { const runtime = Runtime.getState(); - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; if (!payload.organization) return false; try { - await context.octokit.rest.orgs.checkMembershipForUser({ + await context.event.octokit.rest.orgs.checkMembershipForUser({ org: payload.organization.login, username, }); @@ -399,10 +385,10 @@ async function checkUserPermissionForOrg(username: string, context: Context): Pr } export async function isUserAdminOrBillingManager( - username: string, - context: Context + context: Context, + username: string ): Promise<"admin" | "billing_manager" | false> { - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const isAdmin = await checkIfIsAdmin(); if (isAdmin) return "admin"; @@ -412,7 +398,7 @@ export async function isUserAdminOrBillingManager( return false; async function checkIfIsAdmin() { - const response = await context.octokit.rest.repos.getCollaboratorPermissionLevel({ + const response = await context.event.octokit.rest.repos.getCollaboratorPermissionLevel({ owner: payload.repository.owner.login, repo: payload.repository.name, username, @@ -428,7 +414,7 @@ export async function isUserAdminOrBillingManager( const runtime = Runtime.getState(); if (!payload.organization) throw runtime.logger.error(`No organization found in payload!`); - const { data: membership } = await context.octokit.rest.orgs.getMembershipForUser({ + const { data: membership } = await context.event.octokit.rest.orgs.getMembershipForUser({ org: payload.organization.login, username: payload.repository.owner.login, }); @@ -442,13 +428,12 @@ export async function isUserAdminOrBillingManager( } } -export async function addAssignees(issue: number, assignees: string[]) { +export async function addAssignees(context: Context, issue: number, assignees: string[]) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; try { - await context.octokit.rest.issues.addAssignees({ + await context.event.octokit.rest.issues.addAssignees({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issue, @@ -459,18 +444,17 @@ export async function addAssignees(issue: number, assignees: string[]) { } } -export async function deleteLabel(label: string) { +export async function deleteLabel(context: Context, label: string) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; try { - const response = await context.octokit.rest.search.issuesAndPullRequests({ + const response = await context.event.octokit.rest.search.issuesAndPullRequests({ q: `repo:${payload.repository.owner.login}/${payload.repository.name} label:"${label}" state:open`, }); if (response.data.items.length === 0) { //remove label - await context.octokit.rest.issues.deleteLabel({ + await context.event.octokit.rest.issues.deleteLabel({ owner: payload.repository.owner.login, repo: payload.repository.name, name: label, @@ -481,18 +465,17 @@ export async function deleteLabel(label: string) { } } -export async function removeLabel(name: string) { +export async function removeLabel(context: Context, name: string) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; if (!payload.issue) { runtime.logger.debug("Invalid issue object"); return; } try { - await context.octokit.issues.removeLabel({ + await context.event.octokit.issues.removeLabel({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: payload.issue.number, @@ -504,7 +487,7 @@ export async function removeLabel(name: string) { } export async function getAllPullRequests(context: Context, state: "open" | "closed" | "all" = "open") { - type Pulls = PromiseType>["data"][0]; + type Pulls = PromiseType>["data"][0]; const prArr = [] as Pulls[]; let fetchDone = false; const perPage = 100; @@ -527,8 +510,8 @@ async function getPullRequests( per_page: number, page: number ) { - const payload = context.payload as Payload; - const { data: pulls } = await context.octokit.rest.pulls.list({ + const payload = context.event.payload as Payload; + const { data: pulls } = await context.event.octokit.rest.pulls.list({ owner: payload.repository.owner.login, repo: payload.repository.name, state, @@ -538,12 +521,11 @@ async function getPullRequests( return pulls; } -export async function closePullRequest(pull_number: number) { +export async function closePullRequest(context: Context, pull_number: number) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; try { - await context.octokit.rest.pulls.update({ + await context.event.octokit.rest.pulls.update({ owner: payload.repository.owner.login, repo: payload.repository.name, pull_number, @@ -559,7 +541,7 @@ export async function getAllPullRequestReviews( pull_number: number, format: "raw" | "html" | "text" | "full" = "raw" ) { - type Reviews = PromiseType>["data"][0]; + type Reviews = PromiseType>["data"][0]; const prArr = [] as Reviews[]; let fetchDone = false; const perPage = 100; @@ -585,9 +567,9 @@ async function getPullRequestReviews( ) { const runtime = Runtime.getState(); - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; try { - const { data: reviews } = await context.octokit.rest.pulls.listReviews({ + const { data: reviews } = await context.event.octokit.rest.pulls.listReviews({ owner: payload.repository.owner.login, repo: payload.repository.name, pull_number, @@ -608,7 +590,7 @@ export async function getReviewRequests(context: Context, pull_number: number, o const runtime = Runtime.getState(); try { - const response = await context.octokit.pulls.listRequestedReviewers({ + const response = await context.event.octokit.pulls.listRequestedReviewers({ owner: owner, repo: repo, pull_number: pull_number, @@ -623,9 +605,9 @@ export async function getReviewRequests(context: Context, pull_number: number, o export async function getIssueByNumber(context: Context, issueNumber: number) { const runtime = Runtime.getState(); - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; try { - const { data: issue } = await context.octokit.rest.issues.get({ + const { data: issue } = await context.event.octokit.rest.issues.get({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNumber, @@ -640,8 +622,8 @@ export async function getIssueByNumber(context: Context, issueNumber: number) { export async function getPullByNumber(context: Context, pull: number) { // const runtime = Runtime.getState(); - const payload = context.payload as Payload; - const response = await context.octokit.rest.pulls.get({ + const payload = context.event.payload as Payload; + const response = await context.event.octokit.rest.pulls.get({ owner: payload.repository.owner.login, repo: payload.repository.name, pull_number: pull, @@ -650,16 +632,14 @@ export async function getPullByNumber(context: Context, pull: number) { } // Get issues assigned to a username -export async function getAssignedIssues(username: string) { - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - type Issues = PromiseType>["data"][0]; +export async function getAssignedIssues(context: Context, username: string) { + type Issues = PromiseType>["data"][0]; const issuesArr = [] as Issues[]; let fetchDone = false; const perPage = 100; let curPage = 1; while (!fetchDone) { - const issues = await listIssuesForRepo(IssueType.OPEN, perPage, curPage); + const issues = await listIssuesForRepo(context, IssueType.OPEN, perPage, curPage); // push the objects to array issuesArr.push(...issues); @@ -676,8 +656,8 @@ export async function getAssignedIssues(username: string) { return assigned_issues; } -export async function getOpenedPullRequestsForAnIssue(issueNumber: number, userName: string) { - const pulls = await getOpenedPullRequests(userName); +export async function getOpenedPullRequestsForAnIssue(context: Context, issueNumber: number, userName: string) { + const pulls = await getOpenedPullRequests(context, userName); return pulls.filter((pull) => { if (!pull.body) return false; @@ -691,19 +671,16 @@ export async function getOpenedPullRequestsForAnIssue(issueNumber: number, userN }); } -async function getOpenedPullRequests(username: string) { - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; +async function getOpenedPullRequests(context: Context, username: string) { const prs = await getAllPullRequests(context, "open"); return prs.filter((pr) => !pr.draft && (pr.user?.login === username || !username)); } -export async function getCommitsOnPullRequest(pullNumber: number) { +export async function getCommitsOnPullRequest(context: Context, pullNumber: number) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = runtime.latestEventContext.payload as Payload; + const payload = context.event.payload as Payload; try { - const { data: commits } = await context.octokit.rest.pulls.listCommits({ + const { data: commits } = await context.event.octokit.rest.pulls.listCommits({ owner: payload.repository.owner.login, repo: payload.repository.name, pull_number: pullNumber, @@ -715,15 +692,12 @@ export async function getCommitsOnPullRequest(pullNumber: number) { } } -export async function getAvailableOpenedPullRequests(username: string) { - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - - const unassignConfig = runtime.botConfig.unassign; +export async function getAvailableOpenedPullRequests(context: Context, username: string) { + const unassignConfig = context.config.unassign; const { timeRangeForMaxIssue } = unassignConfig; if (!timeRangeForMaxIssue) return []; - const openedPullRequests = await getOpenedPullRequests(username); + const openedPullRequests = await getOpenedPullRequests(context, username); const result = [] as typeof openedPullRequests; for (let i = 0; i < openedPullRequests.length; i++) { @@ -749,9 +723,8 @@ export async function getAvailableOpenedPullRequests(username: string) { } // Strips out all links from the body of an issue or pull request and fetches the conversational context from each linked issue or pull request -export async function getAllLinkedIssuesAndPullsInBody(issueNumber: number) { +export async function getAllLinkedIssuesAndPullsInBody(context: Context, issueNumber: number) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; const logger = runtime.logger; const issue = await getIssueByNumber(context, issueNumber); @@ -801,8 +774,8 @@ export async function getAllLinkedIssuesAndPullsInBody(issueNumber: number) { login: "system", body: `=============== Pull Request #${pr.number}: ${pr.title} + ===============\n ${pr.body}}`, }); - const prComments = await getAllIssueComments(linkedPrs[i]); - const prCommentsRaw = await getAllIssueComments(linkedPrs[i], "raw"); + const prComments = await getAllIssueComments(context, linkedPrs[i]); + const prCommentsRaw = await getAllIssueComments(context, linkedPrs[i], "raw"); prComments.forEach(async (comment, i) => { if ( comment.user.type == UserType.User || @@ -826,8 +799,8 @@ export async function getAllLinkedIssuesAndPullsInBody(issueNumber: number) { login: "system", body: `=============== Issue #${issue.number}: ${issue.title} + ===============\n ${issue.body} `, }); - const issueComments = await getAllIssueComments(linkedIssues[i]); - const issueCommentsRaw = await getAllIssueComments(linkedIssues[i], "raw"); + const issueComments = await getAllIssueComments(context, linkedIssues[i]); + const issueCommentsRaw = await getAllIssueComments(context, linkedIssues[i], "raw"); issueComments.forEach(async (comment, i) => { if ( comment.user.type == UserType.User || diff --git a/src/helpers/label.ts b/src/helpers/label.ts index c459ea0c9..ff3b19386 100644 --- a/src/helpers/label.ts +++ b/src/helpers/label.ts @@ -1,6 +1,5 @@ -import { Context } from "probot"; import Runtime from "../bindings/bot-runtime"; -import { Label, Payload } from "../types"; +import { Label, Payload, Context } from "../types"; import { deleteLabel } from "./issue"; import { calculateLabelValue } from "../helpers"; import { calculateTaskPrice } from "../handlers/shared/pricing"; @@ -12,12 +11,10 @@ const COLORS = { }; // cspell:enable -export async function listLabelsForRepo(): Promise { - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; +export async function listLabelsForRepo(context: Context): Promise { + const payload = context.event.payload as Payload; - const res = await context.octokit.rest.issues.listLabelsForRepo({ + const res = await context.event.octokit.rest.issues.listLabelsForRepo({ owner: payload.repository.owner.login, repo: payload.repository.name, per_page: 100, @@ -31,14 +28,13 @@ export async function listLabelsForRepo(): Promise { throw new Error(`Failed to fetch lists of labels, code: ${res.status}`); } -export async function createLabel(name: string, labelType?: keyof typeof COLORS): Promise { +export async function createLabel(context: Context, name: string, labelType?: keyof typeof COLORS): Promise { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; const logger = runtime.logger; // console.trace("createLabel", { name, labelType }); - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; try { - await context.octokit.rest.issues.createLabel({ + await context.event.octokit.rest.issues.createLabel({ owner: payload.repository.owner.login, repo: payload.repository.name, name, @@ -49,13 +45,12 @@ export async function createLabel(name: string, labelType?: keyof typeof COLORS) } } -export async function getLabel(name: string): Promise { +export async function getLabel(context: Context, name: string): Promise { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; const logger = runtime.logger; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; try { - const res = await context.octokit.rest.issues.getLabel({ + const res = await context.event.octokit.rest.issues.getLabel({ owner: payload.repository.owner.login, repo: payload.repository.name, name, @@ -70,15 +65,15 @@ export async function getLabel(name: string): Promise { // Function to update labels based on the base rate difference export async function updateLabelsFromBaseRate( + context: Context, owner: string, repo: string, - context: Context, labels: Label[], previousBaseRate: number ) { const runtime = Runtime.getState(); const logger = runtime.logger; - const config = runtime.botConfig; + const config = context.config; const newLabels: string[] = []; const previousLabels: string[] = []; @@ -86,6 +81,7 @@ export async function updateLabelsFromBaseRate( for (const timeLabel of config.price.timeLabels) { for (const priorityLabel of config.price.priorityLabels) { const targetPrice = calculateTaskPrice( + context, calculateLabelValue(timeLabel), calculateLabelValue(priorityLabel), config.price.priceMultiplier @@ -94,6 +90,7 @@ export async function updateLabelsFromBaseRate( newLabels.push(targetPriceLabel); const previousTargetPrice = calculateTaskPrice( + context, calculateLabelValue(timeLabel), calculateLabelValue(priorityLabel), previousBaseRate @@ -117,15 +114,15 @@ export async function updateLabelsFromBaseRate( const labelData = labels.find((obj) => obj["name"] === label) as Label; const index = uniquePreviousLabels.findIndex((obj) => obj === label); - const exist = await getLabel(uniqueNewLabels[index]); + const exist = await getLabel(context, uniqueNewLabels[index]); if (exist) { // we have to delete first logger.debug("Label already exists, deleting it", { label }); - await deleteLabel(uniqueNewLabels[index]); + await deleteLabel(context, uniqueNewLabels[index]); } // we can update safely - await context.octokit.issues.updateLabel({ + await context.event.octokit.issues.updateLabel({ owner, repo, name: label, diff --git a/src/helpers/parser.ts b/src/helpers/parser.ts index e5476a56c..8a364b254 100644 --- a/src/helpers/parser.ts +++ b/src/helpers/parser.ts @@ -2,6 +2,7 @@ import axios from "axios"; import { HTMLElement, parse } from "node-html-parser"; import { getPullByNumber } from "./issue"; import Runtime from "../bindings/bot-runtime"; +import { Context } from "../types"; interface GetLinkedParams { owner: string; @@ -72,9 +73,7 @@ export async function getLinkedPullRequests({ return collection; } -export async function getLatestMergedPullRequest(pulls: GetLinkedResults[]) { - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; +export async function getLatestMergedPullRequest(context: Context, pulls: GetLinkedResults[]) { let latestMergedPullRequest; for (const pullRequest of pulls) { diff --git a/src/helpers/payout.ts b/src/helpers/payout.ts index c1b2f6b25..4c60f08b4 100644 --- a/src/helpers/payout.ts +++ b/src/helpers/payout.ts @@ -12,7 +12,7 @@ */ import { Static } from "@sinclair/typebox"; -import { PayoutConfigSchema } from "../types"; +import { PayoutConfigSchema, Context } from "../types"; import { isUserAdminOrBillingManager } from "./issue"; import Runtime from "../bindings/bot-runtime"; // import { getAccessLevel } from "../adapters/supabase"; @@ -48,11 +48,10 @@ export const getPayoutConfigByNetworkId = (evmNetworkId: number): PayoutConfigPa }; }; -export async function hasLabelEditPermission(label: string, caller: string) { +export async function hasLabelEditPermission(context: Context, label: string, caller: string) { const runtime = Runtime.getState(); - const context = runtime.latestEventContext; const logger = runtime.logger; - const sufficientPrivileges = await isUserAdminOrBillingManager(caller, context); + const sufficientPrivileges = await isUserAdminOrBillingManager(context, caller); // get text before : const match = label.split(":"); diff --git a/src/helpers/permit.ts b/src/helpers/permit.ts index c717e5b0a..eab0e0318 100644 --- a/src/helpers/permit.ts +++ b/src/helpers/permit.ts @@ -3,7 +3,7 @@ import Decimal from "decimal.js"; import { BigNumber, ethers } from "ethers"; import { keccak256, toUtf8Bytes } from "ethers/lib/utils"; import Runtime from "../bindings/bot-runtime"; -import { Payload } from "../types"; +import { Payload, Context } from "../types"; // import { savePermit } from "../adapters/supabase"; const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // same on all networks @@ -46,6 +46,7 @@ export type GeneratedPermit = { export const generatePermit2Signature = async ( // Generates permit2 signature data with `spender` and `amountInETH` + context: Context, spender: string, amountInEth: Decimal, identifier: string, @@ -53,7 +54,7 @@ export const generatePermit2Signature = async ( ): Promise<{ permit: GeneratedPermit; payoutUrl: string }> => { const { payout: { evmNetworkId, privateKey, permitBaseUrl, rpc, paymentToken }, - } = Runtime.getState().botConfig; + } = context.config; const runtime = Runtime.getState(); const logger = runtime.logger; const provider = new ethers.providers.JsonRpcProvider(rpc); @@ -106,6 +107,7 @@ export const generatePermit2Signature = async ( }; export async function savePermitToDB( + context: Context, contributorId: number, txData: GeneratedPermit, evmNetworkId: number, @@ -114,8 +116,7 @@ export async function savePermitToDB( const runtime = Runtime.getState(); const logger = runtime.logger; - const context = runtime.latestEventContext; - const payload = context.payload as Payload; + const payload = context.event.payload as Payload; const comment = payload.comment; if (!comment) throw logger.error("Cannot save permit to DB, missing comment"); diff --git a/src/helpers/shared.ts b/src/helpers/shared.ts index fb2a9db8b..d2b37cad7 100644 --- a/src/helpers/shared.ts +++ b/src/helpers/shared.ts @@ -1,18 +1,15 @@ import ms from "ms"; -import Runtime from "../bindings/bot-runtime"; -import { Label, LabelFromConfig, Payload, UserType } from "../types"; +import { Label, LabelFromConfig, Payload, UserType, Context } from "../types"; const contextNamesToSkip = ["workflow_run"]; -export function shouldSkip() { - const runtime = Runtime.getState(); - const context = runtime.latestEventContext; - const payload = context.payload as Payload; +export function shouldSkip(context: Context) { + const payload = context.event.payload as Payload; const response = { stop: false, reason: null } as { stop: boolean; reason: string | null }; - if (contextNamesToSkip.includes(context.name)) { + if (contextNamesToSkip.includes(context.event.name)) { response.stop = true; - response.reason = `excluded context name: "${context.name}"`; + response.reason = `excluded context name: "${context.event.name}"`; } else if (payload.sender.type === UserType.Bot) { response.stop = true; response.reason = "sender is a bot"; diff --git a/src/types/context.ts b/src/types/context.ts new file mode 100644 index 000000000..73deb69dc --- /dev/null +++ b/src/types/context.ts @@ -0,0 +1,7 @@ +import { Context as ProbotContext } from "probot"; +import { BotConfig } from "./"; + +export interface Context { + event: ProbotContext; + config: BotConfig; +} diff --git a/src/types/handlers.ts b/src/types/handlers.ts index b25a33df8..d42108783 100644 --- a/src/types/handlers.ts +++ b/src/types/handlers.ts @@ -1,14 +1,15 @@ import { LogReturn } from "../adapters/supabase/helpers/tables/logs"; +import { Context } from "./context"; export type HandlerReturnValuesNoVoid = string | LogReturn; -export type MainActionHandler = (/* TODO: context: Context */) => Promise; -type CommandsHandler = (body: string) => Promise; +export type MainActionHandler = (context: Context) => Promise; +type CommandsHandler = (context: Context, body: string) => Promise; -export type PreActionHandler = (/* TODO: context: Context */) => Promise; -export type PostActionHandler = (/* TODO: context: Context */) => Promise; +export type PreActionHandler = (context: Context) => Promise; +export type PostActionHandler = (context: Context) => Promise; -export type WildCardHandler = (/* TODO: context: Context */) => Promise; +export type WildCardHandler = (context: Context) => Promise; /** * @dev A set of handlers to do a pre/main/post action for a given action diff --git a/src/types/index.ts b/src/types/index.ts index 1fac80f09..617ce4e22 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,3 +3,4 @@ export * from "./label"; export * from "./handlers"; export * from "./config"; export * from "./markdown"; +export * from "./context"; diff --git a/src/utils/private.ts b/src/utils/private.ts index 9fa574bf2..fb2a51872 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -6,16 +6,19 @@ import Runtime from "../bindings/bot-runtime"; import { DefaultConfig } from "../configs"; import { upsertLastCommentToIssue } from "../helpers/issue"; import { ConfigSchema, MergedConfig, Payload } from "../types"; -import { Config } from "../types/config"; +import { BotConfig, Config } from "../types/config"; import { validate } from "./ajv"; + const CONFIG_REPO = "ubiquibot-config"; const CONFIG_PATH = ".github/ubiquibot-config.yml"; const KEY_NAME = "privateKeyEncrypted"; const KEY_PREFIX = "HSK_"; + export async function getConfig(context: Context) { const orgConfig = await downloadConfig(context, "org"); const repoConfig = await downloadConfig(context, "repo"); const payload = context.payload as Payload; + let parsedOrg: Config | null; if (typeof orgConfig === "string") { parsedOrg = parseYAML(orgConfig); @@ -26,10 +29,12 @@ export async function getConfig(context: Context) { const { valid, error } = validate(ConfigSchema, parsedOrg); if (!valid) { const err = new Error(`Invalid org config: ${error}`); - if (payload.issue) await upsertLastCommentToIssue(payload.issue.number, err.message); + if (payload.issue) + await upsertLastCommentToIssue({ event: context, config: {} as BotConfig }, payload.issue.number, err.message); throw err; } } + let parsedRepo: Config | null; if (typeof repoConfig === "string") { parsedRepo = parseYAML(repoConfig); @@ -40,11 +45,14 @@ export async function getConfig(context: Context) { const { valid, error } = validate(ConfigSchema, parsedRepo); if (!valid) { const err = new Error(`Invalid repo config: ${error}`); - if (payload.issue) await upsertLastCommentToIssue(payload.issue.number, err.message); + if (payload.issue) + await upsertLastCommentToIssue({ event: context, config: {} as BotConfig }, payload.issue.number, err.message); throw err; } } + const parsedDefault: MergedConfig = DefaultConfig; + const keys = { private: null, public: null } as { private: string | null; public: string | null }; try { if (parsedRepo && parsedRepo[KEY_NAME]) { @@ -61,6 +69,7 @@ export async function getConfig(context: Context) { const configData = { keys, ...mergedConfigData }; return configData; } + async function downloadConfig(context: Context, type: "org" | "repo") { const payload = context.payload as Payload; let repo; @@ -74,19 +83,23 @@ async function downloadConfig(context: Context, type: "org" | "repo") { owner = payload.repository.owner.login; } if (!repo || !owner) return null; + const { data } = await context.octokit.rest.repos.getContent({ owner, repo, path: CONFIG_PATH, mediaType: { format: "raw" }, }); + return data; } + interface MergedConfigs { parsedRepo: Config | null; parsedOrg: Config | null; parsedDefault: MergedConfig; } + export function parseYAML(data?: string) { try { if (data) { @@ -120,8 +133,10 @@ async function getPrivateAndPublicKeys(cipherText: string, keys: { private: stri keys.private = walletPrivateKey?.replace(KEY_PREFIX, ""); return keys; } + async function getScalarKey(X25519_PRIVATE_KEY: string) { const logger = Runtime.getState().logger; + if (X25519_PRIVATE_KEY !== null) { await sodium.ready; // console.trace(); @@ -133,6 +148,7 @@ async function getScalarKey(X25519_PRIVATE_KEY: string) { return null; } } + function mergeConfigs(configs: MergedConfigs) { return merge({}, configs.parsedDefault, configs.parsedOrg, configs.parsedRepo); }