diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index b8a3b23da..4085a7d10 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -1,6 +1,6 @@ import { createClient, SupabaseClient } from "@supabase/supabase-js"; import { getAdapters, getLogger } from "../../../bindings"; -import { Issue, UserProfile } from "../../../types"; +import { BotContext, Issue, UserProfile } from "../../../types"; import { Database } from "../types"; import { InsertPermit, Permit } from "../../../helpers"; import { BigNumber, BigNumberish } from "ethers"; @@ -54,27 +54,27 @@ export const getLastWeeklyTime = async (): Promise => { /** * @dev Updates the last weekly update timestamp */ -export const updateLastWeeklyTime = async (time: Date): Promise => { +export const updateLastWeeklyTime = async (context: BotContext, time: Date): Promise => { const logger = getLogger(); const { supabase } = getAdapters(); const { data, error } = await supabase.from("weekly").select("last_time"); if (error) { - logger.error(`Checking last time failed, error: ${JSON.stringify(error)}`); + logger.error(context, `Checking last time failed, error: ${JSON.stringify(error)}`); throw new Error(`Checking last time failed, error: ${JSON.stringify(error)}`); } if (data && data.length > 0) { const { data, error } = await supabase.from("weekly").update({ last_time: time.toUTCString() }).neq("last_time", time.toUTCString()); if (error) { - logger.error(`Updating last time failed, error: ${JSON.stringify(error)}`); + logger.error(context, `Updating last time failed, error: ${JSON.stringify(error)}`); throw new Error(`Updating last time failed, error: ${JSON.stringify(error)}`); } logger.info(`Updating last time is done, data: ${data}`); } else { const { data, error } = await supabase.from("weekly").insert({ last_time: time.toUTCString() }); if (error) { - logger.error(`Creating last time failed, error: ${JSON.stringify(error)}`); + logger.error(context, `Creating last time failed, error: ${JSON.stringify(error)}`); throw new Error(`Creating last time failed, error: ${JSON.stringify(error)}`); } logger.info(`Creating last time is done, data: ${data}`); @@ -138,12 +138,12 @@ const getDbDataFromUserProfile = (userProfile: UserProfile, additions?: UserProf * Performs an UPSERT on the issues table. * @param issue The issue entity fetched from github event. */ -export const upsertIssue = async (issue: Issue, additions: IssueAdditions): Promise => { +export const upsertIssue = async (context: BotContext, issue: Issue, additions: IssueAdditions): Promise => { const logger = getLogger(); const { supabase } = getAdapters(); const { data, error } = await supabase.from("issues").select("id").eq("issue_number", issue.number); if (error) { - logger.error(`Checking issue failed, error: ${JSON.stringify(error)}`); + logger.error(context, `Checking issue failed, error: ${JSON.stringify(error)}`); throw new Error(`Checking issue failed, error: ${JSON.stringify(error)}`); } @@ -154,14 +154,14 @@ export const upsertIssue = async (issue: Issue, additions: IssueAdditions): Prom .upsert({ id: key, ...getDbDataFromIssue(issue, additions) }) .select(); if (_error) { - logger.error(`Upserting an issue failed, error: ${JSON.stringify(_error)}`); + logger.error(context, `Upserting an issue failed, error: ${JSON.stringify(_error)}`); throw new Error(`Upserting an issue failed, error: ${JSON.stringify(_error)}`); } logger.info(`Upserting an issue done, { data: ${_data}, error: ${_error}`); } else { const { data: _data, error: _error } = await supabase.from("issues").insert(getDbDataFromIssue(issue, additions)); if (_error) { - logger.error(`Creating a new issue record failed, error: ${JSON.stringify(_error)}`); + logger.error(context, `Creating a new issue record failed, error: ${JSON.stringify(_error)}`); throw new Error(`Creating a new issue record failed, error: ${JSON.stringify(_error)}`); } logger.info(`Creating a new issue record done, { data: ${_data}, error: ${_error}`); @@ -172,26 +172,26 @@ export const upsertIssue = async (issue: Issue, additions: IssueAdditions): Prom * Performs an UPSERT on the users table. * @param user The user entity fetched from github event. */ -export const upsertUser = async (user: UserProfile): Promise => { +export const upsertUser = async (context: BotContext, user: UserProfile): Promise => { const logger = getLogger(); const { supabase } = getAdapters(); const { data, error } = await supabase.from("users").select("user_login").eq("user_login", user.login); if (error) { - logger.error(`Checking user failed, error: ${JSON.stringify(error)}`); + logger.error(context, `Checking user failed, error: ${JSON.stringify(error)}`); throw new Error(`Checking user failed, error: ${JSON.stringify(error)}`); } if (data && data.length > 0) { const { data: _data, error: _error } = await supabase.from("users").upsert(getDbDataFromUserProfile(user)).select(); if (_error) { - logger.error(`Upserting a user failed, error: ${JSON.stringify(_error)}`); + logger.error(context, `Upserting a user failed, error: ${JSON.stringify(_error)}`); throw new Error(`Upserting a user failed, error: ${JSON.stringify(_error)}`); } logger.info(`Upserting a user done, { data: ${JSON.stringify(_data)} }`); } else { const { data: _data, error: _error } = await supabase.from("users").insert(getDbDataFromUserProfile(user)); if (_error) { - logger.error(`Creating a new user record failed, error: ${JSON.stringify(_error)}`); + logger.error(context, `Creating a new user record failed, error: ${JSON.stringify(_error)}`); throw new Error(`Creating a new user record failed, error: ${JSON.stringify(_error)}`); } logger.info(`Creating a new user record done, { data: ${JSON.stringify(_data)} }`); @@ -203,13 +203,13 @@ export const upsertUser = async (user: UserProfile): Promise => { * @param username The user name you want to upsert a wallet address for * @param address The account address */ -export const upsertWalletAddress = async (username: string, address: string): Promise => { +export const upsertWalletAddress = async (context: BotContext, username: string, address: string): Promise => { const logger = getLogger(); const { supabase } = getAdapters(); const { data, error } = await supabase.from("wallets").select("user_name").eq("user_name", username); if (error) { - logger.error(`Checking wallet address failed, error: ${JSON.stringify(error)}`); + logger.error(context, `Checking wallet address failed, error: ${JSON.stringify(error)}`); throw new Error(`Checking wallet address failed, error: ${JSON.stringify(error)}`); } @@ -220,7 +220,7 @@ export const upsertWalletAddress = async (username: string, address: string): Pr updated_at: new Date().toUTCString(), }); if (_error) { - logger.error(`Upserting a wallet address failed, error: ${JSON.stringify(_error)}`); + logger.error(context, `Upserting a wallet address failed, error: ${JSON.stringify(_error)}`); throw new Error(`Upserting a wallet address failed, error: ${JSON.stringify(_error)}`); } logger.info(`Upserting a wallet address done, { data: ${JSON.stringify(_data)} }`); @@ -232,7 +232,7 @@ export const upsertWalletAddress = async (username: string, address: string): Pr updated_at: new Date().toUTCString(), }); if (error) { - logger.error(`Creating a new wallet_table record failed, error: ${JSON.stringify(error)}`); + logger.error(context, `Creating a new wallet_table record failed, error: ${JSON.stringify(error)}`); throw new Error(`Creating a new wallet_table record failed, error: ${JSON.stringify(error)}`); } logger.info(`Creating a new wallet_table record done, { data: ${JSON.stringify(data)}, address: $address }`); @@ -244,13 +244,13 @@ export const upsertWalletAddress = async (username: string, address: string): Pr * @param username The user name you want to upsert a wallet address for * @param address The account multiplier */ -export const upsertWalletMultiplier = async (username: string, multiplier: string, reason: string, org_id: string): Promise => { +export const upsertWalletMultiplier = async (context: BotContext, username: string, multiplier: string, reason: string, org_id: string): Promise => { const logger = getLogger(); const { supabase } = getAdapters(); const { data, error } = await supabase.from("multiplier").select("user_id").eq("user_id", `${username}_${org_id}`); if (error) { - logger.error(`Checking wallet multiplier failed, error: ${JSON.stringify(error)}`); + logger.error(context, `Checking wallet multiplier failed, error: ${JSON.stringify(error)}`); throw new Error(`Checking wallet multiplier failed, error: ${JSON.stringify(error)}`); } @@ -262,7 +262,7 @@ export const upsertWalletMultiplier = async (username: string, multiplier: strin updated_at: new Date().toUTCString(), }); if (_error) { - logger.error(`Upserting a wallet multiplier failed, error: ${JSON.stringify(_error)}`); + logger.error(context, `Upserting a wallet multiplier failed, error: ${JSON.stringify(_error)}`); throw new Error(`Upserting a wallet multiplier failed, error: ${JSON.stringify(_error)}`); } logger.info(`Upserting a wallet multiplier done, { data: ${JSON.stringify(_data)} }`); @@ -275,7 +275,7 @@ export const upsertWalletMultiplier = async (username: string, multiplier: strin updated_at: new Date().toUTCString(), }); if (_error) { - logger.error(`Creating a new multiplier record failed, error: ${JSON.stringify(_error)}`); + logger.error(context, `Creating a new multiplier record failed, error: ${JSON.stringify(_error)}`); throw new Error(`Creating a new multiplier record failed, error: ${JSON.stringify(_error)}`); } logger.info(`Creating a new multiplier record done, { data: ${JSON.stringify(_data)} }`); @@ -289,13 +289,13 @@ export const upsertWalletMultiplier = async (username: string, multiplier: strin * @param access Access granting * @param bool Disabling or enabling */ -export const upsertAccessControl = async (username: string, repository: string, access: string, bool: boolean): Promise => { +export const upsertAccessControl = async (context: BotContext, username: string, repository: string, access: string, bool: boolean): Promise => { const logger = getLogger(); const { supabase } = getAdapters(); const { data, error } = await supabase.from("access").select("user_name").eq("user_name", username).eq("repository", repository); if (error) { - logger.error(`Checking access control failed, error: ${JSON.stringify(error)}`); + logger.error(context, `Checking access control failed, error: ${JSON.stringify(error)}`); throw new Error(`Checking access control failed, error: ${JSON.stringify(error)}`); } @@ -309,7 +309,7 @@ export const upsertAccessControl = async (username: string, repository: string, if (data && data.length > 0) { const { data: _data, error: _error } = await supabase.from("access").upsert(properties); if (_error) { - logger.error(`Upserting a access control failed, error: ${JSON.stringify(_error)}`); + logger.error(context, `Upserting a access control failed, error: ${JSON.stringify(_error)}`); throw new Error(`Upserting a access control failed, error: ${JSON.stringify(_error)}`); } logger.info(`Upserting a access control done, { data: ${JSON.stringify(_data)} }`); @@ -323,7 +323,7 @@ export const upsertAccessControl = async (username: string, repository: string, ...properties, }); if (_error) { - logger.error(`Creating a new access control record failed, error: ${JSON.stringify(_error)}`); + logger.error(context, `Creating a new access control record failed, error: ${JSON.stringify(_error)}`); throw new Error(`Creating a new access control record failed, error: ${JSON.stringify(_error)}`); } logger.info(`Creating a new access control record done, { data: ${JSON.stringify(_data)} }`); diff --git a/src/adapters/supabase/helpers/log.ts b/src/adapters/supabase/helpers/log.ts index 81bbf9d6b..5b19c3ec3 100644 --- a/src/adapters/supabase/helpers/log.ts +++ b/src/adapters/supabase/helpers/log.ts @@ -1,6 +1,6 @@ import axios from "axios"; -import { getAdapters, getBotContext, Logger } from "../../../bindings"; -import { Payload, LogLevel, LogNotification } from "../../../types"; +import { getAdapters, Logger } from "../../../bindings"; +import { BotContext, Payload, LogLevel, LogNotification } from "../../../types"; import { getOrgAndRepoFromPath } from "../../../utils/private"; import jwt from "jsonwebtoken"; interface Log { @@ -44,14 +44,18 @@ export class GitHubLogger implements Logger { private retryDelay = 1000; // Delay between retries in milliseconds private throttleCount = 0; private retryLimit = 0; // Retries disabled by default + + private context; private logNotification; - constructor(app: string, logEnvironment: string, maxLevel: LogLevel, retryLimit: number, logNotification: LogNotification) { + constructor(context: BotContext, app: string, logEnvironment: string, maxLevel: LogLevel, retryLimit: number, logNotification: LogNotification) { this.app = app; this.logEnvironment = logEnvironment; this.maxLevel = getNumericLevel(maxLevel); this.retryLimit = retryLimit; this.supabase = getAdapters().supabase; + + this.context = context; this.logNotification = logNotification; } @@ -83,8 +87,7 @@ export class GitHubLogger implements Logger { } } - private sendDataWithJwt(message: string | object, errorPayload?: string | object) { - const context = getBotContext(); + private sendDataWithJwt(context: BotContext, message: string | object, errorPayload?: string | object) { const payload = context.payload as Payload; const { comment, issue, repository } = payload; @@ -195,8 +198,7 @@ export class GitHubLogger implements Logger { private save(logMessage: string | object, level: LogLevel, errorPayload?: string | object) { if (getNumericLevel(level) > this.maxLevel) return; // only return errors lower than max level - const context = getBotContext(); - const payload = context.payload as Payload; + const payload = this.context.payload as Payload; const timestamp = new Date().toUTCString(); const { comment, issue, repository } = payload; @@ -230,9 +232,9 @@ export class GitHubLogger implements Logger { this.save(message, LogLevel.INFO, errorPayload); } - warn(message: string | object, errorPayload?: string | object) { + warn(context: BotContext, message: string | object, errorPayload?: string | object) { this.save(message, LogLevel.WARN, errorPayload); - this.sendDataWithJwt(message, errorPayload) + this.sendDataWithJwt(context, message, errorPayload) .then((response) => { this.save(`Log Notification Success: ${response}`, LogLevel.DEBUG, ""); }) @@ -245,9 +247,9 @@ export class GitHubLogger implements Logger { this.save(message, LogLevel.DEBUG, errorPayload); } - error(message: string | object, errorPayload?: string | object) { + error(context: BotContext, message: string | object, errorPayload?: string | object) { this.save(message, LogLevel.ERROR, errorPayload); - this.sendDataWithJwt(message, errorPayload) + this.sendDataWithJwt(context, message, errorPayload) .then((response) => { this.save(`Log Notification Success: ${response}`, LogLevel.DEBUG, ""); }) diff --git a/src/adapters/telegram/helpers/client.ts b/src/adapters/telegram/helpers/client.ts index 4a54afc09..1ced31f41 100644 --- a/src/adapters/telegram/helpers/client.ts +++ b/src/adapters/telegram/helpers/client.ts @@ -1,5 +1,6 @@ import { Input } from "telegraf"; -import { getAdapters, getBotConfig } from "../../../bindings"; +import { getAdapters } from "../../../bindings"; +import { BotContext } from "../../../types"; import { TLMessageFormattedPayload, TLMessagePayload, TLPhotoPayload } from "../types/payload"; export function messageFormatter(messagePayload: TLMessagePayload) { @@ -14,10 +15,10 @@ export function messageFormatter(messagePayload: TLMessagePayload) { return msgObj; } -export async function telegramFormattedNotifier(messagePayload: TLMessageFormattedPayload) { +export async function telegramFormattedNotifier(context: BotContext, messagePayload: TLMessageFormattedPayload) { const { telegram: { delay }, - } = getBotConfig(); + } = context.botConfig; const { telegram } = getAdapters(); const { chatIds, text, parseMode } = messagePayload; @@ -40,14 +41,14 @@ export async function telegramFormattedNotifier(messagePayload: TLMessageFormatt sendHandler(); } -export async function telegramNotifier(messagePayload: TLMessagePayload) { +export async function telegramNotifier(context: BotContext, messagePayload: TLMessagePayload) { const messageString = messageFormatter(messagePayload); const messageObj: TLMessageFormattedPayload = { chatIds: messagePayload.chatIds, text: messageString, parseMode: "HTML", }; - await telegramFormattedNotifier(messageObj); + await telegramFormattedNotifier(context, messageObj); } export async function telegramPhotoNotifier(messagePayload: TLPhotoPayload) { diff --git a/src/bindings/config.ts b/src/bindings/config.ts index 55d0fb8be..457bb8591 100644 --- a/src/bindings/config.ts +++ b/src/bindings/config.ts @@ -1,12 +1,11 @@ import ms from "ms"; -import { BotConfig, BotConfigSchema, LogLevel } from "../types"; +import { BotContext, BotConfig, BotConfigSchema, LogLevel } from "../types"; import { getPayoutConfigByNetworkId } from "../helpers"; import { ajv } from "../utils"; -import { Context } from "probot"; import { getScalarKey, getWideConfig } from "../utils/private"; -export const loadConfig = async (context: Context): Promise => { +export const loadConfig = async (context: BotContext): Promise => { const { baseMultiplier, timeLabels, @@ -65,9 +64,7 @@ export const loadConfig = async (context: Context): Promise => { permitBaseUrl: process.env.PERMIT_BASE_URL || permitBaseUrl, }, unassign: { - timeRangeForMaxIssue: process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE - ? Number(process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE) - : timeRangeForMaxIssue, + timeRangeForMaxIssue: process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE ? Number(process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE) : timeRangeForMaxIssue, timeRangeForMaxIssueEnabled: process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED ? process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED == "true" : timeRangeForMaxIssueEnabled, diff --git a/src/bindings/event.ts b/src/bindings/event.ts index e3895541a..8c544cca6 100644 --- a/src/bindings/event.ts +++ b/src/bindings/event.ts @@ -1,28 +1,22 @@ -import { Context } from "probot"; import { createAdapters } from "../adapters"; import { processors, wildcardProcessors } from "../handlers/processors"; import { shouldSkip } from "../helpers"; -import { BotConfig, GithubEvent, Payload, PayloadSchema, LogLevel } from "../types"; +import { BotContext, GithubEvent, Payload, PayloadSchema, LogLevel } from "../types"; +import { Context as ProbotContext } from "probot"; import { Adapters } from "../types/adapters"; import { ajv } from "../utils"; import { loadConfig } from "./config"; import { GitHubLogger } from "../adapters/supabase"; import { validateConfigChange } from "../handlers/push"; -let botContext: Context = {} as Context; -export const getBotContext = () => botContext; - -let botConfig: BotConfig = {} as BotConfig; -export const getBotConfig = () => botConfig; - let adapters: Adapters = {} as Adapters; export const getAdapters = () => adapters; export type Logger = { info: (msg: string | object, options?: JSON) => void; debug: (msg: string | object, options?: JSON) => void; - warn: (msg: string | object, options?: JSON) => void; - error: (msg: string | object, options?: JSON) => void; + warn: (context: BotContext, msg: string | object, options?: JSON) => void; + error: (context: BotContext, msg: string | object, options?: JSON) => void; }; let logger: Logger; @@ -30,21 +24,21 @@ export const getLogger = (): Logger => logger; const NO_VALIDATION = [GithubEvent.INSTALLATION_ADDED_EVENT as string, GithubEvent.PUSH_EVENT as string]; -export const bindEvents = async (context: Context): Promise => { +export const bindEvents = async (_context: ProbotContext): Promise => { + const context = _context as BotContext; const { id, name } = context; - botContext = context; const payload = context.payload as Payload; const allowedEvents = Object.values(GithubEvent) as string[]; const eventName = payload.action ? `${name}.${payload.action}` : name; // some events wont have actions as this grows let botConfigError; try { - botConfig = await loadConfig(context); + context.botConfig = await loadConfig(context); } catch (err) { botConfigError = err; } - adapters = createAdapters(botConfig); + adapters = createAdapters(context.botConfig); const options = { app: "UbiquiBot", @@ -52,20 +46,21 @@ export const bindEvents = async (context: Context): Promise => { }; logger = new GitHubLogger( + context, options.app, - botConfig?.log?.logEnvironment ?? "development", - botConfig?.log?.level ?? LogLevel.DEBUG, - botConfig?.log?.retryLimit ?? 0, - botConfig.logNotification + context.botConfig?.log?.logEnvironment ?? "development", + context.botConfig?.log?.level ?? LogLevel.DEBUG, + context.botConfig?.log?.retryLimit ?? 0, + context.botConfig?.logNotification ); // contributors will see logs in console while on development env if (!logger) { return; } if (botConfigError) { - logger.error(botConfigError.toString()); + logger.error(context, botConfigError.toString()); if (eventName === GithubEvent.PUSH_EVENT) { - await validateConfigChange(); + await validateConfigChange(context); } return; } @@ -75,11 +70,11 @@ export const bindEvents = async (context: Context): Promise => { logger.info( `Config loaded! config: ${JSON.stringify({ - price: botConfig.price, - unassign: botConfig.unassign, - mode: botConfig.mode, - log: botConfig.log, - wallet: botConfig.wallet, + price: context.botConfig.price, + unassign: context.botConfig.unassign, + mode: context.botConfig.mode, + log: context.botConfig.log, + wallet: context.botConfig.wallet, })}` ); @@ -98,12 +93,12 @@ export const bindEvents = async (context: Context): Promise => { const valid = validate(payload); if (!valid) { logger.info("Payload schema validation failed!!!", payload); - if (validate.errors) logger.warn(validate.errors); + if (validate.errors) logger.warn(context, validate.errors); return; } // Check if we should skip the event - const { skip, reason } = shouldSkip(); + const { skip, reason } = shouldSkip(context); if (skip) { logger.info(`Skipping the event. reason: ${reason}`); return; @@ -113,7 +108,7 @@ export const bindEvents = async (context: Context): Promise => { // Get the handlers for the action const handlers = processors[eventName]; if (!handlers) { - logger.warn(`No handler configured for event: ${eventName}`); + logger.warn(context, `No handler configured for event: ${eventName}`); return; } @@ -121,18 +116,18 @@ export const bindEvents = async (context: Context): Promise => { // Run pre-handlers logger.info(`Running pre handlers: ${pre.map((fn) => fn.name)}, event: ${eventName}`); for (const preAction of pre) { - await preAction(); + await preAction(context); } // Run main handlers logger.info(`Running main handlers: ${action.map((fn) => fn.name)}, event: ${eventName}`); for (const mainAction of action) { - await mainAction(); + await mainAction(context); } // Run post-handlers logger.info(`Running post handlers: ${post.map((fn) => fn.name)}, event: ${eventName}`); for (const postAction of post) { - await postAction(); + await postAction(context); } // Skip wildcard handlers for installation event and push event @@ -140,7 +135,7 @@ export const bindEvents = async (context: Context): Promise => { // Run wildcard handlers logger.info(`Running wildcard handlers: ${wildcardProcessors.map((fn) => fn.name)}`); for (const wildcardProcessor of wildcardProcessors) { - await wildcardProcessor(); + await wildcardProcessor(context); } } }; diff --git a/src/handlers/access/labels-access.ts b/src/handlers/access/labels-access.ts index 228aa5a38..3a688f96b 100644 --- a/src/handlers/access/labels-access.ts +++ b/src/handlers/access/labels-access.ts @@ -1,20 +1,19 @@ import { getAccessLevel } from "../../adapters/supabase"; -import { getBotConfig, getBotContext, getLogger } from "../../bindings"; +import { getLogger } from "../../bindings"; import { addCommentToIssue, getUserPermission, removeLabel, addLabelToIssue } from "../../helpers"; -import { Payload } from "../../types"; +import { BotContext, Payload } from "../../types"; -export const handleLabelsAccess = async () => { - const { accessControl } = getBotConfig(); +export const handleLabelsAccess = async (context: BotContext) => { + const { accessControl } = context.botConfig; if (!accessControl.label) return true; - const context = getBotContext(); const logger = getLogger(); const payload = context.payload as Payload; if (!payload.issue) return; if (!payload.label?.name) return; const sender = payload.sender.login; const repo = payload.repository; - const permissionLevel = await getUserPermission(sender, context); + const permissionLevel = await getUserPermission(context, sender); // event in plain english const eventName = payload.action === "labeled" ? "add" : "remove"; const labelName = payload.label.name; @@ -34,12 +33,12 @@ export const handleLabelsAccess = async () => { 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(`@${sender} is not allowed to ${eventName} ${labelName}`); return false; } diff --git a/src/handlers/assign/action.ts b/src/handlers/assign/action.ts index d02d1589c..2da5a9c0f 100644 --- a/src/handlers/assign/action.ts +++ b/src/handlers/assign/action.ts @@ -1,13 +1,12 @@ -import { getBotConfig, getBotContext, getLogger } from "../../bindings"; +import { getLogger } from "../../bindings"; import { addCommentToIssue, closePullRequest, calculateWeight, calculateDuration } from "../../helpers"; import { gitLinkedPrParser } from "../../helpers/parser"; -import { Payload, LabelItem } from "../../types"; +import { Payload, LabelItem, BotContext } from "../../types"; import { deadLinePrefix } from "../shared"; const exclude_accounts: string[] = []; -export const commentWithAssignMessage = async (): Promise => { - const context = getBotContext(); - const config = getBotConfig(); +export const commentWithAssignMessage = async (context: BotContext): Promise => { + const config = context.botConfig; const logger = getLogger(); const payload = context.payload as Payload; if (!payload.issue) { @@ -64,16 +63,15 @@ export const commentWithAssignMessage = async (): Promise => { const commitMessage = `${flattened_assignees} ${deadLinePrefix} ${endDate.toUTCString().replace("GMT", "UTC")}`; logger.debug(`Creating an issue comment, commit_msg: ${commitMessage}`); - await addCommentToIssue(commitMessage, payload.issue?.number); + await addCommentToIssue(context, commitMessage, payload.issue?.number); }; -export const closePullRequestForAnIssue = async (): Promise => { - const context = getBotContext(); +export const closePullRequestForAnIssue = async (context: BotContext): Promise => { const logger = getLogger(); const payload = context.payload as Payload; if (!payload.issue?.number) return; - const prs = await gitLinkedPrParser({ + const prs = await gitLinkedPrParser(context, { owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: payload.issue.number, @@ -84,8 +82,8 @@ export const closePullRequestForAnIssue = async (): Promise => { logger.info(`Opened prs for this issue: ${JSON.stringify(prs)}`); let comment = `These linked pull requests are closed: `; for (let i = 0; i < prs.length; i++) { - await closePullRequest(prs[i].prNumber); + await closePullRequest(context, prs[i].prNumber); comment += ` #${prs[i].prNumber} `; } - await addCommentToIssue(comment, payload.issue.number); + await addCommentToIssue(context, comment, payload.issue.number); }; diff --git a/src/handlers/assign/auto.ts b/src/handlers/assign/auto.ts index 380b59983..069fa4783 100644 --- a/src/handlers/assign/auto.ts +++ b/src/handlers/assign/auto.ts @@ -1,11 +1,10 @@ -import { getBotContext, getLogger } from "../../bindings"; +import { getLogger } from "../../bindings"; import { addAssignees, getAllPullRequests, getIssueByNumber, getPullByNumber } from "../../helpers"; import { gitLinkedIssueParser } from "../../helpers/parser"; -import { Payload } from "../../types"; +import { BotContext, Payload } from "../../types"; // Check for pull requests linked to their respective issues but not assigned to them -export const checkPullRequests = async () => { - const context = getBotContext(); +export const checkPullRequests = async (context: BotContext) => { const logger = getLogger(); const pulls = await getAllPullRequests(context); @@ -18,7 +17,7 @@ export const checkPullRequests = async () => { // Loop through the pull requests and assign them to their respective issues if needed for (const pull of pulls) { - const linkedIssue = await gitLinkedIssueParser({ + const linkedIssue = await gitLinkedIssueParser(context, { owner: payload.repository.owner.login, repo: payload.repository.name, pull_number: pull.number, @@ -53,7 +52,7 @@ export const checkPullRequests = async () => { 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 #${pull.number} opener to issue ${linkedIssueNumber}.`); } } diff --git a/src/handlers/comment/action.ts b/src/handlers/comment/action.ts index 60ea5bbae..419152748 100644 --- a/src/handlers/comment/action.ts +++ b/src/handlers/comment/action.ts @@ -1,13 +1,12 @@ -import { getBotConfig, getBotContext, getLogger } from "../../bindings"; -import { Payload } from "../../types"; +import { getLogger } from "../../bindings"; +import { BotContext, Payload } from "../../types"; import { ErrorDiff } from "../../utils/helpers"; import { IssueCommentCommands } from "./commands"; import { commentParser, userCommands } from "./handlers"; import { verifyFirstCheck } from "./handlers/first"; -export const handleComment = async (): Promise => { - const context = getBotContext(); - const config = getBotConfig(); +export const handleComment = async (context: BotContext): Promise => { + const config = context.botConfig; const logger = getLogger(); const payload = context.payload as Payload; @@ -22,11 +21,11 @@ export const handleComment = async (): Promise => { const commentedCommands = commentParser(body); if (commentedCommands.length === 0) { - await verifyFirstCheck(); + await verifyFirstCheck(context); return; } - const allCommands = userCommands(); + const allCommands = userCommands(context); for (const command of commentedCommands) { const userCommand = allCommands.find((i) => i.id == command); @@ -34,7 +33,7 @@ export const handleComment = async (): Promise => { const { id, handler, callback, successComment, failureComment } = userCommand; logger.info(`Running a comment handler: ${handler.name}`); - const { payload: _payload } = getBotContext(); + const { payload: _payload } = context; const issue = (_payload as Payload).issue; if (!issue) continue; @@ -42,20 +41,20 @@ export const handleComment = async (): Promise => { if (!feature?.enabled && id !== IssueCommentCommands.HELP) { logger.info(`Skipping '${id}' because it is disabled on this repo.`); - await callback(issue.number, `Skipping \`${id}\` because it is disabled on this repo.`, payload.action, payload.comment); + await callback(context, issue.number, `Skipping \`${id}\` because it is disabled on this repo.`, payload.action, payload.comment); continue; } try { - const response = await handler(body); + const response = await handler(context, body); const callbackComment = response ?? successComment ?? ""; - if (callbackComment) await callback(issue.number, callbackComment, payload.action, payload.comment); + if (callbackComment) await callback(context, issue.number, callbackComment, payload.action, payload.comment); } catch (err: unknown) { // Use failureComment for failed command if it is available if (failureComment) { - await callback(issue.number, failureComment, payload.action, payload.comment); + await callback(context, issue.number, failureComment, payload.action, payload.comment); } - await callback(issue.number, ErrorDiff(err), payload.action, payload.comment); + await callback(context, issue.number, ErrorDiff(err), payload.action, payload.comment); } } else { logger.info(`Skipping for a command: ${command}`); diff --git a/src/handlers/comment/handlers/allow.ts b/src/handlers/comment/handlers/allow.ts index aa867985c..a43b9b5e0 100644 --- a/src/handlers/comment/handlers/allow.ts +++ b/src/handlers/comment/handlers/allow.ts @@ -1,10 +1,9 @@ import { upsertAccessControl } from "../../../adapters/supabase"; -import { getBotContext, getLogger } from "../../../bindings"; +import { getLogger } from "../../../bindings"; import { getUserPermission } from "../../../helpers"; -import { Payload } from "../../../types"; +import { BotContext, Payload } from "../../../types"; -export const setAccess = async (body: string) => { - const context = getBotContext(); +export const setAccess = async (context: BotContext, body: string) => { const logger = getLogger(); const payload = context.payload as Payload; const sender = payload.sender.login; @@ -31,7 +30,7 @@ export const setAccess = async (body: string) => { else if (part === "true" || part === "false") bool = part; }); if (!accessType || !username || !bool) { - logger.error("Invalid body for allow command"); + logger.error(context, "Invalid body for allow command"); return `Invalid syntax for allow \n usage: '/allow set-(access type) @user true|false' \n ex-1 /allow set-multiplier @user false`; } // Check if access control demand is valid @@ -42,7 +41,7 @@ export const setAccess = async (body: string) => { // check if sender is admin // passing in context so we don't have to make another request to get the user - const permissionLevel = await getUserPermission(sender, context); + const permissionLevel = await getUserPermission(context, sender); // if sender is not admin, return if (permissionLevel !== "admin") { @@ -53,10 +52,10 @@ export const setAccess = async (body: string) => { // convert accessType to valid table const tableName = `${accessType}_access`; - await upsertAccessControl(username, repo.full_name, tableName, bool === "true"); + await upsertAccessControl(context, username, repo.full_name, tableName, bool === "true"); return `Updated access for @${username} successfully!\t Access: **${accessType}** for "${repo.full_name}"`; } else { - logger.error("Invalid body for allow command"); + logger.error(context, "Invalid body for allow command"); return `Invalid syntax for allow \n usage: '/allow set-(access type) @user true|false' \n ex-1 /allow set-multiplier @user false`; } }; diff --git a/src/handlers/comment/handlers/ask.ts b/src/handlers/comment/handlers/ask.ts index 63777d4ae..e48d80ba6 100644 --- a/src/handlers/comment/handlers/ask.ts +++ b/src/handlers/comment/handlers/ask.ts @@ -1,5 +1,5 @@ -import { getBotContext, getLogger } from "../../../bindings"; -import { Payload, StreamlinedComment, UserType } from "../../../types"; +import { getLogger } from "../../../bindings"; +import { BotContext, Payload, StreamlinedComment, UserType } from "../../../types"; import { getAllIssueComments, getAllLinkedIssuesAndPullsInBody } from "../../../helpers"; import { CreateChatCompletionRequestMessage } from "openai/resources/chat"; import { askGPT, decideContextGPT, sysMsg } from "../../../helpers/gpt"; @@ -8,8 +8,7 @@ import { ErrorDiff } from "../../../utils/helpers"; /** * @param body The question to ask */ -export const ask = async (body: string) => { - const context = getBotContext(); +export const ask = async (context: BotContext, body: string) => { const logger = getLogger(); const payload = context.payload as Payload; @@ -36,9 +35,9 @@ export const ask = async (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) { logger.info(`Error getting issue comments`); @@ -62,7 +61,7 @@ export const ask = async (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}`); @@ -72,7 +71,7 @@ export const ask = async (body: string) => { } // let chatgpt deduce what is the most relevant context - const gptDecidedContext = await decideContextGPT(chatHistory, streamlined, linkedPRStreamlined, linkedIssueStreamlined); + const gptDecidedContext = await decideContextGPT(context, chatHistory, streamlined, linkedPRStreamlined, linkedIssueStreamlined); if (linkedIssueStreamlined.length == 0 && linkedPRStreamlined.length == 0) { // No external context to add @@ -108,7 +107,7 @@ export const ask = async (body: string) => { ); } - const gptResponse = await askGPT(body, chatHistory); + const gptResponse = await askGPT(context, body, chatHistory); if (typeof gptResponse === "string") { return gptResponse; diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index d4ab030ef..b17d84bf0 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -1,6 +1,6 @@ import { addAssignees, getAssignedIssues, getAvailableOpenedPullRequests, getAllIssueComments, calculateWeight, calculateDuration } from "../../../helpers"; -import { getBotConfig, getBotContext, getLogger } from "../../../bindings"; -import { Payload, LabelItem, Comment, IssueType, Issue } from "../../../types"; +import { getLogger } from "../../../bindings"; +import { Payload, LabelItem, Comment, IssueType, Issue, BotContext } from "../../../types"; import { deadLinePrefix } from "../../shared"; import { getWalletAddress, getWalletMultiplier } from "../../../adapters/supabase"; import { tableComment } from "./table"; @@ -8,10 +8,10 @@ import { bountyInfo } from "../../wildcard"; import { GLOBAL_STRINGS } from "../../../configs"; import { isParentIssue } from "../../pricing"; -export const assign = async (body: string) => { - const { payload: _payload } = getBotContext(); +export const assign = async (context: BotContext, body: string) => { + const { payload: _payload } = context; const logger = getLogger(); - const config = getBotConfig(); + const config = context.botConfig; const payload = _payload as Payload; const { repository, organization } = payload; @@ -39,10 +39,10 @@ export const assign = async (body: string) => { return GLOBAL_STRINGS.ignoreStartCommandForParentIssueComment; } - 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.bountyHunterMax}`); // check for max and enforce max @@ -110,7 +110,7 @@ export const assign = async (body: string) => { if (!assignees.map((i) => i.login).includes(payload.sender.login)) { logger.info(`Adding the assignee: ${payload.sender.login}`); - await addAssignees(issue.number, [payload.sender.login]); + await addAssignees(context, issue.number, [payload.sender.login]); } let days: number | undefined; @@ -125,17 +125,17 @@ export const assign = async (body: string) => { // double check whether the assign message has been already posted or not logger.info(`Creating an issue comment: ${comment.commit}`); - 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()); const latestComment = comments.length > 0 ? comments[0].body : undefined; if (latestComment && comment.commit != latestComment) { - const { multiplier, reason, bounty } = await getMultiplierInfoToDisplay(payload.sender.login, id?.toString(), issue); + const { multiplier, reason, bounty } = await getMultiplierInfoToDisplay(context, payload.sender.login, id?.toString(), issue); return tableComment({ ...comment, multiplier, reason, bounty, isBountyStale, days }) + comment.tips; } return; }; -const getMultiplierInfoToDisplay = async (senderLogin: string, org_id: string, issue: Issue) => { +const getMultiplierInfoToDisplay = async (context: BotContext, senderLogin: string, org_id: string, issue: Issue) => { const { reason, value } = await getWalletMultiplier(senderLogin, org_id); const multiplier = value?.toFixed(2) || "1.00"; @@ -154,7 +154,7 @@ const getMultiplierInfoToDisplay = async (senderLogin: string, org_id: string, i _multiplierToDisplay = multiplier; _reasonToDisplay = reason; _bountyToDisplay = `Permit generation disabled because price label is not set.`; - const issueDetailed = bountyInfo(issue); + const issueDetailed = bountyInfo(context, issue); if (issueDetailed.priceLabel) { _bountyToDisplay = (+issueDetailed.priceLabel.substring(7, issueDetailed.priceLabel.length - 4) * value).toString() + " USD"; } diff --git a/src/handlers/comment/handlers/authorize.ts b/src/handlers/comment/handlers/authorize.ts index 03918cded..33a4c5be7 100644 --- a/src/handlers/comment/handlers/authorize.ts +++ b/src/handlers/comment/handlers/authorize.ts @@ -1,12 +1,11 @@ import { _approveLabelChange, getLabelChanges } from "../../../adapters/supabase"; -import { getBotContext, getLogger } from "../../../bindings"; +import { getLogger } from "../../../bindings"; import { getUserPermission } from "../../../helpers"; -import { Payload } from "../../../types"; +import { BotContext, Payload } from "../../../types"; import { ErrorDiff } from "../../../utils/helpers"; import { bountyInfo } from "../../wildcard"; -export const approveLabelChange = async () => { - const context = getBotContext(); +export const approveLabelChange = async (context: BotContext) => { const logger = getLogger(); const payload = context.payload as Payload; const sender = payload.sender.login; @@ -21,7 +20,7 @@ export const approveLabelChange = async () => { // check if sender is admin // passing in context so we don't have to make another request to get the user - const permissionLevel = await getUserPermission(sender, context); + const permissionLevel = await getUserPermission(context, sender); // if sender is not admin, return if (permissionLevel !== "admin" && permissionLevel !== "billing_manager") { @@ -29,7 +28,7 @@ export const approveLabelChange = async () => { return ErrorDiff(`You are not an admin/billing_manager and do not have the required permissions to access this function.`); } - const issueDetailed = bountyInfo(issue); + const issueDetailed = bountyInfo(context, issue); if (!issueDetailed.priceLabel || !issueDetailed.priorityLabel || !issueDetailed.timelabel) { logger.info(`Skipping... its not a bounty`); diff --git a/src/handlers/comment/handlers/first.ts b/src/handlers/comment/handlers/first.ts index 77e898420..c2bf354c8 100644 --- a/src/handlers/comment/handlers/first.ts +++ b/src/handlers/comment/handlers/first.ts @@ -1,17 +1,16 @@ -import { getBotConfig, getBotContext, getLogger } from "../../../bindings"; +import { getLogger } from "../../../bindings"; import { upsertCommentToIssue } from "../../../helpers"; -import { Payload } from "../../../types"; +import { BotContext, Payload } from "../../../types"; import { generateHelpMenu } from "./help"; -export const verifyFirstCheck = async (): Promise => { - const context = getBotContext(); +export const verifyFirstCheck = async (context: BotContext): Promise => { const logger = getLogger(); const payload = context.payload as Payload; let msg = ""; if (!payload.issue) return; const { newContributorGreeting: { header, helpMenu, footer, enabled }, - } = getBotConfig(); + } = context.botConfig; try { const response_issue = await context.octokit.rest.search.issuesAndPullRequests({ q: `is:issue repo:${payload.repository.owner.login}/${payload.repository.name} commenter:${payload.sender.login}`, @@ -37,12 +36,12 @@ export const verifyFirstCheck = async (): Promise => { msg += `${header}\n`; } if (helpMenu) { - msg += `${generateHelpMenu()}\n@${payload.sender.login}\n`; + msg += `${generateHelpMenu(context)}\n@${payload.sender.login}\n`; } if (footer) { msg += `${footer}`; } - await upsertCommentToIssue(payload.issue.number, msg, payload.action, payload.comment); + await upsertCommentToIssue(context, payload.issue.number, msg, payload.action, payload.comment); } } } catch (error: unknown) { diff --git a/src/handlers/comment/handlers/help.ts b/src/handlers/comment/handlers/help.ts index c2f545049..f956eb6fc 100644 --- a/src/handlers/comment/handlers/help.ts +++ b/src/handlers/comment/handlers/help.ts @@ -1,10 +1,10 @@ import { userCommands } from "."; -import { getBotConfig, getBotContext, getLogger } from "../../../bindings"; -import { IssueType, Payload } from "../../../types"; +import { getLogger } from "../../../bindings"; +import { BotContext, IssueType, Payload } from "../../../types"; import { IssueCommentCommands } from "../commands"; -export const listAvailableCommands = async (body: string) => { - const { payload: _payload } = getBotContext(); +export const listAvailableCommands = async (context: BotContext, body: string) => { + const { payload: _payload } = context; const logger = getLogger(); if (body != IssueCommentCommands.HELP && body.replace(/`/g, "") != IssueCommentCommands.HELP) { logger.info(`Skipping to list available commands. body: ${body}`); @@ -23,14 +23,14 @@ export const listAvailableCommands = async (body: string) => { return; } - return generateHelpMenu(); + return generateHelpMenu(context); }; -export const generateHelpMenu = () => { - const config = getBotConfig(); - const startEnabled = config.command.find((command) => command.name === "start"); +export const generateHelpMenu = (context: BotContext) => { + const startEnabled = context.botConfig.command.find((command) => command.name === "start"); + let helpMenu = "### Available commands\n```"; - const commands = userCommands(); + const commands = userCommands(context); commands.map((command) => { // if first command, add a new line if (command.id === commands[0].id) { diff --git a/src/handlers/comment/handlers/index.ts b/src/handlers/comment/handlers/index.ts index 4dcbf7f21..66c1e4006 100644 --- a/src/handlers/comment/handlers/index.ts +++ b/src/handlers/comment/handlers/index.ts @@ -1,4 +1,4 @@ -import { Comment, Payload, UserCommands } from "../../../types"; +import { BotContext, Comment, Payload, UserCommands } from "../../../types"; import { IssueCommentCommands } from "../commands"; import { assign } from "./assign"; import { listAvailableCommands } from "./help"; @@ -24,7 +24,7 @@ import { getAllIssueAssignEvents, calculateWeight, } from "../../../helpers"; -import { getBotConfig, getBotContext, getLogger } from "../../../bindings"; +import { getLogger } from "../../../bindings"; import { handleIssueClosed, incentivesCalculation, @@ -91,26 +91,26 @@ export const commentParser = (body: string): IssueCommentCommands[] => { * Callback for issues closed - Processor */ -export const issueClosedCallback = async (): Promise => { - const { payload: _payload } = getBotContext(); +export const issueClosedCallback = async (context: BotContext): Promise => { + const { payload: _payload } = context; const issue = (_payload as Payload).issue; if (!issue) return; try { // assign function incentivesCalculation to a variable - const calculateIncentives = await incentivesCalculation(); + const calculateIncentives = await incentivesCalculation(context); - const creatorReward = await calculateIssueCreatorReward(calculateIncentives); + const creatorReward = await calculateIssueCreatorReward(context, calculateIncentives); const assigneeReward = await calculateIssueAssigneeReward(calculateIncentives); - const conversationRewards = await calculateIssueConversationReward(calculateIncentives); - const pullRequestReviewersReward = await calculatePullRequestReviewsReward(calculateIncentives); + const conversationRewards = await calculateIssueConversationReward(context, calculateIncentives); + const pullRequestReviewersReward = await calculatePullRequestReviewsReward(context, calculateIncentives); - const { error } = await handleIssueClosed(creatorReward, assigneeReward, conversationRewards, pullRequestReviewersReward, calculateIncentives); + const { error } = await handleIssueClosed(context, creatorReward, assigneeReward, conversationRewards, pullRequestReviewersReward, calculateIncentives); if (error) { throw new Error(error); } } catch (err: unknown) { - return await addCommentToIssue(ErrorDiff(err), issue.number); + return await addCommentToIssue(context, ErrorDiff(err), issue.number); } }; @@ -118,10 +118,10 @@ export const issueClosedCallback = async (): Promise => { * Callback for issues created - Processor */ -export const issueCreatedCallback = async (): Promise => { +export const issueCreatedCallback = async (context: BotContext): Promise => { const logger = getLogger(); - const { payload: _payload } = getBotContext(); - const config = getBotConfig(); + const { payload: _payload } = context; + const config = context.botConfig; const issue = (_payload as Payload).issue; if (!issue) return; const labels = issue.labels; @@ -141,17 +141,17 @@ export const issueCreatedCallback = async (): Promise => { timeLabels.length > 0 ? timeLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : config.price.defaultLabels[0]; const minPriorityLabel = priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : config.price.defaultLabels[1]; - if (!timeLabels.length) await addLabelToIssue(minTimeLabel); - if (!priorityLabels.length) await addLabelToIssue(minPriorityLabel); + if (!timeLabels.length) await addLabelToIssue(context, minTimeLabel); + if (!priorityLabels.length) await addLabelToIssue(context, minPriorityLabel); - const targetPriceLabel = getTargetPriceLabel(minTimeLabel, minPriorityLabel); + const targetPriceLabel = getTargetPriceLabel(context, minTimeLabel, minPriorityLabel); if (targetPriceLabel && !labels.map((i) => i.name).includes(targetPriceLabel)) { - const exist = await getLabel(targetPriceLabel); - if (!exist) await createLabel(targetPriceLabel, "price"); - await addLabelToIssue(targetPriceLabel); + const exist = await getLabel(context, targetPriceLabel); + if (!exist) await createLabel(context, targetPriceLabel, "price"); + await addLabelToIssue(context, targetPriceLabel); } } catch (err: unknown) { - await addCommentToIssue(ErrorDiff(err), issue.number); + await addCommentToIssue(context, ErrorDiff(err), issue.number); } }; @@ -159,18 +159,18 @@ export const issueCreatedCallback = async (): Promise => { * Callback for issues reopened - Processor */ -export const issueReopenedCallback = async (): Promise => { - const { payload: _payload } = getBotContext(); +export const issueReopenedCallback = async (context: BotContext): Promise => { + const { payload: _payload } = context; const { payout: { permitBaseUrl }, - } = getBotConfig(); + } = context.botConfig; const logger = getLogger(); const issue = (_payload as Payload).issue; const repository = (_payload as Payload).repository; if (!issue) return; try { // find permit comment from the bot - const comments = await getAllIssueComments(issue.number); + const comments = await getAllIssueComments(context, issue.number); const claimUrlRegex = new RegExp(`\\((${permitBaseUrl}\\?claim=\\S+)\\)`); const permitCommentIdx = comments.findIndex((e) => e.user.type === "Bot" && e.body.match(claimUrlRegex)); if (permitCommentIdx === -1) { @@ -181,13 +181,13 @@ export const issueReopenedCallback = async (): Promise => { const permitComment = comments[permitCommentIdx]; const permitUrl = permitComment.body.match(claimUrlRegex); if (!permitUrl || permitUrl.length < 2) { - logger.error(`Permit URL not found`); + logger.error(context, `Permit URL not found`); return; } const url = new URL(permitUrl[1]); const claimBase64 = url.searchParams.get("claim"); if (!claimBase64) { - logger.error(`Permit claim search parameter not found`); + logger.error(context, `Permit claim search parameter not found`); return; } let networkId = url.searchParams.get("network"); @@ -199,7 +199,7 @@ export const issueReopenedCallback = async (): Promise => { try { claim = JSON.parse(Buffer.from(claimBase64, "base64").toString("utf-8")); } catch (err: unknown) { - logger.error(`Error parsing claim: ${err}`); + logger.error(context, `Error parsing claim: ${err}`); return; } const amount = BigNumber.from(claim.permit.permitted.amount); @@ -208,9 +208,9 @@ export const issueReopenedCallback = async (): Promise => { const tokenSymbol = await getTokenSymbol(tokenAddress, rpc); // find latest assignment before the permit comment - const events = await getAllIssueAssignEvents(issue.number); + const events = await getAllIssueAssignEvents(context, issue.number); if (events.length === 0) { - logger.error(`No assignment found`); + logger.error(context, `No assignment found`); return; } const assignee = events[0].assignee.login; @@ -220,11 +220,12 @@ export const issueReopenedCallback = async (): Promise => { try { await addPenalty(assignee, repository.full_name, tokenAddress, networkId.toString(), amount); } catch (err) { - logger.error(`Error writing penalty to db: ${err}`); + logger.error(context, `Error writing penalty to db: ${err}`); return; } await addCommentToIssue( + context, `@${assignee} please be sure to review this conversation and implement any necessary fixes. Unless this is closed as completed, its payment of **${formattedAmount} ${tokenSymbol}** will be deducted from your next bounty.`, issue.number ); @@ -232,7 +233,7 @@ export const issueReopenedCallback = async (): Promise => { logger.info(`Skipped penalty because amount is 0`); } } catch (err: unknown) { - await addCommentToIssue(ErrorDiff(err), issue.number); + await addCommentToIssue(context, ErrorDiff(err), issue.number); } }; @@ -244,12 +245,12 @@ export const issueReopenedCallback = async (): Promise => { * @param comment - Comment string */ -const commandCallback = async (issue_number: number, comment: string, action: string, reply_to?: Comment) => { - await upsertCommentToIssue(issue_number, comment, action, reply_to); +const commandCallback = async (context: BotContext, issue_number: number, comment: string, action: string, reply_to?: Comment) => { + await upsertCommentToIssue(context, issue_number, comment, action, reply_to); }; -export const userCommands = (): UserCommands[] => { - const config = getBotConfig(); +export const userCommands = (context: BotContext): UserCommands[] => { + const config = context.botConfig; return [ { diff --git a/src/handlers/comment/handlers/multiplier.ts b/src/handlers/comment/handlers/multiplier.ts index a7cc674eb..109a9a7e6 100644 --- a/src/handlers/comment/handlers/multiplier.ts +++ b/src/handlers/comment/handlers/multiplier.ts @@ -1,10 +1,9 @@ import { getAccessLevel, upsertWalletMultiplier } from "../../../adapters/supabase"; -import { getBotContext, getLogger } from "../../../bindings"; +import { getLogger } from "../../../bindings"; import { getUserPermission } from "../../../helpers"; -import { Payload } from "../../../types"; +import { BotContext, Payload } from "../../../types"; -export const multiplier = async (body: string) => { - const context = getBotContext(); +export const multiplier = async (context: BotContext, body: string) => { const logger = getLogger(); const payload = context.payload as Payload; const sender = payload.sender.login; @@ -55,7 +54,7 @@ export const multiplier = async (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 permissionLevel = await getUserPermission(sender, context); + const permissionLevel = await getUserPermission(context, sender); // if sender is not admin or billing_manager, check db for access if (permissionLevel !== "admin" && permissionLevel !== "billing_manager") { @@ -70,7 +69,7 @@ export const multiplier = async (body: string) => { } logger.info(`Upserting to the wallet table, username: ${username}, bountyMultiplier: ${bountyMultiplier}, reason: ${reason}}`); - await upsertWalletMultiplier(username, bountyMultiplier?.toString(), reason, id?.toString()); + await upsertWalletMultiplier(context, username, bountyMultiplier?.toString(), reason, id?.toString()); if (bountyMultiplier > 1) { return `Successfully changed the payout multiplier for @${username} to ${bountyMultiplier}. The reason ${ reason ? `provided is "${reason}"` : "is not provided" @@ -81,7 +80,7 @@ export const multiplier = async (body: string) => { }.`; } } else { - logger.error("Invalid body for bountyMultiplier command"); + logger.error(context, "Invalid body for bountyMultiplier command"); return `Invalid syntax for wallet command \n example usage: "/multiplier @user 0.5 'Multiplier reason'"`; } }; diff --git a/src/handlers/comment/handlers/payout.ts b/src/handlers/comment/handlers/payout.ts index d5aabc7d6..3d6c26624 100644 --- a/src/handlers/comment/handlers/payout.ts +++ b/src/handlers/comment/handlers/payout.ts @@ -1,5 +1,5 @@ -import { getBotContext, getLogger } from "../../../bindings"; -import { Payload } from "../../../types"; +import { getLogger } from "../../../bindings"; +import { BotContext, Payload } from "../../../types"; import { IssueCommentCommands } from "../commands"; import { calculateIssueAssigneeReward, @@ -12,8 +12,8 @@ import { import { getAllIssueComments, getUserPermission } from "../../../helpers"; import { GLOBAL_STRINGS } from "../../../configs"; -export const payout = async (body: string) => { - const { payload: _payload } = getBotContext(); +export const payout = async (context: BotContext, body: string) => { + const { payload: _payload } = context; const logger = getLogger(); if (body != IssueCommentCommands.PAYOUT && body.replace(/`/g, "") != IssueCommentCommands.PAYOUT) { logger.info(`Skipping to payout. body: ${body}`); @@ -34,7 +34,7 @@ export const payout = async (body: string) => { return; } - const IssueComments = await getAllIssueComments(issue.number); + const IssueComments = await getAllIssueComments(context, issue.number); if (IssueComments.length === 0) { return `Permit generation failed due to internal GitHub Error`; } @@ -46,18 +46,17 @@ export const payout = async (body: string) => { } // assign function incentivesCalculation to a variable - const calculateIncentives = await incentivesCalculation(); + const calculateIncentives = await incentivesCalculation(context); - const creatorReward = await calculateIssueCreatorReward(calculateIncentives); + const creatorReward = await calculateIssueCreatorReward(context, calculateIncentives); const assigneeReward = await calculateIssueAssigneeReward(calculateIncentives); - const conversationRewards = await calculateIssueConversationReward(calculateIncentives); - const pullRequestReviewersReward = await calculatePullRequestReviewsReward(calculateIncentives); + const conversationRewards = await calculateIssueConversationReward(context, calculateIncentives); + const pullRequestReviewersReward = await calculatePullRequestReviewsReward(context, calculateIncentives); - return await handleIssueClosed(creatorReward, assigneeReward, conversationRewards, pullRequestReviewersReward, calculateIncentives); + return await handleIssueClosed(context, creatorReward, assigneeReward, conversationRewards, pullRequestReviewersReward, calculateIncentives); }; -export const autoPay = async (body: string) => { - const context = getBotContext(); +export const autoPay = async (context: BotContext, body: string) => { const _payload = context.payload; const logger = getLogger(); @@ -68,7 +67,7 @@ export const autoPay = async (body: string) => { const res = body.match(pattern); if (res) { - const userPermission = await getUserPermission(payload.sender.login, context); + const userPermission = await getUserPermission(context, payload.sender.login); if (userPermission !== "admin" && userPermission !== "billing_manager") { return "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 c80f1f15a..a2d2e86f5 100644 --- a/src/handlers/comment/handlers/query.ts +++ b/src/handlers/comment/handlers/query.ts @@ -1,10 +1,9 @@ import { getAllAccessLevels, getWalletInfo, upsertAccessControl } from "../../../adapters/supabase"; -import { getBotContext, getLogger } from "../../../bindings"; -import { Payload } from "../../../types"; +import { getLogger } from "../../../bindings"; +import { BotContext, Payload } from "../../../types"; import { ErrorDiff } from "../../../utils/helpers"; -export const query = async (body: string) => { - const context = getBotContext(); +export const query = async (context: BotContext, body: string) => { const logger = getLogger(); const payload = context.payload as Payload; const sender = payload.sender.login; @@ -30,7 +29,7 @@ export const query = async (body: string) => { if (!data) { logger.info(`Access info does not exist for @${user}`); try { - await upsertAccessControl(user, repo.full_name, "time_access", true); + await upsertAccessControl(context, user, repo.full_name, "time_access", true); data = { multiplier: false, priority: false, @@ -57,7 +56,7 @@ export const query = async (body: string) => { `; } } else { - logger.error("Invalid body for query command"); + logger.error(context, "Invalid body for query command"); return `Invalid syntax for query command \n usage /query @user`; } }; diff --git a/src/handlers/comment/handlers/unassign.ts b/src/handlers/comment/handlers/unassign.ts index 91aae184e..e1cdcbc4f 100644 --- a/src/handlers/comment/handlers/unassign.ts +++ b/src/handlers/comment/handlers/unassign.ts @@ -1,11 +1,11 @@ import { removeAssignees } from "../../../helpers"; -import { getBotContext, getLogger } from "../../../bindings"; -import { Payload } from "../../../types"; +import { getLogger } from "../../../bindings"; +import { BotContext, Payload } from "../../../types"; import { IssueCommentCommands } from "../commands"; import { closePullRequestForAnIssue } from "../../assign/index"; -export const unassign = async (body: string) => { - const { payload: _payload } = getBotContext(); +export const unassign = async (context: BotContext, body: string) => { + const { payload: _payload } = context; const logger = getLogger(); if (body != IssueCommentCommands.STOP && body.replace(/`/g, "") != IssueCommentCommands.STOP) { logger.info(`Skipping to unassign. body: ${body}`); @@ -28,8 +28,9 @@ export const unassign = async (body: string) => { logger.debug(`Unassigning sender: ${payload.sender.login.toLowerCase()}, assignee: ${assignees[0].login.toLowerCase()}, shouldUnassign: ${shouldUnassign}`); if (shouldUnassign) { - await closePullRequestForAnIssue(); + await closePullRequestForAnIssue(context); await removeAssignees( + context, issue_number, assignees.map((i) => i.login) ); diff --git a/src/handlers/comment/handlers/wallet.ts b/src/handlers/comment/handlers/wallet.ts index 6cabfd357..f4773eb99 100644 --- a/src/handlers/comment/handlers/wallet.ts +++ b/src/handlers/comment/handlers/wallet.ts @@ -1,8 +1,8 @@ import { ethers } from "ethers"; import { upsertWalletAddress } from "../../../adapters/supabase"; -import { getBotConfig, getBotContext, getLogger } from "../../../bindings"; +import { getLogger } from "../../../bindings"; import { resolveAddress } from "../../../helpers"; -import { Payload } from "../../../types"; +import { BotContext, Payload } from "../../../types"; import { formatEthAddress } from "../../../utils"; import { IssueCommentCommands } from "../commands"; import { constants } from "ethers"; @@ -22,9 +22,9 @@ const extractEnsName = (text: string): string | undefined => { return undefined; }; -export const registerWallet = async (body: string) => { - const { payload: _payload } = getBotContext(); - const config = getBotConfig(); +export const registerWallet = async (context: BotContext, body: string) => { + const { payload: _payload } = context; + const config = context.botConfig; const logger = getLogger(); const payload = _payload as Payload; const sender = payload.sender.login; @@ -44,7 +44,7 @@ export const registerWallet = async (body: string) => { if (!address && ensName) { logger.info(`Trying to resolve address from Ens name: ${ensName}`); - address = await resolveAddress(ensName); + address = await resolveAddress(context, ensName); if (!address) { logger.info(`Resolving address from Ens name failed, EnsName: ${ensName}`); return `Resolving address from Ens name failed, Try again`; @@ -79,7 +79,7 @@ export const registerWallet = async (body: string) => { logger.info("Skipping to register a wallet address because user is trying to set their address to null address"); return `Cannot set address to null address`; } - await upsertWalletAddress(sender, address); + await upsertWalletAddress(context, sender, address); return `Updated the wallet address for @${sender} successfully!\t Your new address: ${formatEthAddress(address)}`; } diff --git a/src/handlers/issue/pre.ts b/src/handlers/issue/pre.ts index bebda5dce..f8c438d84 100644 --- a/src/handlers/issue/pre.ts +++ b/src/handlers/issue/pre.ts @@ -1,15 +1,14 @@ import { extractImportantWords, upsertCommentToIssue, measureSimilarity } from "../../helpers"; -import { getBotContext, getLogger } from "../../bindings"; -import { Issue, Payload } from "../../types"; +import { getLogger } from "../../bindings"; +import { BotContext, Issue, Payload } from "../../types"; -export const findDuplicateOne = async () => { +export const findDuplicateOne = async (context: BotContext) => { const logger = getLogger(); - const context = getBotContext(); const payload = context.payload as Payload; const issue = payload.issue; if (!issue?.body) return; - const importantWords = await extractImportantWords(issue); + const importantWords = await extractImportantWords(context, issue); const perPage = 10; let curPage = 1; @@ -28,9 +27,10 @@ export const findDuplicateOne = async () => { for (const result of response.data.items) { if (!result.body) continue; if (result.id === issue.id) continue; - const similarity = await measureSimilarity(issue, result as Issue); + const similarity = await measureSimilarity(context, issue, result as Issue); if (similarity > parseInt(process.env.SIMILARITY_THRESHOLD || "80")) { await upsertCommentToIssue( + context, issue.number, `Similar issue (${result.title}) found at ${result.html_url}.\nSimilarity is about ${similarity}%`, "created" @@ -43,7 +43,7 @@ export const findDuplicateOne = async () => { else curPage++; } } catch (e: unknown) { - logger.error(`Could not find any issues, reason: ${e}`); + logger.error(context, `Could not find any issues, reason: ${e}`); } } }; diff --git a/src/handlers/label/index.ts b/src/handlers/label/index.ts index 491d49f74..cfd321113 100644 --- a/src/handlers/label/index.ts +++ b/src/handlers/label/index.ts @@ -1,11 +1,10 @@ import { saveLabelChange } from "../../adapters/supabase"; -import { getBotContext, getLogger } from "../../bindings"; +import { getLogger } from "../../bindings"; import { hasLabelEditPermission } from "../../helpers"; -import { Payload } from "../../types"; +import { BotContext, Payload } from "../../types"; -export const watchLabelChange = async () => { +export const watchLabelChange = async (context: BotContext) => { const logger = getLogger(); - const context = getBotContext(); const payload = context.payload as Payload; @@ -23,7 +22,7 @@ export const watchLabelChange = async () => { } // check if user is authorized to make the change - const hasAccess = await hasLabelEditPermission(currentLabel, triggerUser, repository.full_name); + const hasAccess = await hasLabelEditPermission(context, currentLabel, triggerUser, repository.full_name); await saveLabelChange(triggerUser, full_name, previousLabel, currentLabel, hasAccess); logger.debug("watchLabelChange: label name change saved to db"); diff --git a/src/handlers/payout/action.ts b/src/handlers/payout/action.ts index 25bf01562..16a5cee04 100644 --- a/src/handlers/payout/action.ts +++ b/src/handlers/payout/action.ts @@ -1,6 +1,7 @@ import { BigNumber, ethers } from "ethers"; import { getLabelChanges, getPenalty, getWalletAddress, getWalletMultiplier, removePenalty } from "../../adapters/supabase"; -import { getBotConfig, getBotContext, getLogger } from "../../bindings"; +import { getLogger } from "../../bindings"; + import { addLabelToIssue, checkUserPermissionForRepoAndOrg, @@ -15,7 +16,8 @@ import { createDetailsTable, savePermitToDB, } from "../../helpers"; -import { UserType, Payload, StateReason, Comment, User, Incentives, Issue } from "../../types"; + +import { UserType, Payload, StateReason, Comment, User, Incentives, Issue, BotContext } from "../../types"; import { bountyInfo } from "../wildcard"; import Decimal from "decimal.js"; import { GLOBAL_STRINGS } from "../../configs"; @@ -63,14 +65,13 @@ export interface RewardByUser { * Collect the information required for the permit generation and error handling */ -export const incentivesCalculation = async (): Promise => { - const context = getBotContext(); +export const incentivesCalculation = async (context: BotContext): Promise => { const { payout: { paymentToken, rpc, permitBaseUrl, networkId, privateKey }, mode: { incentiveMode, paymentPermitMaxPrice }, price: { incentives, issueCreatorMultiplier, baseMultiplier }, accessControl, - } = getBotConfig(); + } = context.botConfig; const logger = getLogger(); const payload = context.payload as Payload; const issue = payload.issue; @@ -83,16 +84,16 @@ export const incentivesCalculation = async (): Promise e.user.type === UserType.Bot && e.body.match(claimUrlRegex)); @@ -100,13 +101,13 @@ export const incentivesCalculation = async (): Promise => { const logger = getLogger(); - const { comments } = getBotConfig(); + const { comments } = context.botConfig; const issueNumber = incentivesCalculation.issue.number; let permitComment = ""; @@ -398,6 +400,7 @@ export const handleIssueClosed = async ( // CREATOR REWARD HANDLER // Generate permit for user if its not the same id as assignee + if (creatorReward && creatorReward.reward && creatorReward.reward[0].account !== "0x") { rewardByUser.push({ account: creatorReward.reward[0].account, @@ -499,23 +502,23 @@ export const handleIssueClosed = async ( reward.priceInEth = price.mul(multiplier); } - const { payoutUrl, txData } = await generatePermit2Signature(reward.account, reward.priceInEth, reward.issueId, reward.userId?.toString()); + const { payoutUrl, txData } = await generatePermit2Signature(context, reward.account, reward.priceInEth, reward.issueId, reward.userId?.toString()); const price = `${reward.priceInEth} ${incentivesCalculation.tokenSymbol.toUpperCase()}`; const comment = createDetailsTable(price, payoutUrl, reward.user, detailsValue, reward.debug); - await savePermitToDB(Number(reward.userId), txData); + await savePermitToDB(context, Number(reward.userId), txData); permitComment += comment; logger.info(`Skipping to generate a permit url for missing accounts. fallback: ${JSON.stringify(conversationRewards.fallbackReward)}`); logger.info(`Skipping to generate a permit url for missing accounts. fallback: ${JSON.stringify(pullRequestReviewersReward.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 addLabelToIssue("Permitted"); + await deleteLabel(context, incentivesCalculation.issueDetailed.priceLabel); + await addLabelToIssue(context, "Permitted"); return { error: "" }; }; diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index e1ef60e76..25a3648a9 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -1,8 +1,8 @@ import { getWalletAddress } from "../../adapters/supabase"; -import { getBotContext, getLogger } from "../../bindings"; +import { getLogger } from "../../bindings"; import { getAllIssueComments, getAllPullRequestReviews, getIssueDescription, parseComments } from "../../helpers"; import { getLatestPullRequest, gitLinkedPrParser } from "../../helpers/parser"; -import { Incentives, MarkdownItem, Payload, UserType } from "../../types"; +import { BotContext, Incentives, MarkdownItem, Payload, UserType } from "../../types"; import { RewardsResponse, commentParser } from "../comment"; import Decimal from "decimal.js"; import { bountyInfo } from "../wildcard"; @@ -24,11 +24,9 @@ const ItemsToExclude: string[] = [MarkdownItem.BlockQuote]; * Incentivize the contributors based on their contribution. * The default formula has been defined in https://github.com/ubiquity/ubiquibot/issues/272 */ -export const calculateIssueConversationReward = async (calculateIncentives: IncentivesCalculationResult): Promise => { +export const calculateIssueConversationReward = async (context: BotContext, calculateIncentives: IncentivesCalculationResult): Promise => { const title = `Issue-Comments`; const logger = getLogger(); - - const context = getBotContext(); const payload = context.payload as Payload; const issue = payload.issue; @@ -39,7 +37,7 @@ export const calculateIssueConversationReward = async (calculateIncentives: Ince return { error: "incentivizeComments: skipping payment permit generation because `assignee` is `undefined`." }; } - const issueComments = await getAllIssueComments(calculateIncentives.issue.number, "full"); + const issueComments = await getAllIssueComments(context, calculateIncentives.issue.number, "full"); logger.info(`Getting the issue comments done. comments: ${JSON.stringify(issueComments)}`); const issueCommentsByUser: Record = {}; for (const issueComment of issueComments) { @@ -101,11 +99,11 @@ export const calculateIssueConversationReward = async (calculateIncentives: Ince return { error: "", title, reward, fallbackReward }; }; -export const calculateIssueCreatorReward = async (incentivesCalculation: IncentivesCalculationResult): Promise => { +export const calculateIssueCreatorReward = async (context: BotContext, incentivesCalculation: IncentivesCalculationResult): Promise => { const title = `Issue-Creation`; const logger = getLogger(); - const issueDetailed = bountyInfo(incentivesCalculation.issue); + const issueDetailed = bountyInfo(context, incentivesCalculation.issue); if (!issueDetailed.isBounty) { logger.info(`incentivizeCreatorComment: its not a bounty`); return { error: `incentivizeCreatorComment: its not a bounty` }; @@ -118,7 +116,7 @@ export const calculateIssueCreatorReward = async (incentivesCalculation: Incenti return { error: "incentivizeCreatorComment: 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) { logger.info(`Skipping to generate a permit url because issue description is empty. description: ${description}`); return { error: `Skipping to generate a permit url because issue description is empty. description: ${description}` }; @@ -160,18 +158,17 @@ export const calculateIssueCreatorReward = async (incentivesCalculation: Incenti }; }; -export const calculatePullRequestReviewsReward = async (incentivesCalculation: IncentivesCalculationResult): Promise => { +export const calculatePullRequestReviewsReward = async (context: BotContext, incentivesCalculation: IncentivesCalculationResult): Promise => { const logger = getLogger(); - const context = getBotContext(); const title = "Review-Reviewer"; - const linkedPullRequest = await gitLinkedPrParser({ + const linkedPullRequest = await gitLinkedPrParser(context, { owner: incentivesCalculation.payload.repository.owner.login, repo: incentivesCalculation.payload.repository.name, issue_number: incentivesCalculation.issue.number, }); - const latestLinkedPullRequest = await getLatestPullRequest(linkedPullRequest); + const latestLinkedPullRequest = await getLatestPullRequest(context, linkedPullRequest); if (!latestLinkedPullRequest) { logger.debug(`calculatePullRequestReviewsReward: No linked pull requests found`); @@ -186,7 +183,7 @@ export const calculatePullRequestReviewsReward = async (incentivesCalculation: I } 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 PR reviews done. comments: ${JSON.stringify(prReviews)}`); const prReviewsByUser: Record = {}; for (const review of prReviews) { diff --git a/src/handlers/pricing/action.ts b/src/handlers/pricing/action.ts index 7e9673c6f..98f29be3d 100644 --- a/src/handlers/pricing/action.ts +++ b/src/handlers/pricing/action.ts @@ -1,13 +1,12 @@ -import { getBotConfig, getBotContext, getLogger } from "../../bindings"; +import { getLogger } from "../../bindings"; import { GLOBAL_STRINGS } from "../../configs"; import { addCommentToIssue, addLabelToIssue, clearAllPriceLabelsOnIssue, createLabel, getLabel, calculateWeight, getAllLabeledEvents } from "../../helpers"; -import { Payload, UserType } from "../../types"; +import { BotContext, Payload, UserType } from "../../types"; import { handleLabelsAccess } from "../access"; import { getTargetPriceLabel } from "../shared"; -export const pricingLabelLogic = async (): Promise => { - const context = getBotContext(); - const config = getBotConfig(); +export const pricingLabelLogic = async (context: BotContext): Promise => { + const config = context.botConfig; const logger = getLogger(); const payload = context.payload as Payload; if (!payload.issue) return; @@ -15,15 +14,15 @@ export const pricingLabelLogic = async (): Promise => { const labelNames = labels.map((i) => i.name); logger.info(`Checking if the issue is a parent issue.`); if (payload.issue.body && isParentIssue(payload.issue.body)) { - logger.error("Identified as parent issue. Disabling price label."); + logger.error(context, "Identified as parent issue. Disabling price label."); const issuePrices = labels.filter((label) => label.name.toString().startsWith("Price:")); if (issuePrices.length) { - await addCommentToIssue(GLOBAL_STRINGS.skipPriceLabelGenerationComment, payload.issue.number); - await clearAllPriceLabelsOnIssue(); + await addCommentToIssue(context, GLOBAL_STRINGS.skipPriceLabelGenerationComment, payload.issue.number); + await clearAllPriceLabelsOnIssue(context); } return; } - const valid = await handleLabelsAccess(); + const valid = await handleLabelsAccess(context); if (!valid && config.accessControl.label) { return; @@ -36,14 +35,14 @@ export const pricingLabelLogic = async (): Promise => { const minTimeLabel = timeLabels.length > 0 ? timeLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : undefined; const minPriorityLabel = priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : undefined; - const targetPriceLabel = getTargetPriceLabel(minTimeLabel, minPriorityLabel); + const targetPriceLabel = getTargetPriceLabel(context, minTimeLabel, minPriorityLabel); if (targetPriceLabel) { const _targetPriceLabel = labelNames.find((name) => name.includes("Price") && name.includes(targetPriceLabel)); if (_targetPriceLabel) { // get all issue events of type "labeled" and the event label includes Price - let labeledEvents = await getAllLabeledEvents(); + let labeledEvents = await getAllLabeledEvents(context); if (!labeledEvents) return; labeledEvents = labeledEvents.filter((event) => event.label?.name.includes("Price")); @@ -55,31 +54,31 @@ export const pricingLabelLogic = async (): Promise => { } else { // add price label to issue becuase wrong price has been added by bot logger.info(`Adding price label to issue`); - await clearAllPriceLabelsOnIssue(); + await clearAllPriceLabelsOnIssue(context); - const exist = await getLabel(targetPriceLabel); + const exist = await getLabel(context, targetPriceLabel); if (assistivePricing && !exist) { logger.info(`${targetPriceLabel} doesn't exist on the repo, creating...`); - await createLabel(targetPriceLabel, "price"); + await createLabel(context, targetPriceLabel, "price"); } - await addLabelToIssue(targetPriceLabel); + await addLabelToIssue(context, targetPriceLabel); } } else { // add price if there is none logger.info(`Adding price label to issue`); - await clearAllPriceLabelsOnIssue(); + await clearAllPriceLabelsOnIssue(context); - const exist = await getLabel(targetPriceLabel); + const exist = await getLabel(context, targetPriceLabel); if (assistivePricing && !exist) { logger.info(`${targetPriceLabel} doesn't exist on the repo, creating...`); - await createLabel(targetPriceLabel, "price"); + await createLabel(context, targetPriceLabel, "price"); } - await addLabelToIssue(targetPriceLabel); + await addLabelToIssue(context, targetPriceLabel); } } else { - await clearAllPriceLabelsOnIssue(); + await clearAllPriceLabelsOnIssue(context); logger.info(`Skipping action...`); } }; diff --git a/src/handlers/pricing/pre.ts b/src/handlers/pricing/pre.ts index 4d048d1ca..a0ff03186 100644 --- a/src/handlers/pricing/pre.ts +++ b/src/handlers/pricing/pre.ts @@ -1,13 +1,14 @@ -import { getBotConfig, getLogger } from "../../bindings"; +import { getLogger } from "../../bindings"; import { calculateWeight, createLabel, listLabelsForRepo } from "../../helpers"; +import { BotContext } from "../../types"; import { calculateBountyPrice } from "../shared"; /** * @dev 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 const validatePriceLabels = async (): Promise => { - const config = getBotConfig(); +export const validatePriceLabels = async (context: BotContext): Promise => { + const config = context.botConfig; const logger = getLogger(); const { assistivePricing } = config.mode; @@ -22,7 +23,7 @@ export const validatePriceLabels = async (): Promise => { const aiLabels: string[] = []; for (const timeLabel of config.price.timeLabels) { for (const priorityLabel of config.price.priorityLabels) { - const targetPrice = calculateBountyPrice(calculateWeight(timeLabel), calculateWeight(priorityLabel), config.price.baseMultiplier); + const targetPrice = calculateBountyPrice(context, calculateWeight(timeLabel), calculateWeight(priorityLabel), config.price.baseMultiplier); const targetPriceLabel = `Price: ${targetPrice} USD`; aiLabels.push(targetPriceLabel); } @@ -32,7 +33,7 @@ export const validatePriceLabels = async (): Promise => { logger.debug(`Got needed labels for setting up price, neededLabels: ${neededLabels.toString()}`); // 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)); @@ -40,7 +41,7 @@ export const validatePriceLabels = async (): Promise => { // Create missing labels if (missingLabels.length > 0) { logger.info(`Creating missing labels: ${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/pull-request/create-devpool-pr.ts b/src/handlers/pull-request/create-devpool-pr.ts index 08046bfbc..e457af14e 100644 --- a/src/handlers/pull-request/create-devpool-pr.ts +++ b/src/handlers/pull-request/create-devpool-pr.ts @@ -1,10 +1,8 @@ -import { getBotContext, getLogger } from "../../bindings"; -import { GithubContent, Payload } from "../../types"; +import { getLogger } from "../../bindings"; +import { BotContext, GithubContent, Payload } from "../../types"; -export const createDevPoolPR = async () => { +export const createDevPoolPR = async (context: BotContext) => { const logger = getLogger(); - - const context = getBotContext(); const payload = context.payload as Payload; const devPoolOwner = "ubiquity"; diff --git a/src/handlers/push/index.ts b/src/handlers/push/index.ts index 340143439..a244088a6 100644 --- a/src/handlers/push/index.ts +++ b/src/handlers/push/index.ts @@ -1,6 +1,6 @@ -import { getBotContext, getLogger } from "../../bindings"; +import { getLogger } from "../../bindings"; import { createCommitComment, getFileContent } from "../../helpers"; -import { CommitsPayload, PushPayload, WideConfigSchema } from "../../types"; +import { BotContext, CommitsPayload, PushPayload, WideConfigSchema } from "../../types"; import { parseYAML } from "../../utils/private"; import { updateBaseRate } from "./update-base"; import { validate } from "../../utils/ajv"; @@ -22,10 +22,8 @@ function getCommitChanges(commits: CommitsPayload[]) { return changes; } -export const runOnPush = async () => { +export const runOnPush = async (context: BotContext) => { const logger = getLogger(); - - const context = getBotContext(); const payload = context.payload as PushPayload; // if zero sha, push is a pr change @@ -49,10 +47,8 @@ export const runOnPush = async () => { } }; -export const validateConfigChange = async () => { +export const validateConfigChange = async (context: BotContext) => { const logger = getLogger(); - - const context = getBotContext(); const payload = context.payload as PushPayload; if (!payload.ref.startsWith("refs/heads/")) { @@ -77,6 +73,7 @@ export const validateConfigChange = async () => { } const configFileContent = await getFileContent( + context, payload.repository.owner.login, payload.repository.name, payload.ref.split("refs/heads/")[1], @@ -89,7 +86,7 @@ export const validateConfigChange = async () => { const config = parseYAML(decodedConfig); const { valid, error } = validate(WideConfigSchema, config); if (!valid) { - await createCommitComment(`@${payload.sender.login} Config validation failed! ${error}`, commitSha, BASE_RATE_FILE); + await createCommitComment(context, `@${payload.sender.login} Config validation failed! ${error}`, commitSha, BASE_RATE_FILE); } } } diff --git a/src/handlers/push/update-base.ts b/src/handlers/push/update-base.ts index da67c8e6c..354f9acf6 100644 --- a/src/handlers/push/update-base.ts +++ b/src/handlers/push/update-base.ts @@ -1,10 +1,9 @@ -import { Context } from "probot"; import { getLogger } from "../../bindings"; import { getPreviousFileContent, listLabelsForRepo, updateLabelsFromBaseRate } from "../../helpers"; -import { Label, PushPayload } from "../../types"; +import { BotContext, Label, PushPayload } from "../../types"; import { parseYAML } from "../../utils/private"; -export const updateBaseRate = async (context: Context, payload: PushPayload, filePath: string) => { +export const updateBaseRate = async (context: BotContext, payload: PushPayload, filePath: string) => { const logger = getLogger(); // Get default branch from ref const branch = payload.ref?.split("refs/heads/")[1]; @@ -12,7 +11,7 @@ export const updateBaseRate = async (context: Context, payload: PushPayload, fil 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"); @@ -29,12 +28,12 @@ export const updateBaseRate = async (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) { logger.debug("No labels on this repo"); return; } - await updateLabelsFromBaseRate(owner, repo, context, repoLabels as Label[], previousBaseRate); + await updateLabelsFromBaseRate(context, owner, repo, repoLabels as Label[], previousBaseRate); }; diff --git a/src/handlers/shared/pricing.ts b/src/handlers/shared/pricing.ts index 0a543bc7c..9671cc08d 100644 --- a/src/handlers/shared/pricing.ts +++ b/src/handlers/shared/pricing.ts @@ -1,22 +1,22 @@ -import { getBotConfig } from "../../bindings"; import { calculateWeight } from "../../helpers"; +import { BotContext } from "../../types"; -export const calculateBountyPrice = (timeValue: number, priorityValue: number, baseValue?: number): number => { - const botConfig = getBotConfig(); +export const calculateBountyPrice = (context: BotContext, timeValue: number, priorityValue: number, baseValue?: number): number => { + const botConfig = context.botConfig; const base = baseValue ?? botConfig.price.baseMultiplier; const priority = priorityValue / 10; // floats cause bad math const price = 1000 * base * timeValue * priority; return price; }; -export const getTargetPriceLabel = (timeLabel: string | undefined, priorityLabel: string | undefined): string | undefined => { - const botConfig = getBotConfig(); +export const getTargetPriceLabel = (context: BotContext, timeLabel: string | undefined, priorityLabel: string | undefined): string | undefined => { + const botConfig = context.botConfig; let targetPriceLabel: string | undefined = undefined; if (timeLabel && priorityLabel) { const timeWeight = calculateWeight(botConfig.price.timeLabels.find((item) => item.name === timeLabel)); const priorityWeight = calculateWeight(botConfig.price.priorityLabels.find((item) => item.name === priorityLabel)); if (timeWeight && priorityWeight) { - const bountyPrice = calculateBountyPrice(timeWeight, priorityWeight); + const bountyPrice = calculateBountyPrice(context, timeWeight, priorityWeight); targetPriceLabel = `Price: ${bountyPrice} USD`; } } diff --git a/src/handlers/wildcard/analytics.ts b/src/handlers/wildcard/analytics.ts index df877f070..7abe6c276 100644 --- a/src/handlers/wildcard/analytics.ts +++ b/src/handlers/wildcard/analytics.ts @@ -1,7 +1,7 @@ import { getMaxIssueNumber, upsertIssue, upsertUser } from "../../adapters/supabase"; -import { getBotConfig, getLogger } from "../../bindings"; +import { getLogger } from "../../bindings"; import { listIssuesForRepo, getUser, calculateWeight } from "../../helpers"; -import { Issue, IssueType, User, UserProfile } from "../../types"; +import { BotContext, Issue, IssueType, User, UserProfile } from "../../types"; /** * Checks the issue whether it's a bounty for hunters or an issue for not @@ -9,6 +9,7 @@ import { Issue, IssueType, User, UserProfile } from "../../types"; * @returns If bounty - true, If issue - false */ export const bountyInfo = ( + context: BotContext, issue: Issue ): { isBounty: boolean; @@ -16,7 +17,7 @@ export const bountyInfo = ( priorityLabel: string | undefined; priceLabel: string | undefined; } => { - const config = getBotConfig(); + const config = context.botConfig; const labels = issue.labels; const timeLabels = config.price.timeLabels.filter((item) => labels.map((i) => i.name).includes(item.name)); @@ -40,11 +41,11 @@ export const bountyInfo = ( /** * Collects all the analytics information by scanning the issues opened | closed */ -export const collectAnalytics = async (): Promise => { +export const collectAnalytics = async (context: BotContext): Promise => { const logger = getLogger(); const { mode: { disableAnalytics }, - } = getBotConfig(); + } = context.botConfig; if (disableAnalytics) { logger.info(`Skipping to collect analytics, reason: mode=${disableAnalytics}`); return; @@ -56,12 +57,12 @@ export const collectAnalytics = async (): Promise => { const perPage = 30; let curPage = 1; while (!fetchDone) { - const issues = await listIssuesForRepo(IssueType.ALL, perPage, curPage); + const issues = await listIssuesForRepo(context, IssueType.ALL, perPage, curPage); // need to skip checking the closed issues already stored in the db and filter them by doing a sanitation checks. // sanitation checks would be basically checking labels of the issue // whether the issue has both `priority` label and `timeline` label - const bounties = issues.filter((issue) => bountyInfo(issue as Issue).isBounty); + const bounties = issues.filter((issue) => bountyInfo(context, issue as Issue).isBounty); // collect assignees from both type of issues (opened/closed) const assignees = bounties.filter((bounty) => bounty.assignee).map((bounty) => bounty.assignee as User); @@ -71,7 +72,7 @@ export const collectAnalytics = async (): Promise => { const assigneesToUpsert = assignees.filter((assignee, pos) => tmp.indexOf(assignee.login) == pos); const userProfilesToUpsert = await Promise.all( assigneesToUpsert.map(async (assignee) => { - const res = await getUser(assignee.login); + const res = await getUser(context, assignee.login); return res as UserProfile; }) ); @@ -83,16 +84,16 @@ export const collectAnalytics = async (): Promise => { .toString()}` ); - await Promise.all(userProfilesToUpsert.map((i) => upsertUser(i))); + await Promise.all(userProfilesToUpsert.map((i) => upsertUser(context, i))); // No need to update the record for the bounties already closed const bountiesToUpsert = bounties.filter((bounty) => (bounty.state === IssueType.CLOSED ? bounty.number > maximumIssueNumber : true)); logger.info(`Upserting bounties: ${bountiesToUpsert.map((i) => i.title).toString()}`); await Promise.all( bountiesToUpsert.map((i) => { - const additions = bountyInfo(i as Issue); + const additions = bountyInfo(context, i as Issue); if (additions.timelabel && additions.priorityLabel && additions.priceLabel) - return upsertIssue(i as Issue, { + return upsertIssue(context, i as Issue, { labels: { timeline: additions.timelabel, priority: additions.priorityLabel, diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index ec057ce73..61dd177cd 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -1,4 +1,4 @@ -import { getBotConfig, getBotContext, getLogger } from "../../bindings"; +import { getLogger } from "../../bindings"; import { GLOBAL_STRINGS } from "../../configs/strings"; import { addCommentToIssue, @@ -9,39 +9,38 @@ import { listAllIssuesForRepo, removeAssignees, } from "../../helpers"; -import { Comment, Issue, IssueType, Payload, UserType } from "../../types"; +import { BotContext, Comment, Issue, IssueType, Payload, UserType } from "../../types"; import { deadLinePrefix } from "../shared"; /** * @dev Check out the bounties which haven't been completed within the initial timeline * and try to release the bounty back to dev pool */ -export const checkBountiesToUnassign = async () => { +export const checkBountiesToUnassign = async (context: BotContext) => { const logger = getLogger(); logger.info(`Getting all the issues...`); // 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 bounties in parallel - const res = await Promise.all(assigned_issues.map(async (issue) => checkBountyToUnassign(issue as Issue))); + const res = await Promise.all(assigned_issues.map(async (issue) => checkBountyToUnassign(context, issue as Issue))); logger.info(`Checking expired bounties done! total: ${res.length}, unassigned: ${res.filter((i) => i).length}`); }; -const checkBountyToUnassign = async (issue: Issue): Promise => { - const context = getBotContext(); +const checkBountyToUnassign = async (context: BotContext, issue: Issue): Promise => { const payload = context.payload as Payload; const logger = getLogger(); const { unassign: { followUpTime, disqualifyTime }, - } = getBotConfig(); + } = context.botConfig; logger.info(`Checking the bounty to unassign, issue_number: ${issue.number}`); const { unassignComment, askUpdate } = GLOBAL_STRINGS; 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 @@ -50,9 +49,9 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { const lastAskTime = askUpdateComments.length > 0 ? 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(context, pullRequest[0].number, payload.repository.owner.login, payload.repository.name); @@ -67,8 +66,8 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { `Unassigning... lastActivityTime: ${lastActivity.getTime()}, curTime: ${curTimestamp}, passedDuration: ${passedDuration}, followUpTime: ${followUpTime}, disqualifyTime: ${disqualifyTime}` ); // remove assignees from the issue - await removeAssignees(issue.number, assignees); - await addCommentToIssue(`@${assignees[0]} - ${unassignComment} \nLast activity time: ${lastActivity}`, issue.number); + await removeAssignees(context, issue.number, assignees); + await addCommentToIssue(context, `@${assignees[0]} - ${unassignComment} \nLast activity time: ${lastActivity}`, issue.number); return true; } else if (passedDuration >= followUpTime) { @@ -82,6 +81,7 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { ); } else { await addCommentToIssue( + context, `${askUpdate} @${assignees[0]}? If you would like to release the bounty back to the DevPool, please comment \`/stop\` \nLast activity time: ${lastActivity}`, issue.number ); @@ -92,7 +92,7 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { return false; }; -const lastActivityTime = async (issue: Issue, comments: Comment[]): Promise => { +const lastActivityTime = async (context: BotContext, issue: Issue, comments: Comment[]): Promise => { const logger = getLogger(); logger.info(`Checking the latest activity for the issue, issue_number: ${issue.number}`); const assignees = issue.assignees.map((i) => i.login); @@ -110,14 +110,14 @@ const lastActivityTime = async (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/handlers/wildcard/weekly.ts b/src/handlers/wildcard/weekly.ts index 8d9dbdc70..cb02577fa 100644 --- a/src/handlers/wildcard/weekly.ts +++ b/src/handlers/wildcard/weekly.ts @@ -1,14 +1,14 @@ import { run } from "./weekly/action"; import { getLastWeeklyTime, updateLastWeeklyTime } from "../../adapters/supabase"; -import { getBotConfig, getBotContext } from "../../bindings"; +import { BotContext } from "../../types"; const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds -export const checkWeeklyUpdate = async () => { - const { log } = getBotContext(); +export const checkWeeklyUpdate = async (context: BotContext) => { + const { log } = context; const { mode: { disableAnalytics }, - } = getBotConfig(); + } = context.botConfig; if (disableAnalytics) { log.info(`Skipping to collect the weekly analytics, reason: mode=${disableAnalytics}`); return; @@ -16,8 +16,8 @@ export const checkWeeklyUpdate = async () => { const curTime = new Date(); const lastTime = await getLastWeeklyTime(); if (lastTime == undefined || new Date(lastTime.getTime() + SEVEN_DAYS) < curTime) { - await run(); - await updateLastWeeklyTime(curTime); + await run(context); + await updateLastWeeklyTime(context, curTime); } else { log.info(`Skipping to collect the weekly analytics because 7 days have not passed`); } diff --git a/src/handlers/wildcard/weekly/action.ts b/src/handlers/wildcard/weekly/action.ts index 09e06e878..cb4ba0f6f 100644 --- a/src/handlers/wildcard/weekly/action.ts +++ b/src/handlers/wildcard/weekly/action.ts @@ -6,10 +6,9 @@ import path from "path"; import axios from "axios"; import Jimp from "jimp"; import nodeHtmlToImage from "node-html-to-image"; -import { getBotConfig, getBotContext } from "../../../bindings"; import { telegramPhotoNotifier } from "../../../adapters"; import { Context } from "probot"; -import { Payload } from "../../../types"; +import { BotContext, Payload } from "../../../types"; import { fetchImage } from "../../../utils/web-assets"; import { weeklyConfig } from "../../../configs/weekly"; import { ProximaNovaRegularBase64 } from "../../../assets/fonts/proxima-nova-regular-b64"; @@ -340,8 +339,7 @@ const processTelegram = async (caption: string) => { }); }; -export const run = async () => { - const context = getBotContext(); +export const run = async (context: BotContext) => { const payload = context.payload as Payload; const repository = payload.repository.full_name; const eventsList = await fetchEvents(context); @@ -350,7 +348,7 @@ export const run = async () => { await htmlImage(summaryInfo); await compositeImage(); - const { telegram } = getBotConfig(); + const { telegram } = context.botConfig; if (telegram.token) { await processTelegram(dataPadded); } else { diff --git a/src/helpers/commit.ts b/src/helpers/commit.ts index 678b4d774..ab13e42f1 100644 --- a/src/helpers/commit.ts +++ b/src/helpers/commit.ts @@ -1,8 +1,6 @@ -import { getBotContext } from "../bindings"; -import { Payload } from "../types"; +import { BotContext, Payload } from "../types"; -export async function createCommitComment(body: string, commitSha: string, path?: string, owner?: string, repo?: string) { - const context = getBotContext(); +export async function createCommitComment(context: BotContext, body: string, commitSha: string, path?: string, owner?: string, repo?: string) { const payload = context.payload as Payload; if (!owner) { owner = payload.repository.owner.login; diff --git a/src/helpers/ens.ts b/src/helpers/ens.ts index a9344c41a..90f1c79f5 100644 --- a/src/helpers/ens.ts +++ b/src/helpers/ens.ts @@ -1,14 +1,13 @@ import { ethers } from "ethers"; -import { getBotConfig } from "../bindings"; - +import { BotContext } from "../types"; /** * Gets the Ethereum address associated with an ENS (Ethereum Name Service) name * @param ensName - The ENS name, i.e. alice12345.crypto */ -export const resolveAddress = async (ensName: string): Promise => { +export const resolveAddress = async (context: BotContext, ensName: string): Promise => { const { payout: { rpc }, - } = getBotConfig(); + } = context.botConfig; const provider = new ethers.providers.JsonRpcProvider(rpc); const address = await provider.resolveName(ensName); return address; diff --git a/src/helpers/file.ts b/src/helpers/file.ts index b21905098..aa4016632 100644 --- a/src/helpers/file.ts +++ b/src/helpers/file.ts @@ -1,9 +1,9 @@ -import { getBotContext, getLogger } from "../bindings"; +import { getLogger } from "../bindings"; +import { BotContext } from "../types"; // Get the previous file content -export async function getPreviousFileContent(owner: string, repo: string, branch: string, filePath: string) { +export async function getPreviousFileContent(context: BotContext, owner: string, repo: string, branch: string, filePath: string) { const logger = getLogger(); - const context = getBotContext(); try { // Get the latest commit of the branch @@ -60,9 +60,15 @@ export async function getPreviousFileContent(owner: string, repo: string, branch } } -export async function getFileContent(owner: string, repo: string, branch: string, filePath: string, commitSha?: string): Promise { +export async function getFileContent( + context: BotContext, + owner: string, + repo: string, + branch: string, + filePath: string, + commitSha?: string +): Promise { const logger = getLogger(); - const context = getBotContext(); try { if (!commitSha) { diff --git a/src/helpers/gpt.ts b/src/helpers/gpt.ts index 046503d79..2ee9cd3aa 100644 --- a/src/helpers/gpt.ts +++ b/src/helpers/gpt.ts @@ -1,5 +1,5 @@ -import { getBotConfig, getBotContext, getLogger } from "../bindings"; -import { Payload, StreamlinedComment, UserType } from "../types"; +import { getLogger } from "../bindings"; +import { BotContext, Payload, StreamlinedComment, UserType } from "../types"; import { getAllIssueComments, getAllLinkedIssuesAndPullsInBody } from "../helpers"; import OpenAI from "openai"; import { CreateChatCompletionRequestMessage } from "openai/resources/chat"; @@ -62,12 +62,12 @@ Example:[ * @param linkedIssueStreamlined an array of comments in the form of { login: string, body: string } */ export const decideContextGPT = async ( + context: BotContext, chatHistory: CreateChatCompletionRequestMessage[], streamlined: StreamlinedComment[], linkedPRStreamlined: StreamlinedComment[], linkedIssueStreamlined: StreamlinedComment[] ) => { - const context = getBotContext(); const logger = getLogger(); const payload = context.payload as Payload; @@ -78,9 +78,9 @@ export const decideContextGPT = async ( } // 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`); @@ -104,7 +104,7 @@ export const decideContextGPT = async ( }); // 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}`); @@ -133,7 +133,7 @@ export const decideContextGPT = async ( ); // 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; }; @@ -143,9 +143,9 @@ export const decideContextGPT = async ( * @param question the question to ask * @param chatHistory the conversational context to provide to GPT */ -export const askGPT = async (question: string, chatHistory: CreateChatCompletionRequestMessage[]) => { +export const askGPT = async (context: BotContext, question: string, chatHistory: CreateChatCompletionRequestMessage[]) => { const logger = getLogger(); - const config = getBotConfig(); + const config = context.botConfig; if (!config.ask.apiKey) { logger.info(`No OpenAI API Key provided`); diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index 8cd78b047..44544b2fb 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -1,10 +1,9 @@ import { Context } from "probot"; -import { getBotConfig, getBotContext, getLogger } from "../bindings"; -import { AssignEvent, Comment, IssueType, Payload, StreamlinedComment, UserType } from "../types"; +import { getLogger } from "../bindings"; +import { AssignEvent, BotContext, Comment, IssueType, Payload, StreamlinedComment, UserType } from "../types"; import { checkRateLimitGit } from "../utils"; -export const getAllIssueEvents = async () => { - const context = getBotContext(); +export const getAllIssueEvents = async (context: BotContext) => { const logger = getLogger(); const payload = context.payload as Payload; if (!payload.issue) return; @@ -35,20 +34,19 @@ export const getAllIssueEvents = async () => { } } catch (e: unknown) { shouldFetch = false; - logger.error(`Getting all issue events failed, reason: ${e}`); + logger.error(context, `Getting all issue events failed, reason: ${e}`); return null; } return events; }; -export const getAllLabeledEvents = async () => { - const events = await getAllIssueEvents(); +export const getAllLabeledEvents = async (context: BotContext) => { + const events = await getAllIssueEvents(context); if (!events) return null; return events.filter((event) => event.event === "labeled"); }; -export const clearAllPriceLabelsOnIssue = async (): Promise => { - const context = getBotContext(); +export const clearAllPriceLabelsOnIssue = async (context: BotContext): Promise => { const logger = getLogger(); const payload = context.payload as Payload; @@ -71,8 +69,7 @@ export const clearAllPriceLabelsOnIssue = async (): Promise => { } }; -export const addLabelToIssue = async (labelName: string) => { - const context = getBotContext(); +export const addLabelToIssue = async (context: BotContext, labelName: string) => { const logger = getLogger(); const payload = context.payload as Payload; if (!payload.issue) { @@ -93,13 +90,13 @@ export const addLabelToIssue = async (labelName: string) => { }; export const listIssuesForRepo = async ( + context: BotContext, state: "open" | "closed" | "all" = "open", per_page = 30, page = 1, sort: "created" | "updated" | "comments" = "created", direction: "desc" | "asc" = "desc" ) => { - const context = getBotContext(); const payload = context.payload as Payload; const response = await context.octokit.issues.listForRepo({ @@ -121,13 +118,13 @@ export const listIssuesForRepo = async ( } }; -export const listAllIssuesForRepo = async (state: "open" | "closed" | "all" = "open") => { +export const listAllIssuesForRepo = async (context: BotContext, state: "open" | "closed" | "all" = "open") => { const issuesArr = []; let fetchDone = false; const perPage = 100; let curPage = 1; while (!fetchDone) { - const issues = await listIssuesForRepo(state, perPage, curPage); + const issues = await listIssuesForRepo(context, state, perPage, curPage); // push the objects to array issuesArr.push(...issues); @@ -139,8 +136,7 @@ export const listAllIssuesForRepo = async (state: "open" | "closed" | "all" = "o return issuesArr; }; -export const addCommentToIssue = async (msg: string, issue_number: number) => { - const context = getBotContext(); +export const addCommentToIssue = async (context: BotContext, msg: string, issue_number: number) => { const logger = getLogger(); const payload = context.payload as Payload; @@ -156,8 +152,7 @@ export const addCommentToIssue = async (msg: string, issue_number: number) => { } }; -export const updateCommentOfIssue = async (msg: string, issue_number: number, reply_to: Comment) => { - const context = getBotContext(); +export const updateCommentOfIssue = async (context: BotContext, msg: string, issue_number: number, reply_to: Comment) => { const logger = getLogger(); const payload = context.payload as Payload; @@ -191,35 +186,34 @@ export const updateCommentOfIssue = async (msg: string, issue_number: number, re }); } else { logger.info(`Falling back to add comment. Couldn't find response to edit for comment_id: ${reply_to.id}`); - await addCommentToIssue(msg, issue_number); + await addCommentToIssue(context, msg, issue_number); } } catch (e: unknown) { logger.debug(`Updating a comment failed! reason: ${e}`); } }; -export const upsertCommentToIssue = async (issue_number: number, comment: string, action: string, reply_to?: Comment) => { +export const upsertCommentToIssue = async (context: BotContext, issue_number: number, comment: string, action: string, reply_to?: Comment) => { if (action == "edited" && reply_to) { - await updateCommentOfIssue(comment, issue_number, reply_to); + await updateCommentOfIssue(context, comment, issue_number, reply_to); } else { - await addCommentToIssue(comment, issue_number); + await addCommentToIssue(context, comment, issue_number); } }; -export const upsertLastCommentToIssue = async (issue_number: number, commentBody: string) => { +export const upsertLastCommentToIssue = async (context: BotContext, issue_number: number, commentBody: string) => { const logger = getLogger(); 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); + if (comments.length > 0 && comments[comments.length - 1].body !== commentBody) await addCommentToIssue(context, commentBody, issue_number); } catch (e: unknown) { logger.debug(`Upserting last comment failed! reason: ${e}`); } }; -export const getCommentsOfIssue = async (issue_number: number): Promise => { - const context = getBotContext(); +export const getCommentsOfIssue = async (context: BotContext, issue_number: number): Promise => { const logger = getLogger(); const payload = context.payload as Payload; @@ -239,8 +233,7 @@ export const getCommentsOfIssue = async (issue_number: number): Promise => { - const context = getBotContext(); +export const getIssueDescription = async (context: BotContext, issue_number: number, format: "raw" | "html" | "text" = "raw"): Promise => { const logger = getLogger(); const payload = context.payload as Payload; @@ -273,8 +266,7 @@ export const getIssueDescription = async (issue_number: number, format: "raw" | return result; }; -export const getAllIssueComments = async (issue_number: number, format: "raw" | "html" | "text" | "full" = "raw"): Promise => { - const context = getBotContext(); +export const getAllIssueComments = async (context: BotContext, issue_number: number, format: "raw" | "html" | "text" | "full" = "raw"): Promise => { const payload = context.payload as Payload; const result: Comment[] = []; @@ -310,8 +302,7 @@ export const getAllIssueComments = async (issue_number: number, format: "raw" | return result; }; -export const getAllIssueAssignEvents = async (issue_number: number): Promise => { - const context = getBotContext(); +export const getAllIssueAssignEvents = async (context: BotContext, issue_number: number): Promise => { const payload = context.payload as Payload; const result: AssignEvent[] = []; @@ -344,8 +335,7 @@ export const getAllIssueAssignEvents = async (issue_number: number): Promise (new Date(a.created_at) > new Date(b.created_at) ? -1 : 1)); }; -export const wasIssueReopened = async (issue_number: number): Promise => { - const context = getBotContext(); +export const wasIssueReopened = async (context: BotContext, issue_number: number): Promise => { const payload = context.payload as Payload; let shouldFetch = true; @@ -377,8 +367,7 @@ export const wasIssueReopened = async (issue_number: number): Promise = return false; }; -export const removeAssignees = async (issue_number: number, assignees: string[]): Promise => { - const context = getBotContext(); +export const removeAssignees = async (context: BotContext, issue_number: number, assignees: string[]): Promise => { const logger = getLogger(); const payload = context.payload as Payload; @@ -394,15 +383,15 @@ export const removeAssignees = async (issue_number: number, assignees: string[]) } }; -export const checkUserPermissionForRepoAndOrg = async (username: string, context: Context): Promise => { - const permissionForRepo = await checkUserPermissionForRepo(username, context); - const permissionForOrg = await checkUserPermissionForOrg(username, context); - const userPermission = await getUserPermission(username, context); +export const checkUserPermissionForRepoAndOrg = async (context: BotContext, username: string): Promise => { + const permissionForRepo = await checkUserPermissionForRepo(context, username); + const permissionForOrg = await checkUserPermissionForOrg(context, username); + const userPermission = await getUserPermission(context, username); return permissionForOrg || permissionForRepo || userPermission === "admin" || userPermission === "billing_manager"; }; -export const checkUserPermissionForRepo = async (username: string, context: Context): Promise => { +export const checkUserPermissionForRepo = async (context: BotContext, username: string): Promise => { const logger = getLogger(); const payload = context.payload as Payload; @@ -415,12 +404,12 @@ export const checkUserPermissionForRepo = async (username: string, context: Cont return res.status === 204; } catch (e: unknown) { - logger.error(`Checking if user permisson for repo failed! reason: ${e}`); + logger.error(context, `Checking if user permisson for repo failed! reason: ${e}`); return false; } }; -export const checkUserPermissionForOrg = async (username: string, context: Context): Promise => { +export const checkUserPermissionForOrg = async (context: BotContext, username: string): Promise => { const logger = getLogger(); const payload = context.payload as Payload; if (!payload.organization) return false; @@ -433,12 +422,12 @@ export const checkUserPermissionForOrg = async (username: string, context: Conte // skipping status check due to type error of checkMembershipForUser function of octokit return true; } catch (e: unknown) { - logger.error(`Checking if user permisson for org failed! reason: ${e}`); + logger.error(context, `Checking if user permisson for org failed! reason: ${e}`); return false; } }; -export const getUserPermission = async (username: string, context: Context): Promise => { +export const getUserPermission = async (context: BotContext, username: string): Promise => { const logger = getLogger(); const payload = context.payload as Payload; @@ -460,8 +449,7 @@ export const getUserPermission = async (username: string, context: Context): Pro } }; -export const addAssignees = async (issue_number: number, assignees: string[]): Promise => { - const context = getBotContext(); +export const addAssignees = async (context: BotContext, issue_number: number, assignees: string[]): Promise => { const logger = getLogger(); const payload = context.payload as Payload; @@ -477,8 +465,7 @@ export const addAssignees = async (issue_number: number, assignees: string[]): P } }; -export const deleteLabel = async (label: string): Promise => { - const context = getBotContext(); +export const deleteLabel = async (context: BotContext, label: string): Promise => { const logger = getLogger(); const payload = context.payload as Payload; @@ -499,8 +486,7 @@ export const deleteLabel = async (label: string): Promise => { } }; -export const removeLabel = async (name: string) => { - const context = getBotContext(); +export const removeLabel = async (context: BotContext, name: string) => { const logger = getLogger(); const payload = context.payload as Payload; if (!payload.issue) { @@ -555,12 +541,11 @@ export const getPullRequests = async (context: Context, state: "open" | "closed" } }; -export const closePullRequest = async (pull_number: number) => { - const context = getBotContext(); +export const closePullRequest = async (context: BotContext, pull_number: number) => { const payload = context.payload as Payload; const logger = getLogger(); try { - await getBotContext().octokit.rest.pulls.update({ + await context.octokit.rest.pulls.update({ owner: payload.repository.owner.login, repo: payload.repository.name, pull_number, @@ -615,7 +600,7 @@ export const getPullRequestReviews = async ( } }; -export const getReviewRequests = async (context: Context, pull_number: number, owner: string, repo: string) => { +export const getReviewRequests = async (context: BotContext, pull_number: number, owner: string, repo: string) => { const logger = getLogger(); try { const response = await context.octokit.pulls.listRequestedReviewers({ @@ -625,7 +610,7 @@ export const getReviewRequests = async (context: Context, pull_number: number, o }); return response.data; } catch (e: unknown) { - logger.error(`Error: could not get requested reviewers, reason: ${e}`); + logger.error(context, `Error: could not get requested reviewers, reason: ${e}`); return null; } }; @@ -659,13 +644,13 @@ export const getPullByNumber = async (context: Context, pull_number: number) => }; // Get issues assigned to a username -export const getAssignedIssues = async (username: string) => { +export const getAssignedIssues = async (context: BotContext, username: string) => { const issuesArr = []; let fetchDone = false; const perPage = 30; 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); @@ -680,8 +665,8 @@ export const getAssignedIssues = async (username: string) => { return assigned_issues; }; -export const getOpenedPullRequestsForAnIssue = async (issueNumber: number, userName: string) => { - const pulls = await getOpenedPullRequests(userName); +export const getOpenedPullRequestsForAnIssue = async (context: BotContext, issueNumber: number, userName: string) => { + const pulls = await getOpenedPullRequests(context, userName); return pulls.filter((pull) => { if (!pull.body) return false; @@ -695,16 +680,14 @@ export const getOpenedPullRequestsForAnIssue = async (issueNumber: number, userN }); }; -export const getOpenedPullRequests = async (username: string) => { - const context = getBotContext(); +export const getOpenedPullRequests = async (context: BotContext, username: string) => { const prs = await getAllPullRequests(context, "open"); return prs.filter((pr) => !pr.draft && (pr.user?.login === username || !username)); }; -export const getCommitsOnPullRequest = async (pullNumber: number) => { +export const getCommitsOnPullRequest = async (context: BotContext, pullNumber: number) => { const logger = getLogger(); - const context = getBotContext(); - const payload = getBotContext().payload as Payload; + const payload = context.payload as Payload; try { const { data: commits } = await context.octokit.rest.pulls.listCommits({ owner: payload.repository.owner.login, @@ -718,14 +701,13 @@ export const getCommitsOnPullRequest = async (pullNumber: number) => { } }; -export const getAvailableOpenedPullRequests = async (username: string) => { - const context = getBotContext(); +export const getAvailableOpenedPullRequests = async (context: BotContext, username: string) => { const { unassign: { timeRangeForMaxIssue, timeRangeForMaxIssueEnabled }, - } = await getBotConfig(); + } = await context.botConfig; if (!timeRangeForMaxIssueEnabled) return []; - const opened_prs = await getOpenedPullRequests(username); + const opened_prs = await getOpenedPullRequests(context, username); const result = []; @@ -746,8 +728,7 @@ export const getAvailableOpenedPullRequests = async (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 const getAllLinkedIssuesAndPullsInBody = async (issueNumber: number) => { - const context = getBotContext(); +export const getAllLinkedIssuesAndPullsInBody = async (context: BotContext, issueNumber: number) => { const logger = getLogger(); const issue = await getIssueByNumber(context, issueNumber); @@ -798,8 +779,8 @@ export const getAllLinkedIssuesAndPullsInBody = async (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 || prCommentsRaw[i].body.includes("")) { linkedPRStreamlined.push({ @@ -820,8 +801,8 @@ export const getAllLinkedIssuesAndPullsInBody = async (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 || issueCommentsRaw[i].body.includes("")) { linkedIssueStreamlined.push({ diff --git a/src/helpers/label.ts b/src/helpers/label.ts index 7bc3744ec..31b3a430b 100644 --- a/src/helpers/label.ts +++ b/src/helpers/label.ts @@ -1,7 +1,6 @@ -import { Context } from "probot"; -import { getBotConfig, getBotContext, getLogger } from "../bindings"; import { calculateBountyPrice } from "../handlers"; -import { Label, Payload } from "../types"; +import { BotContext, Label, Payload } from "../types"; +import { getLogger } from "../bindings"; import { deleteLabel } from "./issue"; import { calculateWeight } from "../helpers"; @@ -12,8 +11,7 @@ export const COLORS = { }; // cspell:enable -export const listLabelsForRepo = async (per_page?: number, page?: number): Promise => { - const context = getBotContext(); +export const listLabelsForRepo = async (context: BotContext, per_page?: number, page?: number): Promise => { const payload = context.payload as Payload; const res = await context.octokit.rest.issues.listLabelsForRepo({ @@ -30,8 +28,7 @@ export const listLabelsForRepo = async (per_page?: number, page?: number): Promi throw new Error(`Failed to fetch lists of labels, code: ${res.status}`); }; -export const createLabel = async (name: string, labelType?: keyof typeof COLORS): Promise => { - const context = getBotContext(); +export const createLabel = async (context: BotContext, name: string, labelType?: keyof typeof COLORS): Promise => { const logger = getLogger(); const payload = context.payload as Payload; try { @@ -46,8 +43,7 @@ export const createLabel = async (name: string, labelType?: keyof typeof COLORS) } }; -export const getLabel = async (name: string): Promise => { - const context = getBotContext(); +export const getLabel = async (context: BotContext, name: string): Promise => { const logger = getLogger(); const payload = context.payload as Payload; try { @@ -65,20 +61,20 @@ export const getLabel = async (name: string): Promise => { }; // Function to update labels based on the base rate difference -export const updateLabelsFromBaseRate = async (owner: string, repo: string, context: Context, labels: Label[], previousBaseRate: number) => { +export const updateLabelsFromBaseRate = async (context: BotContext, owner: string, repo: string, labels: Label[], previousBaseRate: number) => { const logger = getLogger(); - const config = getBotConfig(); + const config = context.botConfig; const newLabels: string[] = []; const previousLabels: string[] = []; for (const timeLabel of config.price.timeLabels) { for (const priorityLabel of config.price.priorityLabels) { - const targetPrice = calculateBountyPrice(calculateWeight(timeLabel), calculateWeight(priorityLabel), config.price.baseMultiplier); + const targetPrice = calculateBountyPrice(context, calculateWeight(timeLabel), calculateWeight(priorityLabel), config.price.baseMultiplier); const targetPriceLabel = `Price: ${targetPrice} USD`; newLabels.push(targetPriceLabel); - const previousTargetPrice = calculateBountyPrice(calculateWeight(timeLabel), calculateWeight(priorityLabel), previousBaseRate); + const previousTargetPrice = calculateBountyPrice(context, calculateWeight(timeLabel), calculateWeight(priorityLabel), previousBaseRate); const previousTargetPriceLabel = `Price: ${previousTargetPrice} USD`; previousLabels.push(previousTargetPriceLabel); } @@ -98,11 +94,11 @@ export const updateLabelsFromBaseRate = async (owner: string, repo: string, cont 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(`Deleted ${uniqueNewLabels[index]}, updating it`); - await deleteLabel(uniqueNewLabels[index]); + await deleteLabel(context, uniqueNewLabels[index]); } // we can update safely @@ -122,6 +118,6 @@ export const updateLabelsFromBaseRate = async (owner: string, repo: string, cont } } } catch (error: unknown) { - logger.error(`Error updating labels, error: ${error}`); + logger.error(context, `Error updating labels, error: ${error}`); } }; diff --git a/src/helpers/parser.ts b/src/helpers/parser.ts index 42821a5b5..bc4da48eb 100644 --- a/src/helpers/parser.ts +++ b/src/helpers/parser.ts @@ -1,7 +1,8 @@ import axios from "axios"; import { HTMLElement, parse } from "node-html-parser"; import { getPullByNumber } from "./issue"; -import { getBotContext, getLogger } from "../bindings"; +import { getLogger } from "../bindings"; +import { BotContext } from "../types"; interface GitParser { owner: string; @@ -17,7 +18,7 @@ export interface LinkedPR { prHref: string; } -export const gitLinkedIssueParser = async ({ owner, repo, pull_number }: GitParser) => { +export const gitLinkedIssueParser = async (context: BotContext, { owner, repo, pull_number }: GitParser) => { const logger = getLogger(); try { const { data } = await axios.get(`https://github.com/${owner}/${repo}/pull/${pull_number}`); @@ -32,12 +33,12 @@ export const gitLinkedIssueParser = async ({ owner, repo, pull_number }: GitPars const issueUrl = linkedIssues[0].querySelector("a")?.attrs?.href || ""; return issueUrl; } catch (error) { - logger.error(`${JSON.stringify(error)}`); + logger.error(context, `${JSON.stringify(error)}`); return null; } }; -export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser): Promise => { +export const gitLinkedPrParser = async (context: BotContext, { owner, repo, issue_number }: GitParser): Promise => { const logger = getLogger(); try { const prData = []; @@ -70,13 +71,12 @@ export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser return prData; } catch (error) { - logger.error(`${JSON.stringify(error)}`); + logger.error(context, `${JSON.stringify(error)}`); return []; } }; -export const getLatestPullRequest = async (prs: LinkedPR[]) => { - const context = getBotContext(); +export const getLatestPullRequest = async (context: BotContext, prs: LinkedPR[]) => { let linkedPullRequest = null; for (const _pr of prs) { if (Number.isNaN(_pr.prNumber)) return null; diff --git a/src/helpers/payout.ts b/src/helpers/payout.ts index c6f4a06f2..428141940 100644 --- a/src/helpers/payout.ts +++ b/src/helpers/payout.ts @@ -12,9 +12,9 @@ */ import { Static } from "@sinclair/typebox"; -import { PayoutConfigSchema } from "../types"; +import { BotContext, PayoutConfigSchema } from "../types"; import { getUserPermission } from "./issue"; -import { getBotContext, getLogger } from "../bindings"; +import { getLogger } from "../bindings"; import { getAccessLevel } from "../adapters/supabase"; // available tokens for payouts @@ -48,10 +48,9 @@ export const getPayoutConfigByNetworkId = (networkId: number): PayoutConfigParti }; }; -export const hasLabelEditPermission = async (label: string, caller: string, repository: string) => { - const context = getBotContext(); +export const hasLabelEditPermission = async (context: BotContext, label: string, caller: string, repository: string) => { const logger = getLogger(); - const permissionLevel = await getUserPermission(caller, context); + const permissionLevel = await getUserPermission(context, caller); // get text before : const match = label.split(":"); diff --git a/src/helpers/permit.ts b/src/helpers/permit.ts index deddcffe0..b1c3f5809 100644 --- a/src/helpers/permit.ts +++ b/src/helpers/permit.ts @@ -1,9 +1,9 @@ import { MaxUint256, PermitTransferFrom, SignatureTransfer } from "@uniswap/permit2-sdk"; import { BigNumber, ethers } from "ethers"; -import { getBotConfig, getBotContext, getLogger } from "../bindings"; +import { getLogger } from "../bindings"; import { keccak256, toUtf8Bytes } from "ethers/lib/utils"; import Decimal from "decimal.js"; -import { Payload } from "../types"; +import { BotContext, Payload } from "../types"; import { savePermit } from "../adapters/supabase"; const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // same on all networks @@ -53,6 +53,7 @@ type TxData = { * @returns Permit2 url including base64 encoded data */ export const generatePermit2Signature = async ( + context: BotContext, spender: string, amountInEth: Decimal, identifier: string, @@ -60,7 +61,7 @@ export const generatePermit2Signature = async ( ): Promise<{ txData: TxData; payoutUrl: string }> => { const { payout: { networkId, privateKey, permitBaseUrl, rpc, paymentToken }, - } = getBotConfig(); + } = context.botConfig; const logger = getLogger(); const provider = new ethers.providers.JsonRpcProvider(rpc); const adminWallet = new ethers.Wallet(privateKey, provider); @@ -106,20 +107,19 @@ export const generatePermit2Signature = async ( return { txData, payoutUrl }; }; -export const savePermitToDB = async (bountyHunterId: number, txData: TxData): Promise => { +export const savePermitToDB = async (context: BotContext, bountyHunterId: number, txData: TxData): Promise => { const logger = getLogger(); - const context = getBotContext(); const payload = context.payload as Payload; const issue = payload.issue; const repository = payload.repository; const organization = payload.organization; if (!issue || !repository) { - logger.error("Cannot save permit to DB, missing issue, repository or organization"); + logger.error(context, "Cannot save permit to DB, missing issue, repository or organization"); throw new Error("Cannot save permit to DB, missing issue, repository or organization"); } - const { payout } = getBotConfig(); + const { payout } = context.botConfig; const { networkId } = payout; const permit: InsertPermit = { diff --git a/src/helpers/shared.ts b/src/helpers/shared.ts index 86e1599eb..03ff273b5 100644 --- a/src/helpers/shared.ts +++ b/src/helpers/shared.ts @@ -1,11 +1,9 @@ import ms from "ms"; -import { getBotContext } from "../bindings"; -import { LabelItem, Payload, UserType } from "../types"; +import { BotContext, LabelItem, Payload, UserType } from "../types"; const contextNamesToSkip = ["workflow_run"]; -export const shouldSkip = (): { skip: boolean; reason: string } => { - const context = getBotContext(); +export const shouldSkip = (context: BotContext): { skip: boolean; reason: string } => { const { name } = context; const payload = context.payload as Payload; const res: { skip: boolean; reason: string } = { skip: false, reason: "" }; diff --git a/src/helpers/similarity.ts b/src/helpers/similarity.ts index a5743e562..e69cecfcd 100644 --- a/src/helpers/similarity.ts +++ b/src/helpers/similarity.ts @@ -3,10 +3,11 @@ import axios, { AxiosError } from "axios"; import { ajv } from "../utils"; import { Static, Type } from "@sinclair/typebox"; import { backOff } from "exponential-backoff"; -import { Issue } from "../types"; +import { BotContext, Issue } from "../types"; -export const extractImportantWords = async (issue: Issue): Promise => { +export const extractImportantWords = async (context: BotContext, issue: Issue): Promise => { const res = await getAnswerFromChatGPT( + context, "", `${ process.env.CHATGPT_USER_PROMPT_FOR_IMPORTANT_WORDS || @@ -18,8 +19,9 @@ export const extractImportantWords = async (issue: Issue): Promise => return res.split(/[,# ]/); }; -export const measureSimilarity = async (first: Issue, second: Issue): Promise => { +export const measureSimilarity = async (context: BotContext, first: Issue, second: Issue): Promise => { const res = await getAnswerFromChatGPT( + context, "", `${( process.env.CHATGPT_USER_PROMPT_FOR_MEASURE_SIMILARITY || @@ -48,7 +50,13 @@ const ChoicesSchema = Type.Object({ type Choices = Static; -export const getAnswerFromChatGPT = async (systemPrompt: string, userPrompt: string, temperature = 0, max_tokens = 1500): Promise => { +export const getAnswerFromChatGPT = async ( + context: BotContext, + systemPrompt: string, + userPrompt: string, + temperature = 0, + max_tokens = 1500 +): Promise => { const logger = getLogger(); const body = JSON.stringify({ model: "gpt-3.5-turbo", @@ -87,18 +95,18 @@ export const getAnswerFromChatGPT = async (systemPrompt: string, userPrompt: str const validate = ajv.compile(ChoicesSchema); const valid = validate(data); if (!valid) { - logger.error(`Error occured from OpenAI`); + logger.error(context, `Error occured from OpenAI`); return ""; } const { choices: choice } = data; if (choice.length <= 0) { - logger.error(`No result from OpenAI`); + logger.error(context, `No result from OpenAI`); return ""; } const answer = choice[0].message.content; return answer; } catch (error) { - logger.error(`Getting response from ChatGPT failed: ${error}`); + logger.error(context, `Getting response from ChatGPT failed: ${error}`); return ""; } }; diff --git a/src/helpers/user.ts b/src/helpers/user.ts index 5f151f241..211b4131e 100644 --- a/src/helpers/user.ts +++ b/src/helpers/user.ts @@ -1,13 +1,12 @@ -import { getBotContext, getLogger } from "../bindings"; -import { User } from "../types"; +import { getLogger } from "../bindings"; +import { BotContext, User } from "../types"; /** * @dev Gets the publicly available information about `username` * * @param username The username you're getting information for */ -export const getUser = async (username: string): Promise => { - const context = getBotContext(); +export const getUser = async (context: BotContext, username: string): Promise => { const logger = getLogger(); try { @@ -33,8 +32,7 @@ export const getUser = async (username: string): Promise => { * * @returns The role name of a user in the organization. "admin" || "member" || "billing_manager" */ -export const getOrgMembershipOfUser = async (org: string, username: string): Promise => { - const context = getBotContext(); +export const getOrgMembershipOfUser = async (context: BotContext, org: string, username: string): Promise => { const logger = getLogger(); let membership: string | undefined = undefined; diff --git a/src/tests/message-test.ts b/src/tests/message-test.ts index 97aec52a8..0806eb8ac 100644 --- a/src/tests/message-test.ts +++ b/src/tests/message-test.ts @@ -1,9 +1,10 @@ import { telegramFormattedNotifier, telegramNotifier } from "../adapters/telegram/helpers"; +import { BotContext } from "../types"; const chatIds = ["tg1", "tg2", "tg3"]; // SHOULD update chatIds with valid ones -export async function messageTests() { +export async function messageTests(context: BotContext) { /**@method telegramNotifier - call unformatted issue */ - await telegramNotifier({ + await telegramNotifier(context, { chatIds: chatIds, action: `new issue`, title: `Optimize CI/CD Build Speed`, @@ -14,7 +15,7 @@ export async function messageTests() { }); /**@method telegramNotifier - call unformatted pull */ - await telegramNotifier({ + await telegramNotifier(context, { chatIds: chatIds, action: `new pull`, title: `Enhancement/styles`, @@ -25,7 +26,7 @@ export async function messageTests() { }); /**@method telegramFormattedNotifier - call formatted issue */ - await telegramFormattedNotifier({ + await telegramFormattedNotifier(context, { chatIds: chatIds, text: `new issue: Optimize CI/CD Build Speed ` + @@ -37,7 +38,7 @@ export async function messageTests() { }); /**@method telegramFormattedNotifier - call formatted pull */ - await telegramFormattedNotifier({ + await telegramFormattedNotifier(context, { chatIds: chatIds, text: `new pull: Enhancement/styles ` + diff --git a/src/tests/summarize-test.ts b/src/tests/summarize-test.ts deleted file mode 100644 index 621548a8c..000000000 --- a/src/tests/summarize-test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { run } from "../handlers/wildcard/weekly/action"; - -run() - .then((res) => { - console.log(res); - }) - .catch((err) => { - console.log(err); - }); diff --git a/src/types/config.ts b/src/types/config.ts index a22683a28..e276956f9 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1,5 +1,6 @@ import { Static, Type } from "@sinclair/typebox"; import { LogLevel } from "./log"; +import { Context } from "probot"; const LabelItemSchema = Type.Object( { @@ -163,6 +164,10 @@ export const BotConfigSchema = Type.Object({ export type BotConfig = Static; +export interface BotContext extends Context { + botConfig: BotConfig; +} + export const StreamlinedCommentSchema = Type.Object({ login: Type.Optional(Type.String()), body: Type.Optional(Type.String()), diff --git a/src/types/handlers.ts b/src/types/handlers.ts index 5a79f4dd0..47c6df62f 100644 --- a/src/types/handlers.ts +++ b/src/types/handlers.ts @@ -1,8 +1,9 @@ +import { BotContext } from "./config"; import { Comment } from "./payload"; -export type CommandsHandler = (args: string) => Promise; -export type ActionHandler = (args?: string) => Promise; -export type CallbackHandler = (issue_number: number, text: string, action: string, reply_to?: Comment) => Promise; +export type CommandsHandler = (context: BotContext, args: string) => Promise; +export type ActionHandler = (context: BotContext, args?: string) => Promise; +export type CallbackHandler = (context: BotContext, issue_number: number, text: string, action: string, reply_to?: Comment) => Promise; export type PreActionHandler = ActionHandler; export type PostActionHandler = ActionHandler; diff --git a/src/utils/private.ts b/src/utils/private.ts index 44ba7fb15..cd78f6fdf 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -1,6 +1,6 @@ import _sodium from "libsodium-wrappers"; import YAML from "yaml"; -import { MergedConfig, Payload } from "../types"; +import { BotContext, MergedConfig, Payload } from "../types"; import { Context } from "probot"; import merge from "lodash/merge"; @@ -108,7 +108,7 @@ const mergeConfigs = (configs: MergedConfigs) => { return merge({}, configs.parsedDefault, configs.parsedOrg, configs.parsedRepo); }; -export const getWideConfig = async (context: Context) => { +export const getWideConfig = async (context: BotContext) => { const orgConfig = await getConfigSuperset(context, "org", CONFIG_PATH); const repoConfig = await getConfigSuperset(context, "repo", CONFIG_PATH); const payload = context.payload as Payload; @@ -119,7 +119,7 @@ export const getWideConfig = async (context: Context) => { const { valid, error } = validate(WideConfigSchema, 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(context, payload.issue.number, err.message); throw err; } } @@ -130,7 +130,7 @@ export const getWideConfig = async (context: Context) => { const { valid, error } = validate(WideConfigSchema, 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(context, payload.issue.number, err.message); throw err; } }