From fbc1e0d6cc2b8f6a4ffd037a4719142063de6daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Fri, 7 Jul 2023 15:15:04 +0900 Subject: [PATCH 001/106] chore: sync upstream --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 8d05c1033..08f125862 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,10 @@ "start:serverless": "tsx src/adapters/github/github-actions.ts", "start:watch": "nodemon --exec 'yarn start'", "start": "probot run ./lib/index.js", - "prepare": "husky install" + "prepare": "husky install", + "utils:sync-upstream-1": "git fetch upstream", + "utils:sync-upstream-2": "git merge upstream/development", + "utils:sync-upstream-3": "git push origin development" }, "dependencies": { "@actions/core": "^1.10.0", From 25a928bb3a327721173e00d154fbfcfce454a23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Sun, 20 Aug 2023 20:39:59 +0900 Subject: [PATCH 002/106] refactor: remove unused declaration --- src/adapters/supabase/helpers/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index d0610c090..c914c1843 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -169,13 +169,13 @@ export const upsertWalletAddress = async (username: string, address: string): Pr }); logger.info(`Upserting a wallet address done, { data: ${data}, error: ${error} }`); } else { - const { data: _data, error: _error } = await supabase.from("wallets").insert({ + const { error } = await supabase.from("wallets").insert({ user_name: username, wallet_address: address, created_at: new Date().toUTCString(), updated_at: new Date().toUTCString(), }); - logger.info(`Creating a new wallet_table record done, { error: ${_error?.message} }`); + logger.info(`Creating a new wallet_table record done, { error: ${error?.message} }`); } }; From 4b973fc281d8b6b6164f422d8177a5efd514079f Mon Sep 17 00:00:00 2001 From: HARALD Date: Wed, 23 Aug 2023 11:05:24 +0900 Subject: [PATCH 003/106] feat: incentiveForPullRequest reviewers --- package.json | 3 +- src/handlers/payout/post.ts | 144 +++++++++++++++++++++++++++++++++++- src/handlers/processors.ts | 4 +- 3 files changed, 147 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index bf2367eb1..fa7b7a601 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@sinclair/typebox": "^0.25.9", "@supabase/supabase-js": "^2.4.0", "@types/ms": "^0.7.31", + "@types/parse5": "^7.0.0", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "@uniswap/permit2-sdk": "^1.2.0", @@ -41,8 +42,8 @@ "ajv": "^8.11.2", "ajv-formats": "^2.1.1", "axios": "^1.3.2", - "decimal.js": "^10.4.3", "copyfiles": "^2.4.1", + "decimal.js": "^10.4.3", "ethers": "^5.7.2", "husky": "^8.0.2", "jimp": "^0.22.4", diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 17c8eb4ad..4a9171520 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -1,6 +1,16 @@ import { getWalletAddress } from "../../adapters/supabase"; import { getBotConfig, getBotContext, getLogger } from "../../bindings"; -import { addCommentToIssue, generatePermit2Signature, getAllIssueComments, getIssueDescription, getTokenSymbol, parseComments } from "../../helpers"; +import { + addCommentToIssue, + generatePermit2Signature, + getAllIssueComments, + getAllPullRequestReviews, + getAllPullRequests, + getIssueDescription, + getTokenSymbol, + parseComments, +} from "../../helpers"; +import { gitLinkedIssueParser } from "../../helpers/parser"; import { Incentives, Payload, StateReason, UserType } from "../../types"; import { commentParser } from "../comment"; import Decimal from "decimal.js"; @@ -121,6 +131,138 @@ export const incentivizeComments = async () => { await addCommentToIssue(comment, issue.number); }; +export const incentivizePullRequestReviews = async () => { + const logger = getLogger(); + const { + mode: { incentiveMode, paymentPermitMaxPrice }, + price: { baseMultiplier, incentives }, + payout: { paymentToken, rpc }, + } = getBotConfig(); + if (!incentiveMode) { + logger.info(`No incentive mode. skipping to process`); + // return; + } + const context = getBotContext(); + const payload = context.payload as Payload; + const issue = payload.issue; + if (!issue) { + logger.info(`Incomplete payload. issue: ${issue}`); + return; + } + + if (issue.state_reason !== StateReason.COMPLETED) { + logger.info("incentivizeComments: comment incentives skipped because the issue was not closed as completed"); + // return; + } + + if (paymentPermitMaxPrice == 0 || !paymentPermitMaxPrice) { + logger.info(`incentivizeComments: skipping to generate permit2 url, reason: { paymentPermitMaxPrice: ${paymentPermitMaxPrice}}`); + // return; + } + + const issueDetailed = bountyInfo(issue); + if (!issueDetailed.isBounty) { + logger.info(`incentivizeComments: its not a bounty`); + // return; + } + + const pulls = await getAllPullRequests(context, "closed"); + if (pulls.length === 0) { + logger.debug(`No pull requests found at this time`); + return; + } + + let linkedPull; + + for (const pull of pulls) { + const pullRequestLinked = await gitLinkedIssueParser({ + owner: payload.repository.owner.login, + repo: payload.repository.name, + issue_number: pull.number, + }); + const linkedIssueNumber = pullRequestLinked.substring(pullRequestLinked.lastIndexOf("/") + 1); + if (linkedIssueNumber === issue.number.toString()) { + linkedPull = pull; + break; + } + } + + if (!linkedPull) { + logger.debug(`No linked pull requests found`); + return; + } + + const comments = await getAllIssueComments(issue.number); + const permitComments = comments.filter( + (content) => content.body.includes("Reviewer Rewards") && content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot + ); + if (permitComments.length > 0) { + logger.info(`incentivizePullRequestReviews: skip to generate a permit url because it has been already posted`); + return; + } + + const assignees = issue?.assignees ?? []; + const assignee = assignees.length > 0 ? assignees[0] : undefined; + if (!assignee) { + logger.info("incentivizePullRequestReviews: skipping payment permit generation because `assignee` is `undefined`."); + return; + } + + const prReviews = await getAllPullRequestReviews(context, linkedPull.number); + logger.info(`Getting the PR reviews done. comments: ${JSON.stringify(prReviews)}`); + const prReviewsByUser: Record = {}; + for (const review of prReviews) { + const user = review.user; + if (!user) continue; + if (user.type == UserType.Bot || user.login == assignee) continue; + if (!review.body) { + logger.info(`Skipping to parse the comment because body_html is undefined. comment: ${JSON.stringify(review)}`); + continue; + } + if (!prReviewsByUser[user.login]) { + prReviewsByUser[user.login] = []; + } + prReviewsByUser[user.login].push(review.body); + } + const tokenSymbol = await getTokenSymbol(paymentToken, rpc); + logger.info(`Filtering by the user type done. commentsByUser: ${JSON.stringify(prReviewsByUser)}`); + + // The mapping between gh handle and comment with a permit url + const reward: Record = {}; + + // The mapping between gh handle and amount in ETH + const fallbackReward: Record = {}; + let comment = `#### Reviewer Rewards\n`; + for (const user of Object.keys(prReviewsByUser)) { + const comments = prReviewsByUser[user]; + const commentsByNode = await parseComments(comments, ItemsToExclude); + const rewardValue = calculateRewardValue(commentsByNode, incentives); + if (rewardValue.equals(0)) { + logger.info(`Skipping to generate a permit url because the reward value is 0. user: ${user}`); + continue; + } + logger.info(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); + const account = await getWalletAddress(user); + const amountInETH = rewardValue.mul(baseMultiplier).div(1000); + if (amountInETH.gt(paymentPermitMaxPrice)) { + logger.info(`Skipping comment reward for user ${user} because reward is higher than payment permit max price`); + continue; + } + if (account) { + const { payoutUrl } = await generatePermit2Signature(account, amountInETH, issue.node_id); + comment = `${comment}### [ **${user}: [ CLAIM ${amountInETH} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n`; + reward[user] = payoutUrl; + } else { + fallbackReward[user] = amountInETH; + } + } + + logger.info(`Permit url generated for contributors. reward: ${JSON.stringify(reward)}`); + logger.info(`Skipping to generate a permit url for missing accounts. fallback: ${JSON.stringify(fallbackReward)}`); + + await addCommentToIssue(comment, issue.number); +}; + export const incentivizeCreatorComment = async () => { const logger = getLogger(); const { diff --git a/src/handlers/processors.ts b/src/handlers/processors.ts index d64e07eaa..1c274db02 100644 --- a/src/handlers/processors.ts +++ b/src/handlers/processors.ts @@ -7,7 +7,7 @@ import { handleComment, issueClosedCallback, issueReopenedCallback } from "./com import { checkPullRequests } from "./assign/auto"; import { createDevPoolPR } from "./pull-request"; import { runOnPush } from "./push"; -import { incentivizeComments, incentivizeCreatorComment } from "./payout"; +import { incentivizeComments, incentivizeCreatorComment, incentivizePullRequestReviews } from "./payout"; export const processors: Record = { [GithubEvent.ISSUES_OPENED]: { @@ -53,7 +53,7 @@ export const processors: Record = { [GithubEvent.ISSUES_CLOSED]: { pre: [nullHandler], action: [issueClosedCallback], - post: [incentivizeCreatorComment, incentivizeComments], + post: [incentivizeCreatorComment, incentivizeComments, incentivizePullRequestReviews], }, [GithubEvent.PULL_REQUEST_OPENED]: { pre: [nullHandler], From aa84e7d641a89ea5c50d8734ef1b980079febc80 Mon Sep 17 00:00:00 2001 From: HARALD Date: Wed, 23 Aug 2023 11:26:28 +0900 Subject: [PATCH 004/106] feat: minor fix --- src/handlers/payout/post.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 4a9171520..89095d55e 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -151,24 +151,24 @@ export const incentivizePullRequestReviews = async () => { } if (issue.state_reason !== StateReason.COMPLETED) { - logger.info("incentivizeComments: comment incentives skipped because the issue was not closed as completed"); + logger.info("incentivizePullRequestReviews: comment incentives skipped because the issue was not closed as completed"); // return; } if (paymentPermitMaxPrice == 0 || !paymentPermitMaxPrice) { - logger.info(`incentivizeComments: skipping to generate permit2 url, reason: { paymentPermitMaxPrice: ${paymentPermitMaxPrice}}`); + logger.info(`incentivizePullRequestReviews: skipping to generate permit2 url, reason: { paymentPermitMaxPrice: ${paymentPermitMaxPrice}}`); // return; } const issueDetailed = bountyInfo(issue); if (!issueDetailed.isBounty) { - logger.info(`incentivizeComments: its not a bounty`); + logger.info(`incentivizePullRequestReviews: its not a bounty`); // return; } const pulls = await getAllPullRequests(context, "closed"); if (pulls.length === 0) { - logger.debug(`No pull requests found at this time`); + logger.debug(`incentivizePullRequestReviews: No pull requests found at this time`); return; } @@ -188,7 +188,7 @@ export const incentivizePullRequestReviews = async () => { } if (!linkedPull) { - logger.debug(`No linked pull requests found`); + logger.debug(`incentivizePullRequestReviews: No linked pull requests found`); return; } @@ -216,7 +216,7 @@ export const incentivizePullRequestReviews = async () => { if (!user) continue; if (user.type == UserType.Bot || user.login == assignee) continue; if (!review.body) { - logger.info(`Skipping to parse the comment because body_html is undefined. comment: ${JSON.stringify(review)}`); + logger.info(`incentivizePullRequestReviews: Skipping to parse the comment because body_html is undefined. comment: ${JSON.stringify(review)}`); continue; } if (!prReviewsByUser[user.login]) { @@ -225,7 +225,7 @@ export const incentivizePullRequestReviews = async () => { prReviewsByUser[user.login].push(review.body); } const tokenSymbol = await getTokenSymbol(paymentToken, rpc); - logger.info(`Filtering by the user type done. commentsByUser: ${JSON.stringify(prReviewsByUser)}`); + logger.info(`incentivizePullRequestReviews: Filtering by the user type done. commentsByUser: ${JSON.stringify(prReviewsByUser)}`); // The mapping between gh handle and comment with a permit url const reward: Record = {}; @@ -238,14 +238,14 @@ export const incentivizePullRequestReviews = async () => { const commentsByNode = await parseComments(comments, ItemsToExclude); const rewardValue = calculateRewardValue(commentsByNode, incentives); if (rewardValue.equals(0)) { - logger.info(`Skipping to generate a permit url because the reward value is 0. user: ${user}`); + logger.info(`incentivizePullRequestReviews: Skipping to generate a permit url because the reward value is 0. user: ${user}`); continue; } - logger.info(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); + logger.info(`incentivizePullRequestReviews: Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); const amountInETH = rewardValue.mul(baseMultiplier).div(1000); if (amountInETH.gt(paymentPermitMaxPrice)) { - logger.info(`Skipping comment reward for user ${user} because reward is higher than payment permit max price`); + logger.info(`incentivizePullRequestReviews: Skipping comment reward for user ${user} because reward is higher than payment permit max price`); continue; } if (account) { @@ -257,8 +257,8 @@ export const incentivizePullRequestReviews = async () => { } } - logger.info(`Permit url generated for contributors. reward: ${JSON.stringify(reward)}`); - logger.info(`Skipping to generate a permit url for missing accounts. fallback: ${JSON.stringify(fallbackReward)}`); + logger.info(`incentivizePullRequestReviews: Permit url generated for pull request reviewers. reward: ${JSON.stringify(reward)}`); + logger.info(`incentivizePullRequestReviews: Skipping to generate a permit url for missing accounts. fallback: ${JSON.stringify(fallbackReward)}`); await addCommentToIssue(comment, issue.number); }; From 109758e19bbef1309fe6dff5e2d97105f9233d0b Mon Sep 17 00:00:00 2001 From: HARALD Date: Wed, 23 Aug 2023 11:36:09 +0900 Subject: [PATCH 005/106] feat: slight change --- src/handlers/payout/post.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 89095d55e..be3104fb7 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -131,7 +131,7 @@ export const incentivizeComments = async () => { await addCommentToIssue(comment, issue.number); }; -export const incentivizePullRequestReviews = async () => { +export async function incentivizePullRequestReviews() { const logger = getLogger(); const { mode: { incentiveMode, paymentPermitMaxPrice }, @@ -261,7 +261,7 @@ export const incentivizePullRequestReviews = async () => { logger.info(`incentivizePullRequestReviews: Skipping to generate a permit url for missing accounts. fallback: ${JSON.stringify(fallbackReward)}`); await addCommentToIssue(comment, issue.number); -}; +} export const incentivizeCreatorComment = async () => { const logger = getLogger(); From 12bccaeea35636665f31e859fe011527801d8004 Mon Sep 17 00:00:00 2001 From: HARALD Date: Wed, 23 Aug 2023 11:37:01 +0900 Subject: [PATCH 006/106] feat: fix function declaration --- src/handlers/payout/post.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index be3104fb7..ac423fda7 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -21,7 +21,7 @@ const ItemsToExclude: string[] = ["blockquote"]; * Incentivize the contributors based on their contribution. * The default formula has been defined in https://github.com/ubiquity/ubiquibot/issues/272 */ -export const incentivizeComments = async () => { +export async function incentivizeComments() { const logger = getLogger(); const { mode: { incentiveMode, paymentPermitMaxPrice }, @@ -129,7 +129,7 @@ export const incentivizeComments = async () => { logger.info(`Skipping to generate a permit url for missing accounts. fallback: ${JSON.stringify(fallbackReward)}`); await addCommentToIssue(comment, issue.number); -}; +} export async function incentivizePullRequestReviews() { const logger = getLogger(); From 25c4da5e4eacb131963949f7739ec2c2003301ed Mon Sep 17 00:00:00 2001 From: HARALD Date: Wed, 23 Aug 2023 18:25:33 +0900 Subject: [PATCH 007/106] feat: renamed some variables --- src/helpers/parser.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/helpers/parser.ts b/src/helpers/parser.ts index b05411fee..fed365efd 100644 --- a/src/helpers/parser.ts +++ b/src/helpers/parser.ts @@ -25,19 +25,19 @@ export const gitIssueParser = async ({ owner, repo, issue_number }: GitParser): } }; -export const gitLinkedIssueParser = async ({ owner, repo, issue_number }: GitParser): Promise => { +export const gitLinkedIssueParser = async ({ owner, repo, issue_number: pull_number }: GitParser): Promise => { try { - const { data } = await axios.get(`https://github.com/${owner}/${repo}/pull/${issue_number}`); + const { data } = await axios.get(`https://github.com/${owner}/${repo}/pull/${pull_number}`); const dom = parse(data); const devForm = dom.querySelector("[data-target='create-branch.developmentForm']") as HTMLElement; - const linkedPRs = devForm.querySelectorAll(".my-1"); + const linkedIssues = devForm.querySelectorAll(".my-1"); - if (linkedPRs.length === 0) { + if (linkedIssues.length === 0) { return ""; } - const prUrl = linkedPRs[0].querySelector("a")?.attrs?.href || ""; - return prUrl; + const issueUrl = linkedIssues[0].querySelector("a")?.attrs?.href || ""; + return issueUrl; } catch (error) { return ""; } From efb941d8e933676384195f90024aea14c2e5ddd8 Mon Sep 17 00:00:00 2001 From: HARALD Date: Wed, 23 Aug 2023 18:31:28 +0900 Subject: [PATCH 008/106] feat: get comments as markdown --- src/helpers/issue.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index 5127c1c6c..7e86f3fb1 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -506,13 +506,13 @@ export const closePullRequest = async (pull_number: number) => { } }; -export const getAllPullRequestReviews = async (context: Context, pull_number: number) => { +export const getAllPullRequestReviews = async (context: Context, pull_number: number, format: "raw" | "html" | "text" | "full" = "raw") => { const prArr = []; let fetchDone = false; const perPage = 30; let curPage = 1; while (!fetchDone) { - const prs = await getPullRequestReviews(context, pull_number, perPage, curPage); + const prs = await getPullRequestReviews(context, pull_number, perPage, curPage, format); // push the objects to array prArr.push(...prs); @@ -523,7 +523,13 @@ export const getAllPullRequestReviews = async (context: Context, pull_number: nu return prArr; }; -export const getPullRequestReviews = async (context: Context, pull_number: number, per_page: number, page: number) => { +export const getPullRequestReviews = async ( + context: Context, + pull_number: number, + per_page: number, + page: number, + format: "raw" | "html" | "text" | "full" = "raw" +) => { const logger = getLogger(); const payload = context.payload as Payload; try { @@ -533,6 +539,9 @@ export const getPullRequestReviews = async (context: Context, pull_number: numbe pull_number, per_page, page, + mediaType: { + format, + }, }); return reviews; } catch (e: unknown) { From 06d688a84d443012b9e180892d7ce8c0583bb1b0 Mon Sep 17 00:00:00 2001 From: HARALD Date: Thu, 24 Aug 2023 17:14:50 +0900 Subject: [PATCH 009/106] feat: revert changing function names --- src/handlers/payout/post.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index ac423fda7..89095d55e 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -21,7 +21,7 @@ const ItemsToExclude: string[] = ["blockquote"]; * Incentivize the contributors based on their contribution. * The default formula has been defined in https://github.com/ubiquity/ubiquibot/issues/272 */ -export async function incentivizeComments() { +export const incentivizeComments = async () => { const logger = getLogger(); const { mode: { incentiveMode, paymentPermitMaxPrice }, @@ -129,9 +129,9 @@ export async function incentivizeComments() { logger.info(`Skipping to generate a permit url for missing accounts. fallback: ${JSON.stringify(fallbackReward)}`); await addCommentToIssue(comment, issue.number); -} +}; -export async function incentivizePullRequestReviews() { +export const incentivizePullRequestReviews = async () => { const logger = getLogger(); const { mode: { incentiveMode, paymentPermitMaxPrice }, @@ -261,7 +261,7 @@ export async function incentivizePullRequestReviews() { logger.info(`incentivizePullRequestReviews: Skipping to generate a permit url for missing accounts. fallback: ${JSON.stringify(fallbackReward)}`); await addCommentToIssue(comment, issue.number); -} +}; export const incentivizeCreatorComment = async () => { const logger = getLogger(); From 07b2275e03f84fa649013f3dc39ae7d067c67ee8 Mon Sep 17 00:00:00 2001 From: HARALD Date: Thu, 24 Aug 2023 21:03:43 +0900 Subject: [PATCH 010/106] feat: minor fix --- src/handlers/payout/post.ts | 4 ++-- src/helpers/parser.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index bcd502c32..348f12a47 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -215,14 +215,14 @@ export const incentivizePullRequestReviews = async () => { const user = review.user; if (!user) continue; if (user.type == UserType.Bot || user.login == assignee) continue; - if (!review.body) { + if (!review.body_html) { logger.info(`incentivizePullRequestReviews: Skipping to parse the comment because body_html is undefined. comment: ${JSON.stringify(review)}`); continue; } if (!prReviewsByUser[user.login]) { prReviewsByUser[user.login] = []; } - prReviewsByUser[user.login].push(review.body); + prReviewsByUser[user.login].push(review.body_html); } const tokenSymbol = await getTokenSymbol(paymentToken, rpc); logger.info(`incentivizePullRequestReviews: Filtering by the user type done. commentsByUser: ${JSON.stringify(prReviewsByUser)}`); diff --git a/src/helpers/parser.ts b/src/helpers/parser.ts index fed365efd..ceb08fbcf 100644 --- a/src/helpers/parser.ts +++ b/src/helpers/parser.ts @@ -4,7 +4,8 @@ import { HTMLElement, parse } from "node-html-parser"; interface GitParser { owner: string; repo: string; - issue_number: number; + issue_number?: number; + pull_number?: number; } export const gitIssueParser = async ({ owner, repo, issue_number }: GitParser): Promise => { @@ -25,7 +26,7 @@ export const gitIssueParser = async ({ owner, repo, issue_number }: GitParser): } }; -export const gitLinkedIssueParser = async ({ owner, repo, issue_number: pull_number }: GitParser): Promise => { +export const gitLinkedIssueParser = async ({ owner, repo, pull_number }: GitParser): Promise => { try { const { data } = await axios.get(`https://github.com/${owner}/${repo}/pull/${pull_number}`); const dom = parse(data); From 7104dd39b2e8da61e171b48513fee3b3d609f8b4 Mon Sep 17 00:00:00 2001 From: HARALD Date: Thu, 24 Aug 2023 21:29:44 +0900 Subject: [PATCH 011/106] feat: minor fix --- src/handlers/payout/post.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 348f12a47..57e472893 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -178,7 +178,7 @@ export const incentivizePullRequestReviews = async () => { const pullRequestLinked = await gitLinkedIssueParser({ owner: payload.repository.owner.login, repo: payload.repository.name, - issue_number: pull.number, + pull_number: pull.number, }); const linkedIssueNumber = pullRequestLinked.substring(pullRequestLinked.lastIndexOf("/") + 1); if (linkedIssueNumber === issue.number.toString()) { @@ -208,7 +208,7 @@ export const incentivizePullRequestReviews = async () => { return; } - const prReviews = await getAllPullRequestReviews(context, linkedPull.number); + const prReviews = await getAllPullRequestReviews(context, linkedPull.number, "full"); logger.info(`Getting the PR reviews done. comments: ${JSON.stringify(prReviews)}`); const prReviewsByUser: Record = {}; for (const review of prReviews) { From 66d2d7b2d7a61eee271123a77afc1d6bff09e567 Mon Sep 17 00:00:00 2001 From: HARALD Date: Sat, 26 Aug 2023 00:27:02 +0900 Subject: [PATCH 012/106] feat: slight change --- src/handlers/payout/post.ts | 38 ++++++++++--------------------------- src/helpers/parser.ts | 13 +++++++++++++ 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 95b17caea..ffeaee275 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -5,12 +5,11 @@ import { generatePermit2Signature, getAllIssueComments, getAllPullRequestReviews, - getAllPullRequests, getIssueDescription, getTokenSymbol, parseComments, } from "../../helpers"; -import { gitLinkedIssueParser } from "../../helpers/parser"; +import { gitLinkedPrParser } from "../../helpers/parser"; import { Incentives, MarkdownItem, Payload, StateReason, UserType } from "../../types"; import { commentParser } from "../comment"; import Decimal from "decimal.js"; @@ -140,7 +139,7 @@ export const incentivizePullRequestReviews = async () => { } = getBotConfig(); if (!incentiveMode) { logger.info(`No incentive mode. skipping to process`); - // return; + return; } const context = getBotContext(); const payload = context.payload as Payload; @@ -152,46 +151,29 @@ export const incentivizePullRequestReviews = async () => { if (issue.state_reason !== StateReason.COMPLETED) { logger.info("incentivizePullRequestReviews: comment incentives skipped because the issue was not closed as completed"); - // return; + return; } if (paymentPermitMaxPrice == 0 || !paymentPermitMaxPrice) { logger.info(`incentivizePullRequestReviews: skipping to generate permit2 url, reason: { paymentPermitMaxPrice: ${paymentPermitMaxPrice}}`); - // return; + return; } const issueDetailed = bountyInfo(issue); if (!issueDetailed.isBounty) { logger.info(`incentivizePullRequestReviews: its not a bounty`); - // return; - } - - const pulls = await getAllPullRequests(context, "closed"); - if (pulls.length === 0) { - logger.debug(`incentivizePullRequestReviews: No pull requests found at this time`); return; } - let linkedPull; + const pullRequestLinked = await gitLinkedPrParser({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issue.number }); - for (const pull of pulls) { - const pullRequestLinked = await gitLinkedIssueParser({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - pull_number: pull.number, - }); - const linkedIssueNumber = pullRequestLinked.substring(pullRequestLinked.lastIndexOf("/") + 1); - if (linkedIssueNumber === issue.number.toString()) { - linkedPull = pull; - break; - } - } - - if (!linkedPull) { + if (pullRequestLinked === "") { logger.debug(`incentivizePullRequestReviews: No linked pull requests found`); return; } + const linkedPullNumber = pullRequestLinked.substring(pullRequestLinked.lastIndexOf("/") + 1); + const comments = await getAllIssueComments(issue.number); const permitComments = comments.filter( (content) => content.body.includes("Reviewer Rewards") && content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot @@ -208,7 +190,7 @@ export const incentivizePullRequestReviews = async () => { return; } - const prReviews = await getAllPullRequestReviews(context, linkedPull.number, "full"); + const prReviews = await getAllPullRequestReviews(context, Number(linkedPullNumber), "full"); logger.info(`Getting the PR reviews done. comments: ${JSON.stringify(prReviews)}`); const prReviewsByUser: Record = {}; for (const review of prReviews) { @@ -243,7 +225,7 @@ export const incentivizePullRequestReviews = async () => { } logger.info(`incentivizePullRequestReviews: Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); - const amountInETH = rewardValue.mul(baseMultiplier).div(1000); + const amountInETH = rewardValue.mul(baseMultiplier); if (amountInETH.gt(paymentPermitMaxPrice)) { logger.info(`incentivizePullRequestReviews: Skipping comment reward for user ${user} because reward is higher than payment permit max price`); continue; diff --git a/src/helpers/parser.ts b/src/helpers/parser.ts index ceb08fbcf..b7080f9a7 100644 --- a/src/helpers/parser.ts +++ b/src/helpers/parser.ts @@ -43,3 +43,16 @@ export const gitLinkedIssueParser = async ({ owner, repo, pull_number }: GitPars return ""; } }; + +export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser): Promise => { + try { + const { data } = await axios.get(`https://github.com/${owner}/${repo}/issues/${issue_number}`); + const dom = parse(data); + const devForm = dom.querySelector("[data-target='create-branch.developmentForm']") as HTMLElement; + const linkedPRs = devForm.querySelectorAll(".my-1"); + if (linkedPRs.length === 0) return ""; + return linkedPRs[linkedPRs.length - 1].querySelector("a")?.attrs?.href || ""; + } catch (error) { + return ""; + } +}; From 70ca321f6c646907c8537634dcc998a845d4f719 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Sat, 26 Aug 2023 00:12:49 +0100 Subject: [PATCH 013/106] fix: stale bounty ready to start message in /start response --- src/bindings/config.ts | 1 + src/handlers/comment/handlers/assign.ts | 7 ++++++- src/handlers/comment/handlers/table.ts | 7 ++++--- src/types/config.ts | 1 + 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/bindings/config.ts b/src/bindings/config.ts index e7cdf8bea..f918f60e9 100644 --- a/src/bindings/config.ts +++ b/src/bindings/config.ts @@ -76,6 +76,7 @@ export const loadConfig = async (context: Context): Promise => { command: commandSettings, assign: { bountyHunterMax: bountyHunterMax, + staleBountyTime: ms(process.env.STALE_BOUNTY_TIME || "30d"), }, sodium: { privateKey: process.env.X25519_PRIVATE_KEY ?? "", diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index 82d73768f..f08501929 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -18,8 +18,11 @@ export const assign = async (body: string) => { const id = organization?.id || repository?.id; // repository?.id as fallback + const staleBounty = config.assign.staleBountyTime; + logger.info(`Received '/start' command from user: ${payload.sender.login}, body: ${body}`); const issue = (_payload as Payload).issue; + if (!issue) { logger.info(`Skipping '/start' because of no issue instance`); return "Skipping '/start' because of no issue instance"; @@ -109,6 +112,8 @@ export const assign = async (body: string) => { await addAssignees(issue.number, [payload.sender.login]); } + const isBountyStale = new Date().getTime() - new Date(issue.created_at).getTime() > staleBounty; + // 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); @@ -116,7 +121,7 @@ export const assign = async (body: string) => { 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); - return tableComment({ ...comment, multiplier, reason, bounty }) + comment.tips; + return tableComment({ ...comment, multiplier, reason, bounty, isBountyStale }) + comment.tips; } return; }; diff --git a/src/handlers/comment/handlers/table.ts b/src/handlers/comment/handlers/table.ts index 13bed9e88..c8d025d80 100644 --- a/src/handlers/comment/handlers/table.ts +++ b/src/handlers/comment/handlers/table.ts @@ -4,20 +4,21 @@ export const tableComment = ({ multiplier, reason, bounty, + isBountyStale, }: { deadline: string; wallet: string; multiplier?: string; reason?: string; bounty?: string; + isBountyStale?: boolean; }) => { return ` - - - + + ${!isBountyStale ? `` : ``} diff --git a/src/types/config.ts b/src/types/config.ts index 97d844ab6..373262377 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -69,6 +69,7 @@ export const ModeSchema = Type.Object({ export const AssignSchema = Type.Object({ bountyHunterMax: Type.Number(), + staleBountyTime: Type.Number(), }); export const LogConfigSchema = Type.Object({ From 517a3e526d63609faab70b96bb39d78a148b192f Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Sat, 26 Aug 2023 23:09:39 +0100 Subject: [PATCH 014/106] fix: requested changes made fix config and warning --- src/bindings/config.ts | 4 ++-- src/configs/shared.ts | 1 + src/handlers/comment/handlers/assign.ts | 4 +++- src/handlers/comment/handlers/table.ts | 4 +++- src/utils/private.ts | 1 + ubiquibot-config-default.json | 21 +++++++++++++-------- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/bindings/config.ts b/src/bindings/config.ts index f918f60e9..83718e1a1 100644 --- a/src/bindings/config.ts +++ b/src/bindings/config.ts @@ -1,7 +1,7 @@ import ms from "ms"; import { BotConfig, BotConfigSchema } from "../types"; -import { DEFAULT_BOT_DELAY, DEFAULT_DISQUALIFY_TIME, DEFAULT_FOLLOWUP_TIME, DEFAULT_PERMIT_BASE_URL } from "../configs"; +import { DEFAULT_BOT_DELAY, DEFAULT_DISQUALIFY_TIME, DEFAULT_FOLLOWUP_TIME, DEFAULT_PERMIT_BASE_URL, STALE_BOUNTY_TIME } from "../configs"; import { getPayoutConfigByNetworkId } from "../helpers"; import { ajv } from "../utils"; import { Context } from "probot"; @@ -76,7 +76,7 @@ export const loadConfig = async (context: Context): Promise => { command: commandSettings, assign: { bountyHunterMax: bountyHunterMax, - staleBountyTime: ms(process.env.STALE_BOUNTY_TIME || "30d"), + staleBountyTime: ms(STALE_BOUNTY_TIME), }, sodium: { privateKey: process.env.X25519_PRIVATE_KEY ?? "", diff --git a/src/configs/shared.ts b/src/configs/shared.ts index 2d817b7a0..25af7325c 100644 --- a/src/configs/shared.ts +++ b/src/configs/shared.ts @@ -22,6 +22,7 @@ export const ASSIGN_COMMAND_ENABLED = true; */ export const DEFAULT_FOLLOWUP_TIME = "4 days"; // 4 days export const DEFAULT_DISQUALIFY_TIME = "7 days"; // 7 days +export const STALE_BOUNTY_TIME = "30d"; // 2 minutes export const DEFAULT_NETWORK_ID = 1; // ethereum export const DEFAULT_RPC_ENDPOINT = "https://rpc-bot.ubq.fi/v1/mainnet"; diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index f08501929..04f295010 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -7,6 +7,7 @@ import { tableComment } from "./table"; import { bountyInfo } from "../../wildcard"; import { ASSIGN_COMMAND_ENABLED, GLOBAL_STRINGS } from "../../../configs"; import { isParentIssue } from "../../pricing"; +import ms from "ms"; export const assign = async (body: string) => { const { payload: _payload } = getBotContext(); @@ -113,6 +114,7 @@ export const assign = async (body: string) => { } const isBountyStale = new Date().getTime() - new Date(issue.created_at).getTime() > staleBounty; + const days = Math.floor((new Date().getTime() - new Date(issue.created_at).getTime()) / (1000 * 60 * 60 * 24)); // double check whether the assign message has been already posted or not logger.info(`Creating an issue comment: ${comment.commit}`); @@ -121,7 +123,7 @@ export const assign = async (body: string) => { 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); - return tableComment({ ...comment, multiplier, reason, bounty, isBountyStale }) + comment.tips; + return tableComment({ ...comment, multiplier, reason, bounty, isBountyStale, days }) + comment.tips; } return; }; diff --git a/src/handlers/comment/handlers/table.ts b/src/handlers/comment/handlers/table.ts index c8d025d80..a84517589 100644 --- a/src/handlers/comment/handlers/table.ts +++ b/src/handlers/comment/handlers/table.ts @@ -5,6 +5,7 @@ export const tableComment = ({ reason, bounty, isBountyStale, + days, }: { deadline: string; wallet: string; @@ -12,13 +13,14 @@ export const tableComment = ({ reason?: string; bounty?: string; isBountyStale?: boolean; + days?: number; }) => { return `
Ready to begin?You can start right away!Please wait for confirmation first.
Deadline
- ${!isBountyStale ? `` : ``} + ${!isBountyStale ? `` : ``} diff --git a/src/utils/private.ts b/src/utils/private.ts index 1b6147b0a..2e01d61a9 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -21,6 +21,7 @@ import { } from "./helpers"; import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json"; +import ms from "ms"; const CONFIG_REPO = "ubiquibot-config"; const CONFIG_PATH = ".github/ubiquibot-config.yml"; diff --git a/ubiquibot-config-default.json b/ubiquibot-config-default.json index b859c45f0..98c119890 100644 --- a/ubiquibot-config-default.json +++ b/ubiquibot-config-default.json @@ -62,35 +62,40 @@ "command-settings": [ { "name": "start", - "enabled": false + "enabled": true }, { "name": "stop", - "enabled": false + "enabled": true }, { "name": "wallet", - "enabled": false + "enabled": true }, { "name": "payout", - "enabled": false + "enabled": true }, { "name": "multiplier", - "enabled": false + "enabled": true }, { "name": "query", - "enabled": false + "enabled": true }, { "name": "allow", - "enabled": false + "enabled": true }, { "name": "autopay", - "enabled": false + "enabled": true + }, + { + "name": "assign", + "enabled": true, + "staleBountyTime": 120000 } ], "incentives": { From 5a38700325b6fc3a76bc9a5a8c5ae34369b9b053 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Sat, 26 Aug 2023 23:32:32 +0100 Subject: [PATCH 015/106] fix: pulls from default uses default json first unless 0 then uses 30d --- src/bindings/config.ts | 3 ++- src/configs/shared.ts | 2 +- src/handlers/comment/handlers/assign.ts | 3 ++- src/utils/private.ts | 3 +++ ubiquibot-config-default.json | 6 +----- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/bindings/config.ts b/src/bindings/config.ts index 83718e1a1..66cf7e64e 100644 --- a/src/bindings/config.ts +++ b/src/bindings/config.ts @@ -26,6 +26,7 @@ export const loadConfig = async (context: Context): Promise => { commandSettings, assistivePricing, registerWalletWithVerification, + staleBountyTime, } = await getWideConfig(context); const publicKey = await getScalarKey(process.env.X25519_PRIVATE_KEY); @@ -76,7 +77,7 @@ export const loadConfig = async (context: Context): Promise => { command: commandSettings, assign: { bountyHunterMax: bountyHunterMax, - staleBountyTime: ms(STALE_BOUNTY_TIME), + staleBountyTime: staleBountyTime ? staleBountyTime : ms(STALE_BOUNTY_TIME), }, sodium: { privateKey: process.env.X25519_PRIVATE_KEY ?? "", diff --git a/src/configs/shared.ts b/src/configs/shared.ts index 25af7325c..b90b587ff 100644 --- a/src/configs/shared.ts +++ b/src/configs/shared.ts @@ -22,7 +22,7 @@ export const ASSIGN_COMMAND_ENABLED = true; */ export const DEFAULT_FOLLOWUP_TIME = "4 days"; // 4 days export const DEFAULT_DISQUALIFY_TIME = "7 days"; // 7 days -export const STALE_BOUNTY_TIME = "30d"; // 2 minutes +export const STALE_BOUNTY_TIME = "30d"; // 30 days export const DEFAULT_NETWORK_ID = 1; // ethereum export const DEFAULT_RPC_ENDPOINT = "https://rpc-bot.ubq.fi/v1/mainnet"; diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index 04f295010..20be74f7d 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -7,7 +7,6 @@ import { tableComment } from "./table"; import { bountyInfo } from "../../wildcard"; import { ASSIGN_COMMAND_ENABLED, GLOBAL_STRINGS } from "../../../configs"; import { isParentIssue } from "../../pricing"; -import ms from "ms"; export const assign = async (body: string) => { const { payload: _payload } = getBotContext(); @@ -21,6 +20,8 @@ export const assign = async (body: string) => { const staleBounty = config.assign.staleBountyTime; + console.log("staleBounty", staleBounty); + logger.info(`Received '/start' command from user: ${payload.sender.login}, body: ${body}`); const issue = (_payload as Payload).issue; diff --git a/src/utils/private.ts b/src/utils/private.ts index 2e01d61a9..d10ac378e 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -21,6 +21,7 @@ import { } from "./helpers"; import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json"; +import { STALE_BOUNTY_TIME } from "../configs"; import ms from "ms"; const CONFIG_REPO = "ubiquibot-config"; @@ -83,6 +84,7 @@ export interface WideConfig { "comment-incentives"?: boolean; "assistive-pricing"?: boolean; "max-concurrent-assigns"?: number; + "stale-bounty-time"?: string; incentives?: Incentives; "default-labels"?: string[]; "register-wallet-with-verification"?: boolean; @@ -185,6 +187,7 @@ export const getWideConfig = async (context: Context) => { defaultLabels: getDefaultLabels(configs), promotionComment: getPromotionComment(configs), registerWalletWithVerification: getRegisterWalletWithVerification(configs), + staleBountyTime: parsedDefault["stale-bounty-time"] == "0" ? ms(STALE_BOUNTY_TIME) : parsedDefault["stale-bounty-time"], }; return configData; diff --git a/ubiquibot-config-default.json b/ubiquibot-config-default.json index 98c119890..5271a5d7f 100644 --- a/ubiquibot-config-default.json +++ b/ubiquibot-config-default.json @@ -8,6 +8,7 @@ "disable-analytics": false, "comment-incentives": false, "register-wallet-with-verification": false, + "stale-bounty-time": 42060, "promotion-comment": "\n
If you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot!
", "default-labels": [], "time-labels": [ @@ -91,11 +92,6 @@ { "name": "autopay", "enabled": true - }, - { - "name": "assign", - "enabled": true, - "staleBountyTime": 120000 } ], "incentives": { From 5a41966d6ca92f9f7b0f8226098d0c904d80188a Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Sat, 26 Aug 2023 23:36:51 +0100 Subject: [PATCH 016/106] fix: remove console log remove console log and adjust warning --- src/handlers/comment/handlers/assign.ts | 2 -- src/handlers/comment/handlers/table.ts | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index 20be74f7d..cbb7df465 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -20,8 +20,6 @@ export const assign = async (body: string) => { const staleBounty = config.assign.staleBountyTime; - console.log("staleBounty", staleBounty); - logger.info(`Received '/start' command from user: ${payload.sender.login}, body: ${body}`); const issue = (_payload as Payload).issue; diff --git a/src/handlers/comment/handlers/table.ts b/src/handlers/comment/handlers/table.ts index a84517589..bc2349cdf 100644 --- a/src/handlers/comment/handlers/table.ts +++ b/src/handlers/comment/handlers/table.ts @@ -20,7 +20,11 @@ export const tableComment = ({
Ready to begin?You can start right away!Please wait for confirmation first.You can start right away!This task was created over ${days} days ago. Please verify that it is still current.
Deadline
- ${!isBountyStale ? `` : ``} + ${ + !isBountyStale + ? `` + : `` + } From 2e667f89e64446fe999f4057549f164ee2c233cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= <4975670+pavlovcik@users.noreply.github.com> Date: Sun, 27 Aug 2023 14:48:25 +0900 Subject: [PATCH 017/106] Update ubiquibot-config.yml > One thing that comes to mind, could be wrong, but the main deployment of ubiquibot is behind, but the config it is reading is with new priority and times labels is ahead. The main deployment doesn't understand these new configs as the code that handles it is not yet present at the main deployment. Source https://t.me/c/1588400061/3250/4399 --- .github/ubiquibot-config.yml | 48 +----------------------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/.github/ubiquibot-config.yml b/.github/ubiquibot-config.yml index 9eed56284..609d52ba1 100644 --- a/.github/ubiquibot-config.yml +++ b/.github/ubiquibot-config.yml @@ -1,50 +1,4 @@ -evm-network-id: 100 price-multiplier: 1.5 -issue-creator-multiplier: 2 -time-labels: - - name: "Time: <1 Hour" - weight: 0.125 - value: 3600 - - name: "Time: <2 Hours" - weight: 0.25 - value: 7200 - - name: "Time: <4 Hours" - weight: 0.5 - value: 14400 - - name: "Time: <1 Day" - weight: 1 - value: 86400 - - name: "Time: <1 Week" - weight: 2 - value: 604800 -priority-labels: - - name: "Priority: 0 (Normal)" - weight: 1 - - name: "Priority: 1 (Medium)" - weight: 2 - - name: "Priority: 2 (High)" - weight: 3 - - name: "Priority: 3 (Urgent)" - weight: 4 - - name: "Priority: 4 (Emergency)" - weight: 5 default-labels: - "Time: <1 Hour" - - "Priority: 0 (Normal)" -payment-permit-max-price: 1000 -comment-incentives: true -max-concurrent-bounties: 2 -promotion-comment: "
If you've enjoyed your experience in the DevPool, we'd appreciate your support. Follow Ubiquity on GitHub and star this repo. Your endorsement means the world to us and helps us grow!
We are excited to announce that the DevPool and UbiquiBot are now available to partners! Our ideal collaborators are globally distributed crypto-native organizations, who actively work on open source on GitHub, and excel in research & development. If you can introduce us to the repository maintainers in these types of companies, we have a special bonus in store for you!
" -incentives: - comment: - elements: - code: 5 - img: 5 - h1: 1 - li: 0.5 - a: 0.5 - blockquote: 0 - totals: - word: 0.1 -register-wallet-with-verification: false -assistive-pricing: true + - "Priority: 1 (Normal)" From dda1cbb15efffba1b46cc12e4d89d299226d485e Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sun, 27 Aug 2023 16:09:37 +0200 Subject: [PATCH 018/106] fix: getBotConfig instead of loadConfig --- src/helpers/issue.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index 00ccd0e93..abaa9e73a 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -1,5 +1,5 @@ import { Context } from "probot"; -import { getBotContext, getLogger, loadConfig } from "../bindings"; +import { getBotConfig, getBotContext, getLogger } from "../bindings"; import { AssignEvent, Comment, IssueType, Payload } from "../types"; import { checkRateLimitGit } from "../utils"; @@ -641,8 +641,10 @@ export const getCommitsOnPullRequest = async (pullNumber: number) => { export const getAvailableOpenedPullRequests = async (username: string) => { const context = getBotContext(); - const botConfig = await loadConfig(context); - if (!botConfig.unassign.timeRangeForMaxIssueEnabled) return []; + const { + unassign: { timeRangeForMaxIssue, timeRangeForMaxIssueEnabled }, + } = await getBotConfig(); + if (!timeRangeForMaxIssueEnabled) return []; const opened_prs = await getOpenedPullRequests(username); @@ -657,7 +659,7 @@ export const getAvailableOpenedPullRequests = async (username: string) => { if (approvedReviews) result.push(pr); } - if (reviews.length === 0 && (new Date().getTime() - new Date(pr.created_at).getTime()) / (1000 * 60 * 60) >= botConfig.unassign.timeRangeForMaxIssue) { + if (reviews.length === 0 && (new Date().getTime() - new Date(pr.created_at).getTime()) / (1000 * 60 * 60) >= timeRangeForMaxIssue) { result.push(pr); } } From bdb691768bc2c5a78c7ea145c70f4d37c6d95e0c Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sun, 27 Aug 2023 16:13:56 +0200 Subject: [PATCH 019/106] feat: log config errors --- src/bindings/event.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/bindings/event.ts b/src/bindings/event.ts index 3ea42f287..3422cb526 100644 --- a/src/bindings/event.ts +++ b/src/bindings/event.ts @@ -34,7 +34,12 @@ export const bindEvents = async (context: Context): Promise => { botContext = context; const payload = context.payload as Payload; - botConfig = await loadConfig(context); + let botConfigError; + try { + botConfig = await loadConfig(context); + } catch (err) { + botConfigError = err; + } adapters = createAdapters(botConfig); @@ -48,6 +53,19 @@ export const bindEvents = async (context: Context): Promise => { return; } + if (botConfigError) { + logger.error( + `Error loading config: ${botConfigError}. Config: ${JSON.stringify({ + price: botConfig.price, + unassign: botConfig.unassign, + mode: botConfig.mode, + log: botConfig.log, + wallet: botConfig.wallet, + })}` + ); + return; + } + // Create adapters for telegram, supabase, twitter, discord, etc logger.info("Creating adapters for supabase, telegram, twitter, etc..."); From 0aecb8593d8ccba585d6f51dfa791fe40a670186 Mon Sep 17 00:00:00 2001 From: Steveantor <109317559+Steveantor@users.noreply.github.com> Date: Sun, 27 Aug 2023 17:15:51 +0000 Subject: [PATCH 020/106] feat: logs cleaner --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index e846f7d3c..4b38ed11d 100644 --- a/README.md +++ b/README.md @@ -239,3 +239,27 @@ We can't use a `jsonc` file due to limitations with Netlify. Here is a snippet o } } ``` + +## Supabase Cron Job (`logs-cleaner`) + +##### Dashboard > Project > Database > Extensions + +> Search `PG_CRON` and Enable it. + + +##### Dashboard > Project > SQL Editor + +```sql +-- Runs everyday at 03:00 AM to cleanup logs that are older than a week +-- Use the cron time format to modify the trigger time if necessary +select + cron.schedule ( + 'logs-cleaner', -- Job name + '0 3 * * *', -- Everyday at 03:00 AM + $$DELETE FROM logs WHERE timestamp < now() - INTERVAL '1 week'$$ + ); + + +-- Cancel the cron job +select cron.unschedule('logs-cleaner'); +``` From 01fb81791edbb8dc23e450487b99b8376f2a7c28 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Mon, 28 Aug 2023 08:22:25 +0200 Subject: [PATCH 021/106] feat: validate config on push event --- package.json | 2 +- src/handlers/processors.ts | 4 +- src/handlers/push/index.ts | 52 ++++++++++++++++++++++ src/helpers/file.ts | 52 ++++++++++++++++++++++ src/utils/private.ts | 89 +++++++++++++++++++++----------------- yarn.lock | 31 +++++++------ 6 files changed, 174 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index e64d64e99..a839a4e3b 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@netlify/functions": "^1.4.0", "@probot/adapter-aws-lambda-serverless": "^3.0.2", "@probot/adapter-github-actions": "^3.1.3", - "@sinclair/typebox": "^0.25.9", + "@sinclair/typebox": "^0.31.5", "@supabase/supabase-js": "^2.4.0", "@types/ms": "^0.7.31", "@typescript-eslint/eslint-plugin": "^5.59.11", diff --git a/src/handlers/processors.ts b/src/handlers/processors.ts index 16f89a535..745d45e45 100644 --- a/src/handlers/processors.ts +++ b/src/handlers/processors.ts @@ -6,7 +6,7 @@ import { nullHandler } from "./shared"; import { handleComment, issueClosedCallback, issueReopenedCallback } from "./comment"; import { checkPullRequests } from "./assign/auto"; import { createDevPoolPR } from "./pull-request"; -import { runOnPush } from "./push"; +import { runOnPush, validateConfigChange } from "./push"; import { incentivizeComments, incentivizeCreatorComment } from "./payout"; import { findDuplicateOne } from "./issue"; @@ -68,7 +68,7 @@ export const processors: Record = { }, [GithubEvent.PUSH_EVENT]: { pre: [nullHandler], - action: [runOnPush], + action: [validateConfigChange, runOnPush], post: [nullHandler], }, }; diff --git a/src/handlers/push/index.ts b/src/handlers/push/index.ts index 4cd4775ae..6fb269859 100644 --- a/src/handlers/push/index.ts +++ b/src/handlers/push/index.ts @@ -1,6 +1,10 @@ import { getBotContext, getLogger } from "../../bindings"; +import { getFileContent } from "../../helpers"; import { CommitsPayload, PushPayload } from "../../types"; +import { ajv } from "../../utils"; +import { parseYAML } from "../../utils/private"; import { updateBaseRate } from "./update-base"; +import { WideOrgConfigSchema } from "../../utils/private"; const ZERO_SHA = "0000000000000000000000000000000000000000"; const BASE_RATE_FILE = ".github/ubiquibot-config.yml"; @@ -45,3 +49,51 @@ export const runOnPush = async () => { await updateBaseRate(context, payload, BASE_RATE_FILE); } }; + +export const validateConfigChange = async () => { + const logger = getLogger(); + + const context = getBotContext(); + const payload = context.payload as PushPayload; + + if (!payload.ref.startsWith("refs/heads/")) { + logger.debug("Skipping push events, not a branch"); + return; + } + + const changes = getCommitChanges(payload.commits); + + // skip if empty + if (changes && changes.length === 0) { + logger.debug("Skipping push events, file change empty"); + return; + } + + // check for modified or added files and check for specified file + if (changes.includes(BASE_RATE_FILE)) { + const latestCommitSha = payload.after; + const configFileContent = await getFileContent( + payload.repository.owner.login, + payload.repository.name, + payload.ref.split("refs/heads/")[1], + BASE_RATE_FILE, + latestCommitSha + ); + if (configFileContent) { + const decodedConfig = Buffer.from(configFileContent, "base64").toString(); + const config = parseYAML(decodedConfig); + const valid = ajv.validate(WideOrgConfigSchema, config); // additionalProperties: false is required to prevent unknown properties from being allowed + if (!valid) { + // post commit comment + const commitSha = payload.commits.filter((commit) => commit.modified.includes(BASE_RATE_FILE) || commit.added.includes(BASE_RATE_FILE))[0]?.id; + await context.octokit.rest.repos.createCommitComment({ + owner: payload.repository.owner.login, + repo: payload.repository.name, + commit_sha: commitSha, + body: `@${payload.sender.login} Config validation failed! Error: ${ajv.errorsText()}`, + path: BASE_RATE_FILE, + }); + } + } + } +}; diff --git a/src/helpers/file.ts b/src/helpers/file.ts index f32a64e31..b21905098 100644 --- a/src/helpers/file.ts +++ b/src/helpers/file.ts @@ -59,3 +59,55 @@ export async function getPreviousFileContent(owner: string, repo: string, branch return ""; } } + +export async function getFileContent(owner: string, repo: string, branch: string, filePath: string, commitSha?: string): Promise { + const logger = getLogger(); + const context = getBotContext(); + + try { + if (!commitSha) { + // Get the latest commit of the branch + const branchData = await context.octokit.repos.getBranch({ + owner, + repo, + branch, + }); + commitSha = branchData.data.commit.sha; + } + + // Get the commit details + const commitData = await context.octokit.repos.getCommit({ + owner, + repo, + ref: commitSha, + }); + + // Find the file in the commit tree + const file = commitData.data.files ? commitData.data.files.find((file) => file.filename === filePath) : undefined; + if (file) { + // Retrieve the file tree + const tree = await context.octokit.git.getTree({ + owner, + repo, + tree_sha: commitData.data.commit.tree.sha, + recursive: "true", + }); + + // Find the previous file content in the tree + const file = tree.data.tree.find((item) => item.path === filePath); + if (file && file.sha) { + // Get the previous file content + const fileContent = await context.octokit.git.getBlob({ + owner, + repo, + file_sha: file.sha, + }); + return fileContent.data.content; + } + } + return null; + } catch (error: unknown) { + logger.debug(`Error retrieving previous file content. error: ${error}`); + return null; + } +} diff --git a/src/utils/private.ts b/src/utils/private.ts index 36ff92f8c..2ef5e0073 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -21,6 +21,7 @@ import { } from "./helpers"; import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json"; +import { Static, Type } from "@sinclair/typebox"; const CONFIG_REPO = "ubiquibot-config"; const CONFIG_PATH = ".github/ubiquibot-config.yml"; @@ -47,49 +48,57 @@ export const getConfigSuperset = async (context: Context, type: "org" | "repo", } }; -export interface WideLabel { - name: string; -} - -export interface CommentIncentives { - elements: Record; - totals: { - word: number; - }; -} - -export interface Incentives { - comment: CommentIncentives; -} - -export interface CommandObj { - name: string; - enabled: boolean; -} - -export interface WideConfig { - "evm-network-id"?: number; - "price-multiplier"?: number; - "issue-creator-multiplier": number; - "time-labels"?: WideLabel[]; - "priority-labels"?: WideLabel[]; - "payment-permit-max-price"?: number; - "command-settings"?: CommandObj[]; - "promotion-comment"?: string; - "disable-analytics"?: boolean; - "comment-incentives"?: boolean; - "assistive-pricing"?: boolean; - "max-concurrent-assigns"?: number; - incentives?: Incentives; - "default-labels"?: string[]; - "register-wallet-with-verification"?: boolean; -} +const WideLabelSchema = Type.Object({ + name: Type.String(), +}); + +export type WideLabel = Static; + +const CommandObjSchema = Type.Object({ + name: Type.String(), + enabled: Type.Boolean(), +}); + +export type CommandObj = Static; + +const IncentivesSchema = Type.Object({ + comment: Type.Object({ + elements: Type.Record(Type.String(), Type.Number()), + totals: Type.Object({ + word: Type.Number(), + }), + }), +}); + +export type Incentives = Static; + +export const WideConfigSchema = Type.Object({ + "evm-network-id": Type.Optional(Type.Number()), + "price-multiplier": Type.Optional(Type.Number()), + "issue-creator-multiplier": Type.Number(), + "time-labels": Type.Optional(Type.Array(WideLabelSchema)), + "priority-labels": Type.Optional(Type.Array(WideLabelSchema)), + "payment-permit-max-price": Type.Optional(Type.Number()), + "command-settings": Type.Optional(Type.Array(CommandObjSchema)), + "promotion-comment": Type.Optional(Type.String()), + "disable-analytics": Type.Optional(Type.Boolean()), + "comment-incentives": Type.Optional(Type.Boolean()), + "assistive-pricing": Type.Optional(Type.Boolean()), + "max-concurrent-assigns": Type.Optional(Type.Number()), + incentives: Type.Optional(IncentivesSchema), + "default-labels": Type.Optional(Type.Array(Type.String())), + "register-wallet-with-verification": Type.Optional(Type.Boolean()), +}); + +export type WideConfig = Static; export type WideRepoConfig = WideConfig; -export interface WideOrgConfig extends WideConfig { - "private-key-encrypted"?: string; -} +export const WideOrgConfigSchema = Type.Composite([Type.Object({ "private-key-encrypted": Type.Optional(Type.String()) }), WideConfigSchema], { + additionalProperties: false, +}); + +export type WideOrgConfig = Static; export const parseYAML = (data?: string): WideConfig | undefined => { try { diff --git a/yarn.lock b/yarn.lock index 2d930cf18..60ee62730 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1928,10 +1928,10 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== -"@sinclair/typebox@^0.25.9": - version "0.25.24" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" - integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== +"@sinclair/typebox@^0.31.5": + version "0.31.5" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.31.5.tgz#10ae6c60fc523d7d695a730df1ac3dd9725ce207" + integrity sha512-4fbqH1ONle98ULTQakJFVNwGwSx+rv90HEnjZGt1GoApMKooUw1WXw3ub+Ew7rInmyDcwsjIxiHt39bkWzeCBA== "@sinonjs/commons@^1.7.0": version "1.8.6" @@ -1968,10 +1968,10 @@ dependencies: cross-fetch "^3.1.5" -"@supabase/realtime-js@^2.7.3": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@supabase/realtime-js/-/realtime-js-2.7.3.tgz#cbcb84181add681ab99c87032bfe88101c6863b3" - integrity sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw== +"@supabase/realtime-js@^2.7.4": + version "2.7.4" + resolved "https://registry.yarnpkg.com/@supabase/realtime-js/-/realtime-js-2.7.4.tgz#de41195bd3f2cdd6db82d9f93c4c5b8fae9f809b" + integrity sha512-FzSzs1k9ruh/uds5AJ95Nc3beiMCCIhougExJ3O98CX1LMLAKUKFy5FivKLvcNhXnNfUEL0XUfGMb4UH2J7alg== dependencies: "@types/phoenix" "^1.5.4" "@types/websocket" "^1.0.3" @@ -1984,15 +1984,15 @@ dependencies: cross-fetch "^3.1.5" -"@supabase/supabase-js@^2.32.0": - version "2.32.0" - resolved "https://registry.yarnpkg.com/@supabase/supabase-js/-/supabase-js-2.32.0.tgz#863c636d83232c6a2e9ba5932e0d7c1bf80bc436" - integrity sha512-1ShFhuOI5Du7604nlCelBsRD61daXk2O0qwjumoz35bqrYThnSPPtpJqZOHw6Mg6o7mLjIInYLh/DBlh8UvzRg== +"@supabase/supabase-js@^2.4.0": + version "2.33.1" + resolved "https://registry.yarnpkg.com/@supabase/supabase-js/-/supabase-js-2.33.1.tgz#2407861afe63c2817d030514c87a745f78dfe68a" + integrity sha512-jA00rquPTppPOHpBB6KABW98lfg0gYXcuGqP3TB1iiduznRVsi3GGk2qBKXPDLMYSe0kRlQp5xCwWWthaJr8eA== dependencies: "@supabase/functions-js" "^2.1.0" "@supabase/gotrue-js" "^2.46.1" "@supabase/postgrest-js" "^1.8.0" - "@supabase/realtime-js" "^2.7.3" + "@supabase/realtime-js" "^2.7.4" "@supabase/storage-js" "^2.5.1" cross-fetch "^3.1.5" @@ -4189,6 +4189,11 @@ expect@^28.0.0: jest-message-util "^28.1.3" jest-util "^28.1.3" +exponential-backoff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" + integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== + express-handlebars@^6.0.3: version "6.0.7" resolved "https://registry.yarnpkg.com/express-handlebars/-/express-handlebars-6.0.7.tgz#f779254664eff0e250362ef1c2b30587059c212a" From 7be47680ff5044e166596bfb471babe5e5bad9cd Mon Sep 17 00:00:00 2001 From: whilefoo Date: Mon, 28 Aug 2023 10:07:04 +0200 Subject: [PATCH 022/106] feat: find correct commit hash --- src/handlers/push/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/handlers/push/index.ts b/src/handlers/push/index.ts index 6fb269859..63c6d5516 100644 --- a/src/handlers/push/index.ts +++ b/src/handlers/push/index.ts @@ -71,21 +71,22 @@ export const validateConfigChange = async () => { // check for modified or added files and check for specified file if (changes.includes(BASE_RATE_FILE)) { - const latestCommitSha = payload.after; + const commitSha = payload.commits.filter((commit) => commit.modified.includes(BASE_RATE_FILE) || commit.added.includes(BASE_RATE_FILE)).reverse()[0]?.id; + const configFileContent = await getFileContent( payload.repository.owner.login, payload.repository.name, payload.ref.split("refs/heads/")[1], BASE_RATE_FILE, - latestCommitSha + commitSha ); + if (configFileContent) { const decodedConfig = Buffer.from(configFileContent, "base64").toString(); const config = parseYAML(decodedConfig); const valid = ajv.validate(WideOrgConfigSchema, config); // additionalProperties: false is required to prevent unknown properties from being allowed if (!valid) { // post commit comment - const commitSha = payload.commits.filter((commit) => commit.modified.includes(BASE_RATE_FILE) || commit.added.includes(BASE_RATE_FILE))[0]?.id; await context.octokit.rest.repos.createCommitComment({ owner: payload.repository.owner.login, repo: payload.repository.name, From a6cf47efee00caa7ce82907f29796f68dab16653 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Mon, 28 Aug 2023 10:54:10 +0200 Subject: [PATCH 023/106] feat: log invalid config --- src/adapters/index.ts | 4 ++-- src/bindings/event.ts | 19 ++++++++---------- src/utils/private.ts | 46 +++++++++++++++++++++++++++---------------- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/adapters/index.ts b/src/adapters/index.ts index c5d85b7eb..5279a354a 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -6,7 +6,7 @@ export * from "./telegram"; export const createAdapters = (config: BotConfig): Adapters => { return { - supabase: supabase(config.supabase.url, config.supabase.key), - telegram: new Telegraf(config.telegram.token).telegram, + supabase: supabase(config?.supabase?.url ?? process.env.SUPABASE_URL, config?.supabase?.key ?? process.env.SUPABASE_KEY), + telegram: new Telegraf(config?.telegram?.token ?? process.env.TELEGRAM_BOT_TOKEN).telegram, }; }; diff --git a/src/bindings/event.ts b/src/bindings/event.ts index 3422cb526..616580a1a 100644 --- a/src/bindings/event.ts +++ b/src/bindings/event.ts @@ -6,7 +6,7 @@ import { BotConfig, GithubEvent, Payload, PayloadSchema } from "../types"; import { Adapters } from "../types/adapters"; import { ajv } from "../utils"; import { loadConfig } from "./config"; -import { GitHubLogger } from "../adapters/supabase"; +import { GitHubLogger, Level } from "../adapters/supabase"; let botContext: Context = {} as Context; export const getBotContext = () => botContext; @@ -48,21 +48,18 @@ export const bindEvents = async (context: Context): Promise => { // level: botConfig.log.level, }; - logger = new GitHubLogger(options.app, botConfig.log.logEnvironment, botConfig.log.level, botConfig.log.retryLimit); // contributors will see logs in console while on development env + logger = new GitHubLogger( + options.app, + botConfig?.log?.logEnvironment ?? "development", + botConfig?.log?.level ?? Level.DEBUG, + botConfig?.log?.retryLimit ?? 0 + ); // contributors will see logs in console while on development env if (!logger) { return; } if (botConfigError) { - logger.error( - `Error loading config: ${botConfigError}. Config: ${JSON.stringify({ - price: botConfig.price, - unassign: botConfig.unassign, - mode: botConfig.mode, - log: botConfig.log, - wallet: botConfig.wallet, - })}` - ); + logger.error(botConfigError); return; } diff --git a/src/utils/private.ts b/src/utils/private.ts index 2ef5e0073..ca4175058 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -22,6 +22,7 @@ import { import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json"; import { Static, Type } from "@sinclair/typebox"; +import { ajv } from "./ajv"; const CONFIG_REPO = "ubiquibot-config"; const CONFIG_PATH = ".github/ubiquibot-config.yml"; @@ -72,23 +73,28 @@ const IncentivesSchema = Type.Object({ export type Incentives = Static; -export const WideConfigSchema = Type.Object({ - "evm-network-id": Type.Optional(Type.Number()), - "price-multiplier": Type.Optional(Type.Number()), - "issue-creator-multiplier": Type.Number(), - "time-labels": Type.Optional(Type.Array(WideLabelSchema)), - "priority-labels": Type.Optional(Type.Array(WideLabelSchema)), - "payment-permit-max-price": Type.Optional(Type.Number()), - "command-settings": Type.Optional(Type.Array(CommandObjSchema)), - "promotion-comment": Type.Optional(Type.String()), - "disable-analytics": Type.Optional(Type.Boolean()), - "comment-incentives": Type.Optional(Type.Boolean()), - "assistive-pricing": Type.Optional(Type.Boolean()), - "max-concurrent-assigns": Type.Optional(Type.Number()), - incentives: Type.Optional(IncentivesSchema), - "default-labels": Type.Optional(Type.Array(Type.String())), - "register-wallet-with-verification": Type.Optional(Type.Boolean()), -}); +export const WideConfigSchema = Type.Object( + { + "evm-network-id": Type.Optional(Type.Number()), + "price-multiplier": Type.Optional(Type.Number()), + "issue-creator-multiplier": Type.Number(), + "time-labels": Type.Optional(Type.Array(WideLabelSchema)), + "priority-labels": Type.Optional(Type.Array(WideLabelSchema)), + "payment-permit-max-price": Type.Optional(Type.Number()), + "command-settings": Type.Optional(Type.Array(CommandObjSchema)), + "promotion-comment": Type.Optional(Type.String()), + "disable-analytics": Type.Optional(Type.Boolean()), + "comment-incentives": Type.Optional(Type.Boolean()), + "assistive-pricing": Type.Optional(Type.Boolean()), + "max-concurrent-assigns": Type.Optional(Type.Number()), + incentives: Type.Optional(IncentivesSchema), + "default-labels": Type.Optional(Type.Array(Type.String())), + "register-wallet-with-verification": Type.Optional(Type.Boolean()), + }, + { + additionalProperties: false, + } +); export type WideConfig = Static; @@ -169,7 +175,13 @@ export const getWideConfig = async (context: Context) => { const repoConfig = await getConfigSuperset(context, "repo", CONFIG_PATH); const parsedOrg: WideOrgConfig | undefined = parseYAML(orgConfig); + if (parsedOrg !== undefined && !ajv.validate(WideOrgConfigSchema, parsedOrg)) { + throw new Error(`Invalid org config: ${ajv.errorsText()}`); + } const parsedRepo: WideRepoConfig | undefined = parseYAML(repoConfig); + if (parsedRepo !== undefined && !ajv.validate(WideConfigSchema, parsedRepo)) { + throw new Error(`Invalid repo config: ${ajv.errorsText()}`); + } const parsedDefault: WideRepoConfig = DEFAULT_CONFIG_JSON; const privateKeyDecrypted = parsedOrg && parsedOrg[KEY_NAME] ? await getPrivateKey(parsedOrg[KEY_NAME]) : undefined; From 5a61607972014c522aa37d740b542a90d426c338 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Mon, 28 Aug 2023 12:14:58 +0200 Subject: [PATCH 024/106] feat: list unnecessary properties --- src/handlers/push/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/handlers/push/index.ts b/src/handlers/push/index.ts index 63c6d5516..532f7961a 100644 --- a/src/handlers/push/index.ts +++ b/src/handlers/push/index.ts @@ -87,11 +87,18 @@ export const validateConfigChange = async () => { const valid = ajv.validate(WideOrgConfigSchema, config); // additionalProperties: false is required to prevent unknown properties from being allowed if (!valid) { // post commit comment + const additionalProperties = ajv.errors?.map((error) => { + if (error.keyword === "additionalProperties") { + return error.params.additionalProperty; + } + }); await context.octokit.rest.repos.createCommitComment({ owner: payload.repository.owner.login, repo: payload.repository.name, commit_sha: commitSha, - body: `@${payload.sender.login} Config validation failed! Error: ${ajv.errorsText()}`, + body: `@${payload.sender.login} Config validation failed! Error: ${ajv.errorsText()}. ${ + additionalProperties && additionalProperties.length > 0 ? `Unnecessary properties: ${additionalProperties.join(", ")}` : "" + }`, path: BASE_RATE_FILE, }); } From b1250cdfab084c231437b4178c2815ab5d37f448 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Mon, 28 Aug 2023 12:44:09 +0200 Subject: [PATCH 025/106] fix: additional properties --- src/bindings/event.ts | 2 +- src/handlers/push/index.ts | 4 ++++ src/utils/private.ts | 45 +++++++++++++++++++++++++------------- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/bindings/event.ts b/src/bindings/event.ts index 616580a1a..56019fefd 100644 --- a/src/bindings/event.ts +++ b/src/bindings/event.ts @@ -59,7 +59,7 @@ export const bindEvents = async (context: Context): Promise => { } if (botConfigError) { - logger.error(botConfigError); + logger.error(botConfigError.toString()); return; } diff --git a/src/handlers/push/index.ts b/src/handlers/push/index.ts index 532f7961a..ed110afda 100644 --- a/src/handlers/push/index.ts +++ b/src/handlers/push/index.ts @@ -72,6 +72,10 @@ export const validateConfigChange = async () => { // check for modified or added files and check for specified file if (changes.includes(BASE_RATE_FILE)) { const commitSha = payload.commits.filter((commit) => commit.modified.includes(BASE_RATE_FILE) || commit.added.includes(BASE_RATE_FILE)).reverse()[0]?.id; + if (!commitSha) { + logger.debug("Skipping push events, commit sha not found"); + return; + } const configFileContent = await getFileContent( payload.repository.owner.login, diff --git a/src/utils/private.ts b/src/utils/private.ts index ca4175058..d6027c53f 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -49,27 +49,42 @@ export const getConfigSuperset = async (context: Context, type: "org" | "repo", } }; -const WideLabelSchema = Type.Object({ - name: Type.String(), -}); +const WideLabelSchema = Type.Object( + { + name: Type.String(), + }, + { additionalProperties: false } +); export type WideLabel = Static; -const CommandObjSchema = Type.Object({ - name: Type.String(), - enabled: Type.Boolean(), -}); +const CommandObjSchema = Type.Object( + { + name: Type.String(), + enabled: Type.Boolean(), + }, + { additionalProperties: false } +); export type CommandObj = Static; -const IncentivesSchema = Type.Object({ - comment: Type.Object({ - elements: Type.Record(Type.String(), Type.Number()), - totals: Type.Object({ - word: Type.Number(), - }), - }), -}); +const IncentivesSchema = Type.Object( + { + comment: Type.Object( + { + elements: Type.Record(Type.String(), Type.Number()), + totals: Type.Object( + { + word: Type.Number(), + }, + { additionalProperties: false } + ), + }, + { additionalProperties: false } + ), + }, + { additionalProperties: false } +); export type Incentives = Static; From 5ec2d6abc39646f36c388ddc90a1b09e6df15148 Mon Sep 17 00:00:00 2001 From: HARALD Date: Tue, 29 Aug 2023 00:59:49 +0900 Subject: [PATCH 026/106] feat: get correct linked pull request --- src/handlers/payout/post.ts | 8 +++----- src/helpers/parser.ts | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index ffeaee275..dd61d8b23 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -165,15 +165,13 @@ export const incentivizePullRequestReviews = async () => { return; } - const pullRequestLinked = await gitLinkedPrParser({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issue.number }); + const linkedPullRequest = await gitLinkedPrParser({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issue.number }); - if (pullRequestLinked === "") { + if (!linkedPullRequest) { logger.debug(`incentivizePullRequestReviews: No linked pull requests found`); return; } - const linkedPullNumber = pullRequestLinked.substring(pullRequestLinked.lastIndexOf("/") + 1); - const comments = await getAllIssueComments(issue.number); const permitComments = comments.filter( (content) => content.body.includes("Reviewer Rewards") && content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot @@ -190,7 +188,7 @@ export const incentivizePullRequestReviews = async () => { return; } - const prReviews = await getAllPullRequestReviews(context, Number(linkedPullNumber), "full"); + const prReviews = await getAllPullRequestReviews(context, linkedPullRequest.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/helpers/parser.ts b/src/helpers/parser.ts index b7080f9a7..9bd668b7f 100644 --- a/src/helpers/parser.ts +++ b/src/helpers/parser.ts @@ -1,5 +1,7 @@ import axios from "axios"; import { HTMLElement, parse } from "node-html-parser"; +import { getPullByNumber } from "./issue"; +import { getBotContext } from "../bindings"; interface GitParser { owner: string; @@ -44,14 +46,28 @@ export const gitLinkedIssueParser = async ({ owner, repo, pull_number }: GitPars } }; -export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser): Promise => { +export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser) => { try { const { data } = await axios.get(`https://github.com/${owner}/${repo}/issues/${issue_number}`); const dom = parse(data); const devForm = dom.querySelector("[data-target='create-branch.developmentForm']") as HTMLElement; const linkedPRs = devForm.querySelectorAll(".my-1"); if (linkedPRs.length === 0) return ""; - return linkedPRs[linkedPRs.length - 1].querySelector("a")?.attrs?.href || ""; + let linkedPullRequest; + for (const linkedPr of linkedPRs) { + const prHref = linkedPr.querySelector("a")?.attrs?.href || ""; + const prNumber = prHref.substring(prHref.lastIndexOf("/") + 1); + const pr = await getPullByNumber(getBotContext(), Number(prNumber)); + if (!pr || !pr.merged) continue; + + if (!linkedPullRequest) linkedPullRequest = pr; + else if (linkedPullRequest.merged_at && pr.merged_at && new Date(linkedPullRequest.merged_at) < new Date(pr.merged_at)) { + linkedPullRequest = pr; + } + } + console.log("---------------------------"); + console.log(linkedPullRequest); + return linkedPullRequest; } catch (error) { return ""; } From d0f1b0611eecb96250307c56bac8c52ef5acce21 Mon Sep 17 00:00:00 2001 From: HARALD Date: Tue, 29 Aug 2023 01:20:55 +0900 Subject: [PATCH 027/106] feat: check the organization and repo name --- src/helpers/parser.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/helpers/parser.ts b/src/helpers/parser.ts index 9bd668b7f..82563657c 100644 --- a/src/helpers/parser.ts +++ b/src/helpers/parser.ts @@ -2,6 +2,7 @@ import axios from "axios"; import { HTMLElement, parse } from "node-html-parser"; import { getPullByNumber } from "./issue"; import { getBotContext } from "../bindings"; +import { Payload } from "../types"; interface GitParser { owner: string; @@ -49,6 +50,8 @@ export const gitLinkedIssueParser = async ({ owner, repo, pull_number }: GitPars export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser) => { try { const { data } = await axios.get(`https://github.com/${owner}/${repo}/issues/${issue_number}`); + const context = getBotContext(); + const payload = context.payload as Payload; const dom = parse(data); const devForm = dom.querySelector("[data-target='create-branch.developmentForm']") as HTMLElement; const linkedPRs = devForm.querySelectorAll(".my-1"); @@ -56,8 +59,14 @@ export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser let linkedPullRequest; for (const linkedPr of linkedPRs) { const prHref = linkedPr.querySelector("a")?.attrs?.href || ""; + const parts = prHref.split("/"); + // extract the organization name and repo name from the link:(e.g. "https://github.com/wannacfuture/ubiquibot/pull/5";) + const organization = parts[parts.length - 4]; + const repository = parts[parts.length - 3]; + + if (`${organization}/${repository}` !== payload.repository.full_name) continue; const prNumber = prHref.substring(prHref.lastIndexOf("/") + 1); - const pr = await getPullByNumber(getBotContext(), Number(prNumber)); + const pr = await getPullByNumber(context, Number(prNumber)); if (!pr || !pr.merged) continue; if (!linkedPullRequest) linkedPullRequest = pr; @@ -65,8 +74,6 @@ export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser linkedPullRequest = pr; } } - console.log("---------------------------"); - console.log(linkedPullRequest); return linkedPullRequest; } catch (error) { return ""; From ab85a52dd5817856731cfb3ffadab5987b93de1c Mon Sep 17 00:00:00 2001 From: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Tue, 29 Aug 2023 17:16:36 +0800 Subject: [PATCH 028/106] feat: add to codeowners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8b1378917..3acb3deb3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ - +@0xcodercrane \ No newline at end of file From 9e53e1a39532bf378b1786c8f54866381688843b Mon Sep 17 00:00:00 2001 From: Steveantor <109317559+Steveantor@users.noreply.github.com> Date: Tue, 29 Aug 2023 09:18:46 +0000 Subject: [PATCH 029/106] feat: add cron migration --- supabase/migrations/20230829101134_setup_cron.sql | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 supabase/migrations/20230829101134_setup_cron.sql diff --git a/supabase/migrations/20230829101134_setup_cron.sql b/supabase/migrations/20230829101134_setup_cron.sql new file mode 100644 index 000000000..883436a87 --- /dev/null +++ b/supabase/migrations/20230829101134_setup_cron.sql @@ -0,0 +1,11 @@ +-- Enable the pg_cron extension +CREATE EXTENSION IF NOT EXISTS pg_cron; + +-- Runs everyday at 03:00 AM to cleanup logs that are older than a week +-- Use the cron time format to modify the trigger time if necessary +SELECT + cron.schedule( + 'logs-cleaner', -- Job name + '0 3 * * *', -- Everyday at 03:00 AM + $$DELETE FROM logs WHERE timestamp < now() - INTERVAL '1 week'$$ + ); From 6771daa9c4a672da4438cb32927ffa7940ba0b47 Mon Sep 17 00:00:00 2001 From: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Tue, 29 Aug 2023 21:19:40 +0800 Subject: [PATCH 030/106] feat: exploit issue resolved --- src/handlers/processors.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/processors.ts b/src/handlers/processors.ts index 16f89a535..a98c65611 100644 --- a/src/handlers/processors.ts +++ b/src/handlers/processors.ts @@ -3,7 +3,7 @@ import { closePullRequestForAnIssue, commentWithAssignMessage } from "./assign"; import { pricingLabelLogic, validatePriceLabels } from "./pricing"; import { checkBountiesToUnassign, collectAnalytics, checkWeeklyUpdate } from "./wildcard"; import { nullHandler } from "./shared"; -import { handleComment, issueClosedCallback, issueReopenedCallback } from "./comment"; +import { handleComment, issueClosedCallback, issueCreatedCallback, issueReopenedCallback } from "./comment"; import { checkPullRequests } from "./assign/auto"; import { createDevPoolPR } from "./pull-request"; import { runOnPush } from "./push"; @@ -13,7 +13,7 @@ import { findDuplicateOne } from "./issue"; export const processors: Record = { [GithubEvent.ISSUES_OPENED]: { pre: [nullHandler], - action: [findDuplicateOne], // SHOULD not set `issueCreatedCallback` until the exploit issue resolved. https://github.com/ubiquity/ubiquibot/issues/535 + action: [findDuplicateOne, issueCreatedCallback], post: [nullHandler], }, [GithubEvent.ISSUES_REOPENED]: { From 743a4777607bf861e8aa969c859451ef34a7aac1 Mon Sep 17 00:00:00 2001 From: DevPanther Date: Tue, 29 Aug 2023 18:43:17 +0100 Subject: [PATCH 031/106] chore: merged repo and org config --- package.json | 4 +++- src/utils/helpers.ts | 6 +++++- yarn.lock | 28 +++++++++++++++++++--------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index e64d64e99..f6bcd54f2 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,8 @@ "ajv": "^8.11.2", "ajv-formats": "^2.1.1", "axios": "^1.3.2", - "decimal.js": "^10.4.3", "copyfiles": "^2.4.1", + "decimal.js": "^10.4.3", "ethers": "^5.7.2", "exponential-backoff": "^3.1.1", "husky": "^8.0.2", @@ -50,6 +50,7 @@ "js-yaml": "^4.1.0", "libsodium-wrappers": "^0.7.11", "lint-staged": "^13.1.0", + "lodash": "^4.17.21", "ms": "^2.1.3", "node-html-parser": "^6.1.5", "node-html-to-image": "^3.3.0", @@ -65,6 +66,7 @@ "@types/eslint": "^8.40.2", "@types/jest": "^28.1.0", "@types/libsodium-wrappers": "^0.7.10", + "@types/lodash": "^4.14.197", "@types/node": "^14.18.37", "@types/source-map-support": "^0.5.6", "eslint": "^8.43.0", diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index e3965208b..534281d65 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,6 +1,7 @@ import { Incentives } from "./private"; import { Level } from "../adapters/supabase"; import { CommandObj, WideLabel, WideOrgConfig, WideRepoConfig } from "./private"; +import _ from "lodash"; interface Configs { parsedRepo?: WideRepoConfig; @@ -95,7 +96,10 @@ export const getPriorityLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Conf }; export const getIncentives = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): Incentives => { - if (parsedRepo && parsedRepo["incentives"]) { + if (parsedRepo && parsedRepo["incentives"] && parsedOrg && parsedOrg["incentives"]) { + const mergedIncentives = _.merge({}, parsedOrg["incentives"], parsedRepo["incentives"]); + return mergedIncentives; + } else if (parsedRepo && parsedRepo["incentives"]) { return parsedRepo["incentives"]; } else if (parsedOrg && parsedOrg["incentives"]) { return parsedOrg["incentives"]; diff --git a/yarn.lock b/yarn.lock index 2d930cf18..23e875b72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1968,10 +1968,10 @@ dependencies: cross-fetch "^3.1.5" -"@supabase/realtime-js@^2.7.3": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@supabase/realtime-js/-/realtime-js-2.7.3.tgz#cbcb84181add681ab99c87032bfe88101c6863b3" - integrity sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw== +"@supabase/realtime-js@^2.7.4": + version "2.7.4" + resolved "https://registry.yarnpkg.com/@supabase/realtime-js/-/realtime-js-2.7.4.tgz#de41195bd3f2cdd6db82d9f93c4c5b8fae9f809b" + integrity sha512-FzSzs1k9ruh/uds5AJ95Nc3beiMCCIhougExJ3O98CX1LMLAKUKFy5FivKLvcNhXnNfUEL0XUfGMb4UH2J7alg== dependencies: "@types/phoenix" "^1.5.4" "@types/websocket" "^1.0.3" @@ -1984,15 +1984,15 @@ dependencies: cross-fetch "^3.1.5" -"@supabase/supabase-js@^2.32.0": - version "2.32.0" - resolved "https://registry.yarnpkg.com/@supabase/supabase-js/-/supabase-js-2.32.0.tgz#863c636d83232c6a2e9ba5932e0d7c1bf80bc436" - integrity sha512-1ShFhuOI5Du7604nlCelBsRD61daXk2O0qwjumoz35bqrYThnSPPtpJqZOHw6Mg6o7mLjIInYLh/DBlh8UvzRg== +"@supabase/supabase-js@^2.4.0": + version "2.33.1" + resolved "https://registry.yarnpkg.com/@supabase/supabase-js/-/supabase-js-2.33.1.tgz#2407861afe63c2817d030514c87a745f78dfe68a" + integrity sha512-jA00rquPTppPOHpBB6KABW98lfg0gYXcuGqP3TB1iiduznRVsi3GGk2qBKXPDLMYSe0kRlQp5xCwWWthaJr8eA== dependencies: "@supabase/functions-js" "^2.1.0" "@supabase/gotrue-js" "^2.46.1" "@supabase/postgrest-js" "^1.8.0" - "@supabase/realtime-js" "^2.7.3" + "@supabase/realtime-js" "^2.7.4" "@supabase/storage-js" "^2.5.1" cross-fetch "^3.1.5" @@ -2190,6 +2190,11 @@ resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz#a6ebde70d3b4af960fd802af8d0e3c7cfe281eb2" integrity sha512-BqI9B92u+cM3ccp8mpHf+HzJ8fBlRwdmyd6+fz3p99m3V6ifT5O3zmOMi612PGkpeFeG/G6loxUnzlDNhfjPSA== +"@types/lodash@^4.14.197": + version "4.14.197" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.197.tgz#e95c5ddcc814ec3e84c891910a01e0c8a378c54b" + integrity sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g== + "@types/mime@*": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" @@ -4189,6 +4194,11 @@ expect@^28.0.0: jest-message-util "^28.1.3" jest-util "^28.1.3" +exponential-backoff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" + integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== + express-handlebars@^6.0.3: version "6.0.7" resolved "https://registry.yarnpkg.com/express-handlebars/-/express-handlebars-6.0.7.tgz#f779254664eff0e250362ef1c2b30587059c212a" From d0a7f796114bea474f26a1eeb003d72c6d61cd78 Mon Sep 17 00:00:00 2001 From: 0xCodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Wed, 30 Aug 2023 09:15:55 +0800 Subject: [PATCH 032/106] fix: update CODEOWNERS --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3acb3deb3..4a5a1e837 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -@0xcodercrane \ No newline at end of file +* @0xcodercrane From 907b758c0f79123006fe98761137edd5c5f9abe5 Mon Sep 17 00:00:00 2001 From: DevPanther Date: Wed, 30 Aug 2023 08:06:31 +0100 Subject: [PATCH 033/106] chore: merge default,repo and org --- src/utils/helpers.ts | 167 ------------------------------------------- src/utils/private.ts | 90 +++++++++++------------ 2 files changed, 43 insertions(+), 214 deletions(-) diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 534281d65..c188e6d50 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,13 +1,4 @@ -import { Incentives } from "./private"; import { Level } from "../adapters/supabase"; -import { CommandObj, WideLabel, WideOrgConfig, WideRepoConfig } from "./private"; -import _ from "lodash"; - -interface Configs { - parsedRepo?: WideRepoConfig; - parsedOrg?: WideOrgConfig; - parsedDefault: WideRepoConfig; -} export const getNumericLevel = (level: Level) => { switch (level) { @@ -29,161 +20,3 @@ export const getNumericLevel = (level: Level) => { return -1; // Invalid level } }; - -export const getNetworkId = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): number => { - if (parsedRepo && parsedRepo["evm-network-id"] !== undefined && !Number.isNaN(Number(parsedRepo["evm-network-id"]))) { - return Number(parsedRepo["evm-network-id"]); - } else if (parsedOrg && parsedOrg["evm-network-id"] !== undefined && !Number.isNaN(Number(parsedOrg["evm-network-id"]))) { - return Number(parsedOrg["evm-network-id"]); - } else { - return Number(parsedDefault["evm-network-id"]); - } -}; - -export const getBaseMultiplier = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): number => { - if (parsedRepo && parsedRepo["price-multiplier"] !== undefined && !Number.isNaN(Number(parsedRepo["price-multiplier"]))) { - return Number(parsedRepo["price-multiplier"]); - } else if (parsedOrg && parsedOrg["price-multiplier"] !== undefined && !Number.isNaN(Number(parsedOrg["price-multiplier"]))) { - return Number(parsedOrg["price-multiplier"]); - } else { - return Number(parsedDefault["price-multiplier"]); - } -}; - -export const getCreatorMultiplier = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): number => { - if (parsedRepo && parsedRepo["issue-creator-multiplier"] !== undefined && !Number.isNaN(Number(parsedRepo["issue-creator-multiplier"]))) { - return Number(parsedRepo["issue-creator-multiplier"]); - } else if (parsedOrg && parsedOrg["issue-creator-multiplier"] !== undefined && !Number.isNaN(Number(parsedOrg["issue-creator-multiplier"]))) { - return Number(parsedOrg["issue-creator-multiplier"]); - } else { - return Number(parsedDefault["issue-creator-multiplier"]); - } -}; - -export const getTimeLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): WideLabel[] => { - if (parsedRepo && parsedRepo["time-labels"] !== undefined && Array.isArray(parsedRepo["time-labels"]) && parsedRepo["time-labels"].length > 0) { - return parsedRepo["time-labels"]; - } else if (parsedOrg && parsedOrg["time-labels"] !== undefined && Array.isArray(parsedOrg["time-labels"]) && parsedOrg["time-labels"].length > 0) { - return parsedOrg["time-labels"]; - } else { - return parsedDefault["time-labels"] as WideLabel[]; - } -}; - -export const getCommandSettings = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): CommandObj[] => { - if (parsedRepo && parsedRepo["command-settings"] && Array.isArray(parsedRepo["command-settings"]) && parsedRepo["command-settings"].length > 0) { - return parsedRepo["command-settings"]; - } else if (parsedOrg && parsedOrg["command-settings"] && Array.isArray(parsedOrg["command-settings"]) && parsedOrg["command-settings"].length > 0) { - return parsedOrg["command-settings"]; - } else { - return parsedDefault["command-settings"] as CommandObj[]; - } -}; - -export const getPriorityLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): WideLabel[] => { - if (parsedRepo && parsedRepo["priority-labels"] !== undefined && Array.isArray(parsedRepo["priority-labels"]) && parsedRepo["priority-labels"].length > 0) { - return parsedRepo["priority-labels"]; - } else if ( - parsedOrg && - parsedOrg["priority-labels"] !== undefined && - Array.isArray(parsedOrg["priority-labels"]) && - parsedOrg["priority-labels"].length > 0 - ) { - return parsedOrg["priority-labels"]; - } else { - return parsedDefault["priority-labels"] as WideLabel[]; - } -}; - -export const getIncentives = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): Incentives => { - if (parsedRepo && parsedRepo["incentives"] && parsedOrg && parsedOrg["incentives"]) { - const mergedIncentives = _.merge({}, parsedOrg["incentives"], parsedRepo["incentives"]); - return mergedIncentives; - } else if (parsedRepo && parsedRepo["incentives"]) { - return parsedRepo["incentives"]; - } else if (parsedOrg && parsedOrg["incentives"]) { - return parsedOrg["incentives"]; - } else { - return parsedDefault["incentives"] as Incentives; - } -}; - -export const getPaymentPermitMaxPrice = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): number => { - if (parsedRepo && parsedRepo["payment-permit-max-price"] && typeof parsedRepo["payment-permit-max-price"] === "number") { - return Number(parsedRepo["payment-permit-max-price"]); - } else if (parsedOrg && parsedOrg["payment-permit-max-price"] && typeof parsedOrg["payment-permit-max-price"] === "number") { - return Number(parsedOrg["payment-permit-max-price"]); - } else { - return Number(parsedDefault["payment-permit-max-price"]); - } -}; - -export const getAssistivePricing = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): boolean => { - if (parsedRepo && parsedRepo["assistive-pricing"] && typeof parsedRepo["assistive-pricing"] === "boolean") { - return parsedRepo["assistive-pricing"]; - } else if (parsedOrg && parsedOrg["assistive-pricing"] && typeof parsedOrg["assistive-pricing"] === "boolean") { - return parsedOrg["assistive-pricing"]; - } else { - return parsedDefault["assistive-pricing"] as boolean; - } -}; - -export const getAnalyticsMode = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): boolean => { - if (parsedRepo && parsedRepo["disable-analytics"] !== undefined && typeof parsedRepo["disable-analytics"] === "boolean") { - return parsedRepo["disable-analytics"]; - } else if (parsedOrg && parsedOrg["disable-analytics"] !== undefined && typeof parsedOrg["disable-analytics"] === "boolean") { - return parsedOrg["disable-analytics"]; - } else { - return parsedDefault["disable-analytics"] as boolean; - } -}; - -export const getPromotionComment = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): string => { - if (parsedRepo && parsedRepo["promotion-comment"] !== undefined && typeof parsedRepo["promotion-comment"] === "string") { - return parsedRepo["promotion-comment"]; - } else if (parsedOrg && parsedOrg["promotion-comment"] !== undefined && typeof parsedOrg["promotion-comment"] === "string") { - return parsedOrg["promotion-comment"]; - } else { - return parsedDefault["promotion-comment"] as string; - } -}; - -export const getIncentiveMode = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): boolean => { - if (parsedRepo && parsedRepo["comment-incentives"] !== undefined && typeof parsedRepo["comment-incentives"] === "boolean") { - return parsedRepo["comment-incentives"]; - } else if (parsedOrg && parsedOrg["comment-incentives"] !== undefined && typeof parsedOrg["comment-incentives"] === "boolean") { - return parsedOrg["comment-incentives"]; - } else { - return parsedDefault["comment-incentives"] as boolean; - } -}; - -export const getBountyHunterMax = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): number => { - if (parsedRepo && parsedRepo["max-concurrent-assigns"] !== undefined && !Number.isNaN(Number(parsedRepo["max-concurrent-assigns"]))) { - return Number(parsedRepo["max-concurrent-assigns"]); - } else if (parsedOrg && parsedOrg["max-concurrent-assigns"] !== undefined && !Number.isNaN(Number(parsedOrg["max-concurrent-assigns"]))) { - return Number(parsedOrg["max-concurrent-assigns"]); - } else { - return Number(parsedDefault["max-concurrent-assigns"]); - } -}; - -export const getDefaultLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): string[] => { - if (parsedRepo && parsedRepo["default-labels"] !== undefined) { - return parsedRepo["default-labels"]; - } else if (parsedOrg && parsedOrg["default-labels"] !== undefined) { - return parsedOrg["default-labels"]; - } else { - return parsedDefault["default-labels"] as string[]; - } -}; - -export const getRegisterWalletWithVerification = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): boolean => { - if (parsedRepo && parsedRepo["register-wallet-with-verification"] !== undefined && typeof parsedRepo["register-wallet-with-verification"] === "boolean") { - return Boolean(parsedRepo["register-wallet-with-verification"]); - } else if (parsedOrg && parsedOrg["register-wallet-with-verification"] !== undefined && typeof parsedOrg["register-wallet-with-verification"] === "boolean") { - return Boolean(parsedOrg["register-wallet-with-verification"]); - } else { - return Boolean(parsedDefault["register-wallet-with-verification"]); - } -}; diff --git a/src/utils/private.ts b/src/utils/private.ts index 36ff92f8c..116bc37a7 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -2,23 +2,7 @@ import _sodium from "libsodium-wrappers"; import YAML from "yaml"; import { Payload } from "../types"; import { Context } from "probot"; -import { - getAnalyticsMode, - getPaymentPermitMaxPrice, - getBaseMultiplier, - getCreatorMultiplier, - getBountyHunterMax, - getIncentiveMode, - getNetworkId, - getPriorityLabels, - getTimeLabels, - getDefaultLabels, - getPromotionComment, - getIncentives, - getAssistivePricing, - getCommandSettings, - getRegisterWalletWithVerification, -} from "./helpers"; +import _ from "lodash"; import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json"; @@ -68,21 +52,21 @@ export interface CommandObj { } export interface WideConfig { - "evm-network-id"?: number; - "price-multiplier"?: number; + "evm-network-id": number; + "price-multiplier": number; "issue-creator-multiplier": number; - "time-labels"?: WideLabel[]; - "priority-labels"?: WideLabel[]; - "payment-permit-max-price"?: number; - "command-settings"?: CommandObj[]; - "promotion-comment"?: string; - "disable-analytics"?: boolean; - "comment-incentives"?: boolean; - "assistive-pricing"?: boolean; - "max-concurrent-assigns"?: number; - incentives?: Incentives; - "default-labels"?: string[]; - "register-wallet-with-verification"?: boolean; + "time-labels": WideLabel[]; + "priority-labels": WideLabel[]; + "payment-permit-max-price": number; + "command-settings": CommandObj[]; + "promotion-comment": string; + "disable-analytics": boolean; + "comment-incentives": boolean; + "assistive-pricing": boolean; + "max-concurrent-assigns": number; + incentives: Incentives; + "default-labels": string[]; + "register-wallet-with-verification": boolean; } export type WideRepoConfig = WideConfig; @@ -91,6 +75,12 @@ export interface WideOrgConfig extends WideConfig { "private-key-encrypted"?: string; } +export interface MergedConfigs { + parsedRepo: WideRepoConfig | undefined; + parsedOrg: WideOrgConfig | undefined; + parsedDefault: WideRepoConfig; +} + export const parseYAML = (data?: string): WideConfig | undefined => { try { if (data) { @@ -155,6 +145,10 @@ export const getScalarKey = async (X25519_PRIVATE_KEY: string | undefined): Prom } }; +const mergeConfigs = (configs: MergedConfigs) => { + return _.merge({}, ...Object.values(configs)); +}; + export const getWideConfig = async (context: Context) => { const orgConfig = await getConfigSuperset(context, "org", CONFIG_PATH); const repoConfig = await getConfigSuperset(context, "repo", CONFIG_PATH); @@ -164,24 +158,26 @@ export const getWideConfig = async (context: Context) => { const parsedDefault: WideRepoConfig = DEFAULT_CONFIG_JSON; const privateKeyDecrypted = parsedOrg && parsedOrg[KEY_NAME] ? await getPrivateKey(parsedOrg[KEY_NAME]) : undefined; - const configs = { parsedRepo, parsedOrg, parsedDefault }; + const configs: MergedConfigs = { parsedRepo, parsedOrg, parsedDefault }; + const mergedConfigData: WideConfig = mergeConfigs(configs); + const configData = { - networkId: getNetworkId(configs), + networkId: mergedConfigData["evm-network-id"], privateKey: privateKeyDecrypted ?? "", - assistivePricing: getAssistivePricing(configs), - commandSettings: getCommandSettings(configs), - baseMultiplier: getBaseMultiplier(configs), - issueCreatorMultiplier: getCreatorMultiplier(configs), - timeLabels: getTimeLabels(configs), - priorityLabels: getPriorityLabels(configs), - paymentPermitMaxPrice: getPaymentPermitMaxPrice(configs), - disableAnalytics: getAnalyticsMode(configs), - bountyHunterMax: getBountyHunterMax(configs), - incentiveMode: getIncentiveMode(configs), - incentives: getIncentives(configs), - defaultLabels: getDefaultLabels(configs), - promotionComment: getPromotionComment(configs), - registerWalletWithVerification: getRegisterWalletWithVerification(configs), + assistivePricing: mergedConfigData["assistive-pricing"], + commandSettings: mergedConfigData["command-settings"], + baseMultiplier: mergedConfigData["price-multiplier"], + issueCreatorMultiplier: mergedConfigData["issue-creator-multiplier"], + timeLabels: mergedConfigData["time-labels"], + priorityLabels: mergedConfigData["priority-labels"], + paymentPermitMaxPrice: mergedConfigData["payment-permit-max-price"], + disableAnalytics: mergedConfigData["disable-analytics"], + bountyHunterMax: mergedConfigData["max-concurrent-assigns"], + incentiveMode: mergedConfigData["comment-incentives"], + incentives: mergedConfigData["incentives"], + defaultLabels: mergedConfigData["default-labels"], + promotionComment: mergedConfigData["promotion-comment"], + registerWalletWithVerification: mergedConfigData["register-wallet-with-verification"], }; return configData; From 7bbdadabab80bc8870cce94699661bc7d1da8126 Mon Sep 17 00:00:00 2001 From: DevPanther Date: Wed, 30 Aug 2023 08:16:38 +0100 Subject: [PATCH 034/106] chore: re-arrange order to use repo first --- src/utils/private.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/private.ts b/src/utils/private.ts index 116bc37a7..a78b48859 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -158,7 +158,7 @@ export const getWideConfig = async (context: Context) => { const parsedDefault: WideRepoConfig = DEFAULT_CONFIG_JSON; const privateKeyDecrypted = parsedOrg && parsedOrg[KEY_NAME] ? await getPrivateKey(parsedOrg[KEY_NAME]) : undefined; - const configs: MergedConfigs = { parsedRepo, parsedOrg, parsedDefault }; + const configs: MergedConfigs = { parsedDefault, parsedOrg, parsedRepo }; const mergedConfigData: WideConfig = mergeConfigs(configs); const configData = { From 39e2143512d9dce8434595506ec2330d264817be Mon Sep 17 00:00:00 2001 From: DevPanther <42383942+devpanther@users.noreply.github.com> Date: Wed, 30 Aug 2023 09:39:08 +0100 Subject: [PATCH 035/106] Update src/utils/private.ts Co-authored-by: whilefoo <139262667+whilefoo@users.noreply.github.com> --- src/utils/private.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/private.ts b/src/utils/private.ts index a78b48859..e040cc951 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -146,7 +146,7 @@ export const getScalarKey = async (X25519_PRIVATE_KEY: string | undefined): Prom }; const mergeConfigs = (configs: MergedConfigs) => { - return _.merge({}, ...Object.values(configs)); + return merge({}, configs.parsedDefault, configs.parsedOrg, configs.parsedRepo); }; export const getWideConfig = async (context: Context) => { From 646d4b8b0e53c0ead8347a2a54b37dc95dbe36e6 Mon Sep 17 00:00:00 2001 From: DevPanther <42383942+devpanther@users.noreply.github.com> Date: Wed, 30 Aug 2023 09:39:15 +0100 Subject: [PATCH 036/106] Update src/utils/private.ts Co-authored-by: whilefoo <139262667+whilefoo@users.noreply.github.com> --- src/utils/private.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/private.ts b/src/utils/private.ts index e040cc951..bc7ac27da 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -2,7 +2,7 @@ import _sodium from "libsodium-wrappers"; import YAML from "yaml"; import { Payload } from "../types"; import { Context } from "probot"; -import _ from "lodash"; +import merge from "lodash/merge"; import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json"; From 7f1dfc950e3fa4fcd4658feff2e3855c1b9cb631 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Wed, 30 Aug 2023 23:18:41 +0200 Subject: [PATCH 037/106] fix: run validate config if error loading config on push event refactor --- src/adapters/supabase/helpers/log.ts | 47 ++++++++------ src/bindings/config.ts | 5 +- src/bindings/event.ts | 14 +++-- src/handlers/push/index.ts | 25 ++------ src/helpers/commit.ts | 21 +++++++ src/helpers/index.ts | 1 + src/types/config.ts | 89 +++++++++++++++++++++------ src/types/index.ts | 1 + src/types/log.ts | 9 +++ src/utils/ajv.ts | 24 +++++++- src/utils/helpers.ts | 37 +++-------- src/utils/private.ts | 91 ++++------------------------ 12 files changed, 190 insertions(+), 174 deletions(-) create mode 100644 src/helpers/commit.ts create mode 100644 src/types/log.ts diff --git a/src/adapters/supabase/helpers/log.ts b/src/adapters/supabase/helpers/log.ts index e352339a8..51a16f5b2 100644 --- a/src/adapters/supabase/helpers/log.ts +++ b/src/adapters/supabase/helpers/log.ts @@ -1,26 +1,37 @@ import { getAdapters, getBotContext, Logger } from "../../../bindings"; -import { Payload } from "../../../types"; -import { getNumericLevel } from "../../../utils/helpers"; +import { Payload, LogLevel } from "../../../types"; import { getOrgAndRepoFromPath } from "../../../utils/private"; + interface Log { repo: string | null; org: string | null; commentId: number | undefined; issueNumber: number | undefined; logMessage: string; - level: Level; + level: LogLevel; timestamp: string; } -export enum Level { - ERROR = "error", - WARN = "warn", - INFO = "info", - HTTP = "http", - VERBOSE = "verbose", - DEBUG = "debug", - SILLY = "silly", -} +export const getNumericLevel = (level: LogLevel) => { + switch (level) { + case LogLevel.ERROR: + return 0; + case LogLevel.WARN: + return 1; + case LogLevel.INFO: + return 2; + case LogLevel.HTTP: + return 3; + case LogLevel.VERBOSE: + return 4; + case LogLevel.DEBUG: + return 5; + case LogLevel.SILLY: + return 6; + default: + return -1; // Invalid level + } +}; export class GitHubLogger implements Logger { private supabase; @@ -33,7 +44,7 @@ export class GitHubLogger implements Logger { private throttleCount = 0; private retryLimit = 0; // Retries disabled by default - constructor(app: string, logEnvironment: string, maxLevel: Level, retryLimit: number) { + constructor(app: string, logEnvironment: string, maxLevel: LogLevel, retryLimit: number) { this.app = app; this.logEnvironment = logEnvironment; this.maxLevel = getNumericLevel(maxLevel); @@ -118,7 +129,7 @@ export class GitHubLogger implements Logger { } } - private save(logMessage: string | object, level: Level, errorPayload?: string | object) { + 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(); @@ -153,19 +164,19 @@ export class GitHubLogger implements Logger { } info(message: string | object, errorPayload?: string | object) { - this.save(message, Level.INFO, errorPayload); + this.save(message, LogLevel.INFO, errorPayload); } warn(message: string | object, errorPayload?: string | object) { - this.save(message, Level.WARN, errorPayload); + this.save(message, LogLevel.WARN, errorPayload); } debug(message: string | object, errorPayload?: string | object) { - this.save(message, Level.DEBUG, errorPayload); + this.save(message, LogLevel.DEBUG, errorPayload); } error(message: string | object, errorPayload?: string | object) { - this.save(message, Level.ERROR, errorPayload); + this.save(message, LogLevel.ERROR, errorPayload); } async get() { diff --git a/src/bindings/config.ts b/src/bindings/config.ts index 9fc6f4ed0..d87e4deaf 100644 --- a/src/bindings/config.ts +++ b/src/bindings/config.ts @@ -1,6 +1,6 @@ import ms from "ms"; -import { BotConfig, BotConfigSchema } from "../types"; +import { BotConfig, BotConfigSchema, LogLevel } from "../types"; import { DEFAULT_BOT_DELAY, DEFAULT_DISQUALIFY_TIME, @@ -13,7 +13,6 @@ import { getPayoutConfigByNetworkId } from "../helpers"; import { ajv } from "../utils"; import { Context } from "probot"; import { getScalarKey, getWideConfig } from "../utils/private"; -import { Level } from "../adapters/supabase"; export const loadConfig = async (context: Context): Promise => { const { @@ -41,7 +40,7 @@ export const loadConfig = async (context: Context): Promise => { const botConfig: BotConfig = { log: { logEnvironment: process.env.LOG_ENVIRONMENT || "production", - level: (process.env.LOG_LEVEL as Level) || Level.DEBUG, + level: (process.env.LOG_LEVEL as LogLevel) || LogLevel.DEBUG, retryLimit: Number(process.env.LOG_RETRY) || 0, }, price: { diff --git a/src/bindings/event.ts b/src/bindings/event.ts index 56019fefd..f36641565 100644 --- a/src/bindings/event.ts +++ b/src/bindings/event.ts @@ -2,11 +2,12 @@ import { Context } from "probot"; import { createAdapters } from "../adapters"; import { processors, wildcardProcessors } from "../handlers/processors"; import { shouldSkip } from "../helpers"; -import { BotConfig, GithubEvent, Payload, PayloadSchema } from "../types"; +import { BotConfig, GithubEvent, Payload, PayloadSchema, LogLevel } from "../types"; import { Adapters } from "../types/adapters"; import { ajv } from "../utils"; import { loadConfig } from "./config"; -import { GitHubLogger, Level } from "../adapters/supabase"; +import { GitHubLogger } from "../adapters/supabase"; +import { validateConfigChange } from "../handlers/push"; let botContext: Context = {} as Context; export const getBotContext = () => botContext; @@ -33,6 +34,8 @@ export const bindEvents = async (context: Context): Promise => { 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 { @@ -51,7 +54,7 @@ export const bindEvents = async (context: Context): Promise => { logger = new GitHubLogger( options.app, botConfig?.log?.logEnvironment ?? "development", - botConfig?.log?.level ?? Level.DEBUG, + botConfig?.log?.level ?? LogLevel.DEBUG, botConfig?.log?.retryLimit ?? 0 ); // contributors will see logs in console while on development env if (!logger) { @@ -60,6 +63,9 @@ export const bindEvents = async (context: Context): Promise => { if (botConfigError) { logger.error(botConfigError.toString()); + if (eventName === GithubEvent.PUSH_EVENT) { + await validateConfigChange(); + } return; } @@ -75,8 +81,6 @@ export const bindEvents = async (context: Context): Promise => { wallet: botConfig.wallet, })}` ); - const allowedEvents = Object.values(GithubEvent) as string[]; - const eventName = payload.action ? `${name}.${payload.action}` : name; // some events wont have actions as this grows logger.info(`Started binding events... id: ${id}, name: ${eventName}, allowedEvents: ${allowedEvents}`); diff --git a/src/handlers/push/index.ts b/src/handlers/push/index.ts index ed110afda..66dac755f 100644 --- a/src/handlers/push/index.ts +++ b/src/handlers/push/index.ts @@ -1,10 +1,9 @@ import { getBotContext, getLogger } from "../../bindings"; -import { getFileContent } from "../../helpers"; -import { CommitsPayload, PushPayload } from "../../types"; -import { ajv } from "../../utils"; +import { createCommitComment, getFileContent } from "../../helpers"; +import { CommitsPayload, PushPayload, WideOrgConfigSchema } from "../../types"; import { parseYAML } from "../../utils/private"; import { updateBaseRate } from "./update-base"; -import { WideOrgConfigSchema } from "../../utils/private"; +import { validate } from "../../utils/ajv"; const ZERO_SHA = "0000000000000000000000000000000000000000"; const BASE_RATE_FILE = ".github/ubiquibot-config.yml"; @@ -88,23 +87,9 @@ export const validateConfigChange = async () => { if (configFileContent) { const decodedConfig = Buffer.from(configFileContent, "base64").toString(); const config = parseYAML(decodedConfig); - const valid = ajv.validate(WideOrgConfigSchema, config); // additionalProperties: false is required to prevent unknown properties from being allowed + const { valid, error } = validate(WideOrgConfigSchema, config); if (!valid) { - // post commit comment - const additionalProperties = ajv.errors?.map((error) => { - if (error.keyword === "additionalProperties") { - return error.params.additionalProperty; - } - }); - await context.octokit.rest.repos.createCommitComment({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - commit_sha: commitSha, - body: `@${payload.sender.login} Config validation failed! Error: ${ajv.errorsText()}. ${ - additionalProperties && additionalProperties.length > 0 ? `Unnecessary properties: ${additionalProperties.join(", ")}` : "" - }`, - path: BASE_RATE_FILE, - }); + await createCommitComment(`@${payload.sender.login} Config validation failed! ${error}`, commitSha, BASE_RATE_FILE); } } } diff --git a/src/helpers/commit.ts b/src/helpers/commit.ts new file mode 100644 index 000000000..678b4d774 --- /dev/null +++ b/src/helpers/commit.ts @@ -0,0 +1,21 @@ +import { getBotContext } from "../bindings"; +import { Payload } from "../types"; + +export async function createCommitComment(body: string, commitSha: string, path?: string, owner?: string, repo?: string) { + const context = getBotContext(); + const payload = context.payload as Payload; + if (!owner) { + owner = payload.repository.owner.login; + } + if (!repo) { + repo = payload.repository.name; + } + + await context.octokit.rest.repos.createCommitComment({ + owner: owner, + repo: repo, + commit_sha: commitSha, + body: body, + path: path, + }); +} diff --git a/src/helpers/index.ts b/src/helpers/index.ts index ad8ee0cb9..07cf66d74 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -9,3 +9,4 @@ export * from "./comment"; export * from "./payout"; export * from "./file"; export * from "./similarity"; +export * from "./commit"; diff --git a/src/types/config.ts b/src/types/config.ts index cda063811..093650085 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1,28 +1,46 @@ import { Static, Type } from "@sinclair/typebox"; -import { Level } from "../adapters/supabase"; - -const LabelItemSchema = Type.Object({ - name: Type.String(), -}); +import { LogLevel } from "./log"; + +const LabelItemSchema = Type.Object( + { + name: Type.String(), + }, + { + additionalProperties: false, + } +); export type LabelItem = Static; -const CommentIncentivesSchema = Type.Object({ - elements: Type.Record(Type.String(), Type.Number()), - totals: Type.Object({ - word: Type.Number(), - }), -}); +const CommentIncentivesSchema = Type.Object( + { + elements: Type.Record(Type.String(), Type.Number()), + totals: Type.Object( + { + word: Type.Number(), + }, + { additionalProperties: false } + ), + }, + { additionalProperties: false } +); export type CommentIncentives = Static; -const IncentivesSchema = Type.Object({ - comment: CommentIncentivesSchema, -}); +const IncentivesSchema = Type.Object( + { + comment: CommentIncentivesSchema, + }, + { additionalProperties: false } +); + export type Incentives = Static; -const CommandItemSchema = Type.Object({ - name: Type.String(), - enabled: Type.Boolean(), -}); +const CommandItemSchema = Type.Object( + { + name: Type.String(), + enabled: Type.Boolean(), + }, + { additionalProperties: false } +); export type CommandItem = Static; export const PriceConfigSchema = Type.Object({ @@ -73,7 +91,7 @@ export const AssignSchema = Type.Object({ export const LogConfigSchema = Type.Object({ logEnvironment: Type.String(), - level: Type.Enum(Level), + level: Type.Enum(LogLevel), retryLimit: Type.Number(), }); @@ -109,3 +127,36 @@ export const BotConfigSchema = Type.Object({ }); export type BotConfig = Static; + +export const WideConfigSchema = Type.Object( + { + "evm-network-id": Type.Optional(Type.Number()), + "price-multiplier": Type.Optional(Type.Number()), + "issue-creator-multiplier": Type.Number(), + "time-labels": Type.Optional(Type.Array(LabelItemSchema)), + "priority-labels": Type.Optional(Type.Array(LabelItemSchema)), + "payment-permit-max-price": Type.Optional(Type.Number()), + "command-settings": Type.Optional(Type.Array(CommandItemSchema)), + "promotion-comment": Type.Optional(Type.String()), + "disable-analytics": Type.Optional(Type.Boolean()), + "comment-incentives": Type.Optional(Type.Boolean()), + "assistive-pricing": Type.Optional(Type.Boolean()), + "max-concurrent-assigns": Type.Optional(Type.Number()), + incentives: Type.Optional(IncentivesSchema), + "default-labels": Type.Optional(Type.Array(Type.String())), + "register-wallet-with-verification": Type.Optional(Type.Boolean()), + }, + { + additionalProperties: false, + } +); + +export type WideConfig = Static; + +export type WideRepoConfig = WideConfig; + +export const WideOrgConfigSchema = Type.Composite([Type.Object({ "private-key-encrypted": Type.Optional(Type.String()) }), WideConfigSchema], { + additionalProperties: false, +}); + +export type WideOrgConfig = Static; diff --git a/src/types/index.ts b/src/types/index.ts index d797feabf..83eac7e89 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -4,3 +4,4 @@ export * from "./label"; export * from "./handlers"; export * from "./config"; export * from "./markdown"; +export * from "./log"; diff --git a/src/types/log.ts b/src/types/log.ts new file mode 100644 index 000000000..8e3e1b913 --- /dev/null +++ b/src/types/log.ts @@ -0,0 +1,9 @@ +export enum LogLevel { + ERROR = "error", + WARN = "warn", + INFO = "info", + HTTP = "http", + VERBOSE = "verbose", + DEBUG = "debug", + SILLY = "silly", +} diff --git a/src/utils/ajv.ts b/src/utils/ajv.ts index 3a5d59d1b..11a8c87d1 100644 --- a/src/utils/ajv.ts +++ b/src/utils/ajv.ts @@ -1,4 +1,4 @@ -import Ajv from "ajv"; +import Ajv, { Schema } from "ajv"; import addFormats from "ajv-formats"; export const ajv = addFormats(new Ajv(), { @@ -27,3 +27,25 @@ export const ajv = addFormats(new Ajv(), { "binary", ], }); + +export function getAdditionalProperties() { + return ajv.errors?.map((error) => { + if (error.keyword === "additionalProperties") { + return error.params.additionalProperty; + } + }); +} + +export function validate(scheme: string | Schema, data: unknown): { valid: true; error: undefined } | { valid: false; error: string } { + const valid = ajv.validate(scheme, data); + if (!valid) { + const additionalProperties = getAdditionalProperties(); + return { + valid: false, + error: `${ajv.errorsText()}. ${ + additionalProperties && additionalProperties.length > 0 ? `Unnecessary properties: ${additionalProperties.join(", ")}` : "" + }`, + }; + } + return { valid: true, error: undefined }; +} diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index e3965208b..5432dbffc 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,6 +1,4 @@ -import { Incentives } from "./private"; -import { Level } from "../adapters/supabase"; -import { CommandObj, WideLabel, WideOrgConfig, WideRepoConfig } from "./private"; +import { CommandItem, LabelItem, WideOrgConfig, WideRepoConfig, Incentives } from "../types"; interface Configs { parsedRepo?: WideRepoConfig; @@ -8,27 +6,6 @@ interface Configs { parsedDefault: WideRepoConfig; } -export const getNumericLevel = (level: Level) => { - switch (level) { - case Level.ERROR: - return 0; - case Level.WARN: - return 1; - case Level.INFO: - return 2; - case Level.HTTP: - return 3; - case Level.VERBOSE: - return 4; - case Level.DEBUG: - return 5; - case Level.SILLY: - return 6; - default: - return -1; // Invalid level - } -}; - export const getNetworkId = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): number => { if (parsedRepo && parsedRepo["evm-network-id"] !== undefined && !Number.isNaN(Number(parsedRepo["evm-network-id"]))) { return Number(parsedRepo["evm-network-id"]); @@ -59,27 +36,27 @@ export const getCreatorMultiplier = ({ parsedRepo, parsedOrg, parsedDefault }: C } }; -export const getTimeLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): WideLabel[] => { +export const getTimeLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): LabelItem[] => { if (parsedRepo && parsedRepo["time-labels"] !== undefined && Array.isArray(parsedRepo["time-labels"]) && parsedRepo["time-labels"].length > 0) { return parsedRepo["time-labels"]; } else if (parsedOrg && parsedOrg["time-labels"] !== undefined && Array.isArray(parsedOrg["time-labels"]) && parsedOrg["time-labels"].length > 0) { return parsedOrg["time-labels"]; } else { - return parsedDefault["time-labels"] as WideLabel[]; + return parsedDefault["time-labels"] as LabelItem[]; } }; -export const getCommandSettings = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): CommandObj[] => { +export const getCommandSettings = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): CommandItem[] => { if (parsedRepo && parsedRepo["command-settings"] && Array.isArray(parsedRepo["command-settings"]) && parsedRepo["command-settings"].length > 0) { return parsedRepo["command-settings"]; } else if (parsedOrg && parsedOrg["command-settings"] && Array.isArray(parsedOrg["command-settings"]) && parsedOrg["command-settings"].length > 0) { return parsedOrg["command-settings"]; } else { - return parsedDefault["command-settings"] as CommandObj[]; + return parsedDefault["command-settings"] as CommandItem[]; } }; -export const getPriorityLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): WideLabel[] => { +export const getPriorityLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): LabelItem[] => { if (parsedRepo && parsedRepo["priority-labels"] !== undefined && Array.isArray(parsedRepo["priority-labels"]) && parsedRepo["priority-labels"].length > 0) { return parsedRepo["priority-labels"]; } else if ( @@ -90,7 +67,7 @@ export const getPriorityLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Conf ) { return parsedOrg["priority-labels"]; } else { - return parsedDefault["priority-labels"] as WideLabel[]; + return parsedDefault["priority-labels"] as LabelItem[]; } }; diff --git a/src/utils/private.ts b/src/utils/private.ts index d6027c53f..094875b65 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -21,8 +21,8 @@ import { } from "./helpers"; import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json"; -import { Static, Type } from "@sinclair/typebox"; -import { ajv } from "./ajv"; +import { validate } from "./ajv"; +import { WideConfig, WideOrgConfig, WideRepoConfig, WideConfigSchema, WideOrgConfigSchema } from "../types"; const CONFIG_REPO = "ubiquibot-config"; const CONFIG_PATH = ".github/ubiquibot-config.yml"; @@ -49,78 +49,6 @@ export const getConfigSuperset = async (context: Context, type: "org" | "repo", } }; -const WideLabelSchema = Type.Object( - { - name: Type.String(), - }, - { additionalProperties: false } -); - -export type WideLabel = Static; - -const CommandObjSchema = Type.Object( - { - name: Type.String(), - enabled: Type.Boolean(), - }, - { additionalProperties: false } -); - -export type CommandObj = Static; - -const IncentivesSchema = Type.Object( - { - comment: Type.Object( - { - elements: Type.Record(Type.String(), Type.Number()), - totals: Type.Object( - { - word: Type.Number(), - }, - { additionalProperties: false } - ), - }, - { additionalProperties: false } - ), - }, - { additionalProperties: false } -); - -export type Incentives = Static; - -export const WideConfigSchema = Type.Object( - { - "evm-network-id": Type.Optional(Type.Number()), - "price-multiplier": Type.Optional(Type.Number()), - "issue-creator-multiplier": Type.Number(), - "time-labels": Type.Optional(Type.Array(WideLabelSchema)), - "priority-labels": Type.Optional(Type.Array(WideLabelSchema)), - "payment-permit-max-price": Type.Optional(Type.Number()), - "command-settings": Type.Optional(Type.Array(CommandObjSchema)), - "promotion-comment": Type.Optional(Type.String()), - "disable-analytics": Type.Optional(Type.Boolean()), - "comment-incentives": Type.Optional(Type.Boolean()), - "assistive-pricing": Type.Optional(Type.Boolean()), - "max-concurrent-assigns": Type.Optional(Type.Number()), - incentives: Type.Optional(IncentivesSchema), - "default-labels": Type.Optional(Type.Array(Type.String())), - "register-wallet-with-verification": Type.Optional(Type.Boolean()), - }, - { - additionalProperties: false, - } -); - -export type WideConfig = Static; - -export type WideRepoConfig = WideConfig; - -export const WideOrgConfigSchema = Type.Composite([Type.Object({ "private-key-encrypted": Type.Optional(Type.String()) }), WideConfigSchema], { - additionalProperties: false, -}); - -export type WideOrgConfig = Static; - export const parseYAML = (data?: string): WideConfig | undefined => { try { if (data) { @@ -190,12 +118,19 @@ export const getWideConfig = async (context: Context) => { const repoConfig = await getConfigSuperset(context, "repo", CONFIG_PATH); const parsedOrg: WideOrgConfig | undefined = parseYAML(orgConfig); - if (parsedOrg !== undefined && !ajv.validate(WideOrgConfigSchema, parsedOrg)) { - throw new Error(`Invalid org config: ${ajv.errorsText()}`); + + if (parsedOrg) { + const { valid, error } = validate(WideOrgConfigSchema, parsedOrg); + if (!valid) { + throw new Error(`Invalid org config: ${error}`); + } } const parsedRepo: WideRepoConfig | undefined = parseYAML(repoConfig); - if (parsedRepo !== undefined && !ajv.validate(WideConfigSchema, parsedRepo)) { - throw new Error(`Invalid repo config: ${ajv.errorsText()}`); + if (parsedRepo) { + const { valid, error } = validate(WideConfigSchema, parsedRepo); + if (!valid) { + throw new Error(`Invalid repo config: ${error}`); + } } const parsedDefault: WideRepoConfig = DEFAULT_CONFIG_JSON; const privateKeyDecrypted = parsedOrg && parsedOrg[KEY_NAME] ? await getPrivateKey(parsedOrg[KEY_NAME]) : undefined; From 3addf7a71039395a7e089f65019463821f471f37 Mon Sep 17 00:00:00 2001 From: HARALD Date: Thu, 31 Aug 2023 18:08:34 +0900 Subject: [PATCH 038/106] feat: minor regex fix --- src/handlers/comment/handlers/allow.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/comment/handlers/allow.ts b/src/handlers/comment/handlers/allow.ts index 52d22e9bc..a4fb44324 100644 --- a/src/handlers/comment/handlers/allow.ts +++ b/src/handlers/comment/handlers/allow.ts @@ -20,7 +20,7 @@ export const setAccess = async (body: string) => { return; } - const regex = /^\/allow set-(\S+)\s@(\w+)\s(true|false)$/; + const regex = /^\/allow set-(\S+)\s@(\S+)\s(true|false)$/; const matches = body.match(regex); From 67a178b3b21a62096a3d77be7701319a1234ad41 Mon Sep 17 00:00:00 2001 From: HARALD Date: Thu, 31 Aug 2023 18:08:34 +0900 Subject: [PATCH 039/106] feat: minor regex fix --- src/handlers/comment/handlers/allow.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/comment/handlers/allow.ts b/src/handlers/comment/handlers/allow.ts index 52d22e9bc..a4fb44324 100644 --- a/src/handlers/comment/handlers/allow.ts +++ b/src/handlers/comment/handlers/allow.ts @@ -20,7 +20,7 @@ export const setAccess = async (body: string) => { return; } - const regex = /^\/allow set-(\S+)\s@(\w+)\s(true|false)$/; + const regex = /^\/allow set-(\S+)\s@(\S+)\s(true|false)$/; const matches = body.match(regex); From b3b2deaf66e9dddb353e42a310bf9a7d8c000167 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Thu, 31 Aug 2023 14:43:30 +0200 Subject: [PATCH 040/106] fix: filter then map --- src/utils/ajv.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/utils/ajv.ts b/src/utils/ajv.ts index 11a8c87d1..ce632101e 100644 --- a/src/utils/ajv.ts +++ b/src/utils/ajv.ts @@ -29,11 +29,7 @@ export const ajv = addFormats(new Ajv(), { }); export function getAdditionalProperties() { - return ajv.errors?.map((error) => { - if (error.keyword === "additionalProperties") { - return error.params.additionalProperty; - } - }); + return ajv.errors?.filter((error) => error.keyword === "additionalProperties").map((error) => error.params.additionalProperty); } export function validate(scheme: string | Schema, data: unknown): { valid: true; error: undefined } | { valid: false; error: string } { From 7f0b545746b5e1e72871dffdfc620a10cfd23424 Mon Sep 17 00:00:00 2001 From: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Thu, 31 Aug 2023 22:12:07 +0800 Subject: [PATCH 041/106] feat: default to true --- src/adapters/supabase/helpers/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index aa7b4e739..05fc8f4d4 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -266,8 +266,8 @@ export const getAccessLevel = async (username: string, repository: string, label if (!data || !data[`${label_type}_access`]) { logger.info(`Access not found on the database`); - // no access - return false; + // By default, anyone should be able to do anything. + return true; } const accessValues = data[`${label_type}_access`]; From 8beb9431f5e21eb0f0aa8939343f1ae4aa6561ed Mon Sep 17 00:00:00 2001 From: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Thu, 31 Aug 2023 22:12:07 +0800 Subject: [PATCH 042/106] feat: default to true --- src/adapters/supabase/helpers/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index cbd0629f7..da2b04e64 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -331,8 +331,8 @@ export const getAccessLevel = async (username: string, repository: string, label if (!data || !data[`${label_type}_access`]) { logger.info(`Access not found on the database`); - // no access - return false; + // By default, anyone should be able to do anything. + return true; } const accessValues = data[`${label_type}_access`]; From 29008f2da647a5dc907ceb8ea288dd4911f1d804 Mon Sep 17 00:00:00 2001 From: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Thu, 31 Aug 2023 22:40:26 +0800 Subject: [PATCH 043/106] feat: more --- src/adapters/supabase/helpers/client.ts | 6 ++++-- src/configs/shared.ts | 7 +++++++ src/handlers/comment/handlers/allow.ts | 3 ++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index 05fc8f4d4..fd6428ee6 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -4,6 +4,7 @@ import { Issue, UserProfile } from "../../../types"; import { Database } from "../types"; import { InsertPermit, Permit } from "../../../helpers"; import { BigNumber, BigNumberish } from "ethers"; +import { ACCESS_CONTROL_LABELS } from "../../../configs"; /** * @dev Creates a typescript client which will be used to interact with supabase platform @@ -266,8 +267,9 @@ export const getAccessLevel = async (username: string, repository: string, label if (!data || !data[`${label_type}_access`]) { logger.info(`Access not found on the database`); - // By default, anyone should be able to do anything. - return true; + // By default, anyone should be able to set both priority/time labels. + if (label_type == ACCESS_CONTROL_LABELS.priority || label_type == ACCESS_CONTROL_LABELS.time) return true; + else return false; } const accessValues = data[`${label_type}_access`]; diff --git a/src/configs/shared.ts b/src/configs/shared.ts index 2d817b7a0..0c9076fe8 100644 --- a/src/configs/shared.ts +++ b/src/configs/shared.ts @@ -2,6 +2,13 @@ export const COLORS = { default: "ededed", price: "1f883d", }; +export const ACCESS_CONTROL_LABELS = { + priority: "priority", + time: "time", + price: "price", + multiplier: "multiplier", +}; + export const DEFAULT_BOT_DELAY = 100; // 100ms export const DEFAULT_TIME_RANGE_FOR_MAX_ISSUE = 24; export const DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED = true; diff --git a/src/handlers/comment/handlers/allow.ts b/src/handlers/comment/handlers/allow.ts index a4fb44324..26520ad13 100644 --- a/src/handlers/comment/handlers/allow.ts +++ b/src/handlers/comment/handlers/allow.ts @@ -1,5 +1,6 @@ import { upsertAccessControl } from "../../../adapters/supabase"; import { getBotContext, getLogger } from "../../../bindings"; +import { ACCESS_CONTROL_LABELS } from "../../../configs"; import { getUserPermission } from "../../../helpers"; import { Payload } from "../../../types"; @@ -9,7 +10,7 @@ export const setAccess = async (body: string) => { const payload = context.payload as Payload; const sender = payload.sender.login; - const validAccessString = ["priority", "time", "price", "multiplier"]; + const validAccessString = [ACCESS_CONTROL_LABELS.priority, ACCESS_CONTROL_LABELS.time, ACCESS_CONTROL_LABELS.price, ACCESS_CONTROL_LABELS.multiplier]; logger.info(`Received '/allow' command from user: ${sender}`); From 5d3ccd24c4738fd67c86ac122929c3970f3a0e11 Mon Sep 17 00:00:00 2001 From: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Thu, 31 Aug 2023 22:40:26 +0800 Subject: [PATCH 044/106] feat: more --- src/adapters/supabase/helpers/client.ts | 6 ++++-- src/configs/shared.ts | 7 +++++++ src/handlers/comment/handlers/allow.ts | 3 ++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index da2b04e64..951d060f9 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -4,6 +4,7 @@ import { Issue, UserProfile } from "../../../types"; import { Database } from "../types"; import { InsertPermit, Permit } from "../../../helpers"; import { BigNumber, BigNumberish } from "ethers"; +import { ACCESS_CONTROL_LABELS } from "../../../configs"; /** * @dev Creates a typescript client which will be used to interact with supabase platform @@ -331,8 +332,9 @@ export const getAccessLevel = async (username: string, repository: string, label if (!data || !data[`${label_type}_access`]) { logger.info(`Access not found on the database`); - // By default, anyone should be able to do anything. - return true; + // By default, anyone should be able to set both priority/time labels. + if (label_type == ACCESS_CONTROL_LABELS.priority || label_type == ACCESS_CONTROL_LABELS.time) return true; + else return false; } const accessValues = data[`${label_type}_access`]; diff --git a/src/configs/shared.ts b/src/configs/shared.ts index 8c287c4ff..5e1d920bb 100644 --- a/src/configs/shared.ts +++ b/src/configs/shared.ts @@ -2,6 +2,13 @@ export const COLORS = { default: "ededed", price: "1f883d", }; +export const ACCESS_CONTROL_LABELS = { + priority: "priority", + time: "time", + price: "price", + multiplier: "multiplier", +}; + export const DEFAULT_BOT_DELAY = 100; // 100ms export const DEFAULT_TIME_RANGE_FOR_MAX_ISSUE = 24; export const DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED = true; diff --git a/src/handlers/comment/handlers/allow.ts b/src/handlers/comment/handlers/allow.ts index a4fb44324..26520ad13 100644 --- a/src/handlers/comment/handlers/allow.ts +++ b/src/handlers/comment/handlers/allow.ts @@ -1,5 +1,6 @@ import { upsertAccessControl } from "../../../adapters/supabase"; import { getBotContext, getLogger } from "../../../bindings"; +import { ACCESS_CONTROL_LABELS } from "../../../configs"; import { getUserPermission } from "../../../helpers"; import { Payload } from "../../../types"; @@ -9,7 +10,7 @@ export const setAccess = async (body: string) => { const payload = context.payload as Payload; const sender = payload.sender.login; - const validAccessString = ["priority", "time", "price", "multiplier"]; + const validAccessString = [ACCESS_CONTROL_LABELS.priority, ACCESS_CONTROL_LABELS.time, ACCESS_CONTROL_LABELS.price, ACCESS_CONTROL_LABELS.multiplier]; logger.info(`Received '/allow' command from user: ${sender}`); From 65f2920db294eae20a698a7962ae076f987ce5a9 Mon Sep 17 00:00:00 2001 From: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Thu, 31 Aug 2023 23:36:27 +0800 Subject: [PATCH 045/106] Revert "feat: more" This reverts commit 29008f2da647a5dc907ceb8ea288dd4911f1d804. --- src/adapters/supabase/helpers/client.ts | 6 ++---- src/configs/shared.ts | 7 ------- src/handlers/comment/handlers/allow.ts | 3 +-- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index fd6428ee6..05fc8f4d4 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -4,7 +4,6 @@ import { Issue, UserProfile } from "../../../types"; import { Database } from "../types"; import { InsertPermit, Permit } from "../../../helpers"; import { BigNumber, BigNumberish } from "ethers"; -import { ACCESS_CONTROL_LABELS } from "../../../configs"; /** * @dev Creates a typescript client which will be used to interact with supabase platform @@ -267,9 +266,8 @@ export const getAccessLevel = async (username: string, repository: string, label if (!data || !data[`${label_type}_access`]) { logger.info(`Access not found on the database`); - // By default, anyone should be able to set both priority/time labels. - if (label_type == ACCESS_CONTROL_LABELS.priority || label_type == ACCESS_CONTROL_LABELS.time) return true; - else return false; + // By default, anyone should be able to do anything. + return true; } const accessValues = data[`${label_type}_access`]; diff --git a/src/configs/shared.ts b/src/configs/shared.ts index 0c9076fe8..2d817b7a0 100644 --- a/src/configs/shared.ts +++ b/src/configs/shared.ts @@ -2,13 +2,6 @@ export const COLORS = { default: "ededed", price: "1f883d", }; -export const ACCESS_CONTROL_LABELS = { - priority: "priority", - time: "time", - price: "price", - multiplier: "multiplier", -}; - export const DEFAULT_BOT_DELAY = 100; // 100ms export const DEFAULT_TIME_RANGE_FOR_MAX_ISSUE = 24; export const DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED = true; diff --git a/src/handlers/comment/handlers/allow.ts b/src/handlers/comment/handlers/allow.ts index 26520ad13..a4fb44324 100644 --- a/src/handlers/comment/handlers/allow.ts +++ b/src/handlers/comment/handlers/allow.ts @@ -1,6 +1,5 @@ import { upsertAccessControl } from "../../../adapters/supabase"; import { getBotContext, getLogger } from "../../../bindings"; -import { ACCESS_CONTROL_LABELS } from "../../../configs"; import { getUserPermission } from "../../../helpers"; import { Payload } from "../../../types"; @@ -10,7 +9,7 @@ export const setAccess = async (body: string) => { const payload = context.payload as Payload; const sender = payload.sender.login; - const validAccessString = [ACCESS_CONTROL_LABELS.priority, ACCESS_CONTROL_LABELS.time, ACCESS_CONTROL_LABELS.price, ACCESS_CONTROL_LABELS.multiplier]; + const validAccessString = ["priority", "time", "price", "multiplier"]; logger.info(`Received '/allow' command from user: ${sender}`); From 472a822b597d0a9f12ed204f101e6787f321c0ed Mon Sep 17 00:00:00 2001 From: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Thu, 31 Aug 2023 23:37:57 +0800 Subject: [PATCH 046/106] feat: revert of default to true This reverts commit 7f0b545746b5e1e72871dffdfc620a10cfd23424. --- src/adapters/supabase/helpers/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index 05fc8f4d4..aa7b4e739 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -266,8 +266,8 @@ export const getAccessLevel = async (username: string, repository: string, label if (!data || !data[`${label_type}_access`]) { logger.info(`Access not found on the database`); - // By default, anyone should be able to do anything. - return true; + // no access + return false; } const accessValues = data[`${label_type}_access`]; From 07cf8ae5383e00a23ab4ecfd65cc643ac228aa6c Mon Sep 17 00:00:00 2001 From: whilefoo <139262667+whilefoo@users.noreply.github.com> Date: Thu, 31 Aug 2023 17:50:45 +0200 Subject: [PATCH 047/106] feat: access control config (#696) * feat: access control config * feat: types --------- Co-authored-by: whilefoo --- src/bindings/config.ts | 2 ++ src/handlers/access/labels-access.ts | 5 ++++- src/types/config.ts | 7 +++++++ src/utils/helpers.ts | 11 +++++++++++ src/utils/private.ts | 5 ++++- ubiquibot-config-default.json | 3 +++ 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/bindings/config.ts b/src/bindings/config.ts index 9fc6f4ed0..955233253 100644 --- a/src/bindings/config.ts +++ b/src/bindings/config.ts @@ -33,6 +33,7 @@ export const loadConfig = async (context: Context): Promise => { commandSettings, assistivePricing, registerWalletWithVerification, + enableAccessControl, } = await getWideConfig(context); const publicKey = await getScalarKey(process.env.X25519_PRIVATE_KEY); @@ -97,6 +98,7 @@ export const loadConfig = async (context: Context): Promise => { wallet: { registerWalletWithVerification: registerWalletWithVerification, }, + accessControl: enableAccessControl, }; if (botConfig.payout.privateKey == "") { diff --git a/src/handlers/access/labels-access.ts b/src/handlers/access/labels-access.ts index 184a10c0a..78a9d1017 100644 --- a/src/handlers/access/labels-access.ts +++ b/src/handlers/access/labels-access.ts @@ -1,9 +1,12 @@ import { getAccessLevel } from "../../adapters/supabase"; -import { getBotContext, getLogger } from "../../bindings"; +import { getBotConfig, getBotContext, getLogger } from "../../bindings"; import { addCommentToIssue, getUserPermission, removeLabel, addLabelToIssue } from "../../helpers"; import { Payload } from "../../types"; export const handleLabelsAccess = async () => { + const { accessControl } = getBotConfig(); + if (!accessControl.label) return; + const context = getBotContext(); const logger = getLogger(); const payload = context.payload as Payload; diff --git a/src/types/config.ts b/src/types/config.ts index cda063811..02c7e7fcd 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -93,6 +93,12 @@ export const WalletSchema = Type.Object({ registerWalletWithVerification: Type.Boolean(), }); +export const AccessControlSchema = Type.Object({ + label: Type.Boolean(), +}); + +export type AccessControl = Static; + export const BotConfigSchema = Type.Object({ log: LogConfigSchema, price: PriceConfigSchema, @@ -106,6 +112,7 @@ export const BotConfigSchema = Type.Object({ comments: CommentsSchema, command: CommandConfigSchema, wallet: WalletSchema, + accessControl: AccessControlSchema, }); export type BotConfig = Static; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index e3965208b..41b1b1195 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,6 +1,7 @@ import { Incentives } from "./private"; import { Level } from "../adapters/supabase"; import { CommandObj, WideLabel, WideOrgConfig, WideRepoConfig } from "./private"; +import { AccessControl } from "../types"; interface Configs { parsedRepo?: WideRepoConfig; @@ -183,3 +184,13 @@ export const getRegisterWalletWithVerification = ({ parsedRepo, parsedOrg, parse return Boolean(parsedDefault["register-wallet-with-verification"]); } }; + +export const getEnableAccessControl = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): AccessControl => { + if (parsedRepo && parsedRepo["enable-access-control"]) { + return parsedRepo["enable-access-control"]; + } else if (parsedOrg && parsedOrg["enable-access-control"]) { + return parsedOrg["enable-access-control"]; + } else { + return parsedDefault["enable-access-control"] as AccessControl; + } +}; diff --git a/src/utils/private.ts b/src/utils/private.ts index 36ff92f8c..e40bbfa40 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 { Payload } from "../types"; +import { AccessControl, Payload } from "../types"; import { Context } from "probot"; import { getAnalyticsMode, @@ -18,6 +18,7 @@ import { getAssistivePricing, getCommandSettings, getRegisterWalletWithVerification, + getEnableAccessControl, } from "./helpers"; import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json"; @@ -83,6 +84,7 @@ export interface WideConfig { incentives?: Incentives; "default-labels"?: string[]; "register-wallet-with-verification"?: boolean; + "enable-access-control"?: AccessControl; } export type WideRepoConfig = WideConfig; @@ -182,6 +184,7 @@ export const getWideConfig = async (context: Context) => { defaultLabels: getDefaultLabels(configs), promotionComment: getPromotionComment(configs), registerWalletWithVerification: getRegisterWalletWithVerification(configs), + enableAccessControl: getEnableAccessControl(configs), }; return configData; diff --git a/ubiquibot-config-default.json b/ubiquibot-config-default.json index 2cf139e15..ec34e90ae 100644 --- a/ubiquibot-config-default.json +++ b/ubiquibot-config-default.json @@ -85,5 +85,8 @@ "word": 0 } } + }, + "enable-access-control": { + "label": false } } From ba9aab165dcc78379d97ac75f99dd7c1ff87593c Mon Sep 17 00:00:00 2001 From: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Thu, 31 Aug 2023 23:36:27 +0800 Subject: [PATCH 048/106] Revert "feat: more" This reverts commit 29008f2da647a5dc907ceb8ea288dd4911f1d804. --- src/adapters/supabase/helpers/client.ts | 6 ++---- src/configs/shared.ts | 7 ------- src/handlers/comment/handlers/allow.ts | 3 +-- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index 951d060f9..da2b04e64 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -4,7 +4,6 @@ import { Issue, UserProfile } from "../../../types"; import { Database } from "../types"; import { InsertPermit, Permit } from "../../../helpers"; import { BigNumber, BigNumberish } from "ethers"; -import { ACCESS_CONTROL_LABELS } from "../../../configs"; /** * @dev Creates a typescript client which will be used to interact with supabase platform @@ -332,9 +331,8 @@ export const getAccessLevel = async (username: string, repository: string, label if (!data || !data[`${label_type}_access`]) { logger.info(`Access not found on the database`); - // By default, anyone should be able to set both priority/time labels. - if (label_type == ACCESS_CONTROL_LABELS.priority || label_type == ACCESS_CONTROL_LABELS.time) return true; - else return false; + // By default, anyone should be able to do anything. + return true; } const accessValues = data[`${label_type}_access`]; diff --git a/src/configs/shared.ts b/src/configs/shared.ts index 5e1d920bb..8c287c4ff 100644 --- a/src/configs/shared.ts +++ b/src/configs/shared.ts @@ -2,13 +2,6 @@ export const COLORS = { default: "ededed", price: "1f883d", }; -export const ACCESS_CONTROL_LABELS = { - priority: "priority", - time: "time", - price: "price", - multiplier: "multiplier", -}; - export const DEFAULT_BOT_DELAY = 100; // 100ms export const DEFAULT_TIME_RANGE_FOR_MAX_ISSUE = 24; export const DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED = true; diff --git a/src/handlers/comment/handlers/allow.ts b/src/handlers/comment/handlers/allow.ts index 26520ad13..a4fb44324 100644 --- a/src/handlers/comment/handlers/allow.ts +++ b/src/handlers/comment/handlers/allow.ts @@ -1,6 +1,5 @@ import { upsertAccessControl } from "../../../adapters/supabase"; import { getBotContext, getLogger } from "../../../bindings"; -import { ACCESS_CONTROL_LABELS } from "../../../configs"; import { getUserPermission } from "../../../helpers"; import { Payload } from "../../../types"; @@ -10,7 +9,7 @@ export const setAccess = async (body: string) => { const payload = context.payload as Payload; const sender = payload.sender.login; - const validAccessString = [ACCESS_CONTROL_LABELS.priority, ACCESS_CONTROL_LABELS.time, ACCESS_CONTROL_LABELS.price, ACCESS_CONTROL_LABELS.multiplier]; + const validAccessString = ["priority", "time", "price", "multiplier"]; logger.info(`Received '/allow' command from user: ${sender}`); From fac735bdf696d78a84679975b9b5315e371af182 Mon Sep 17 00:00:00 2001 From: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Thu, 31 Aug 2023 23:37:57 +0800 Subject: [PATCH 049/106] feat: revert of default to true This reverts commit 7f0b545746b5e1e72871dffdfc620a10cfd23424. --- src/adapters/supabase/helpers/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index da2b04e64..cbd0629f7 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -331,8 +331,8 @@ export const getAccessLevel = async (username: string, repository: string, label if (!data || !data[`${label_type}_access`]) { logger.info(`Access not found on the database`); - // By default, anyone should be able to do anything. - return true; + // no access + return false; } const accessValues = data[`${label_type}_access`]; From 9cfc6faa9fc3095255f3ce1cc87d21319122faeb Mon Sep 17 00:00:00 2001 From: DevPanther <42383942+devpanther@users.noreply.github.com> Date: Fri, 1 Sep 2023 05:00:52 +0100 Subject: [PATCH 050/106] feat: comment incentives nonce (#692) * chore: adding user id to nonce * chore: use the issue id and user id for nonce * chore: using node id and type of earnings in nonce * chore: linting fix --- src/handlers/payout/action.ts | 2 +- src/handlers/payout/post.ts | 18 +++++++++++------- src/helpers/permit.ts | 10 ++++++++-- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/handlers/payout/action.ts b/src/handlers/payout/action.ts index fd2aaaaac..8c155f3ee 100644 --- a/src/handlers/payout/action.ts +++ b/src/handlers/payout/action.ts @@ -180,7 +180,7 @@ export const handleIssueClosed = async () => { priceInEth = new Decimal(ethers.utils.formatUnits(bountyAmountAfterPenalty, 18)); } - const { txData, payoutUrl } = await generatePermit2Signature(recipient, priceInEth, issue.node_id); + const { txData, payoutUrl } = await generatePermit2Signature(recipient, priceInEth, issue.node_id, assignee.node_id, "ISSUE_ASSIGNEE"); const tokenSymbol = await getTokenSymbol(paymentToken, rpc); const shortenRecipient = shortenEthAddress(recipient, `[ CLAIM ${priceInEth} ${tokenSymbol.toUpperCase()} ]`.length); logger.info(`Posting a payout url to the issue, url: ${payoutUrl}`); diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 200fe5131..371c3d68a 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -64,7 +64,7 @@ export const incentivizeComments = async () => { const issueComments = await getAllIssueComments(issue.number, "full"); logger.info(`Getting the issue comments done. comments: ${JSON.stringify(issueComments)}`); - const issueCommentsByUser: Record = {}; + const issueCommentsByUser: Record = {}; for (const issueComment of issueComments) { const user = issueComment.user; if (user.type == UserType.Bot || user.login == assignee) continue; @@ -77,10 +77,12 @@ export const incentivizeComments = async () => { logger.info(`Skipping to parse the comment because body_html is undefined. comment: ${JSON.stringify(issueComment)}`); continue; } + + // Store the comment along with user's login and node_id if (!issueCommentsByUser[user.login]) { - issueCommentsByUser[user.login] = []; + issueCommentsByUser[user.login] = { id: user.node_id, comments: [] }; } - issueCommentsByUser[user.login].push(issueComment.body_html); + issueCommentsByUser[user.login].comments.push(issueComment.body_html); } const tokenSymbol = await getTokenSymbol(paymentToken, rpc); logger.info(`Filtering by the user type done. commentsByUser: ${JSON.stringify(issueCommentsByUser)}`); @@ -92,8 +94,8 @@ export const incentivizeComments = async () => { const fallbackReward: Record = {}; let comment = `#### Conversation Rewards\n`; for (const user of Object.keys(issueCommentsByUser)) { - const comments = issueCommentsByUser[user]; - const commentsByNode = await parseComments(comments, ItemsToExclude); + const commentsByUser = issueCommentsByUser[user]; + const commentsByNode = await parseComments(commentsByUser.comments, ItemsToExclude); const rewardValue = calculateRewardValue(commentsByNode, incentives); if (rewardValue.equals(0)) { logger.info(`Skipping to generate a permit url because the reward value is 0. user: ${user}`); @@ -107,7 +109,7 @@ export const incentivizeComments = async () => { continue; } if (account) { - const { payoutUrl } = await generatePermit2Signature(account, amountInETH, issue.node_id); + const { payoutUrl } = await generatePermit2Signature(account, amountInETH, issue.node_id, commentsByUser.id, "ISSUE_COMMENTER"); comment = `${comment}### [ **${user}: [ CLAIM ${amountInETH} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n`; reward[user] = payoutUrl; } else { @@ -187,6 +189,7 @@ export const incentivizeCreatorComment = async () => { const tokenSymbol = await getTokenSymbol(paymentToken, rpc); const result = await generatePermitForComments( creator.login, + creator.node_id, [description], issueCreatorMultiplier, incentives, @@ -206,6 +209,7 @@ export const incentivizeCreatorComment = async () => { const generatePermitForComments = async ( user: string, + userId: string, comments: string[], multiplier: number, incentives: Incentives, @@ -229,7 +233,7 @@ const generatePermitForComments = async ( } let comment = `#### Task Creator Reward\n`; if (account) { - const { payoutUrl } = await generatePermit2Signature(account, amountInETH, node_id); + const { payoutUrl } = await generatePermit2Signature(account, amountInETH, node_id, userId, "ISSUE_CREATOR"); comment = `${comment}### [ **${user}: [ CLAIM ${amountInETH} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n`; return { comment, payoutUrl }; } else { diff --git a/src/helpers/permit.ts b/src/helpers/permit.ts index ba1bcaaa0..3e6f21832 100644 --- a/src/helpers/permit.ts +++ b/src/helpers/permit.ts @@ -52,7 +52,13 @@ type TxData = { * * @returns Permit2 url including base64 encocded data */ -export const generatePermit2Signature = async (spender: string, amountInEth: Decimal, identifier: string): Promise<{ txData: TxData; payoutUrl: string }> => { +export const generatePermit2Signature = async ( + spender: string, + amountInEth: Decimal, + identifier: string, + userId = "", + type = "" +): Promise<{ txData: TxData; payoutUrl: string }> => { const { payout: { networkId, privateKey, permitBaseUrl, rpc, paymentToken }, } = getBotConfig(); @@ -69,7 +75,7 @@ export const generatePermit2Signature = async (spender: string, amountInEth: Dec }, // who can transfer the tokens spender: spender, - nonce: BigNumber.from(keccak256(toUtf8Bytes(identifier))), + nonce: BigNumber.from(keccak256(toUtf8Bytes(identifier + userId + type))), // signature deadline deadline: MaxUint256, }; From db00b833e1cc50bed1a4bac85a4463963e161a34 Mon Sep 17 00:00:00 2001 From: Maxima <78043783+web4er@users.noreply.github.com> Date: Fri, 1 Sep 2023 09:10:29 +0500 Subject: [PATCH 051/106] feat: toggle permit generation by external (#697) --- src/handlers/payout/action.ts | 7 +++++-- src/types/config.ts | 1 + ubiquibot-config-default.json | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/handlers/payout/action.ts b/src/handlers/payout/action.ts index 8c155f3ee..76503d662 100644 --- a/src/handlers/payout/action.ts +++ b/src/handlers/payout/action.ts @@ -25,6 +25,7 @@ export const handleIssueClosed = async () => { const { payout: { paymentToken, rpc, permitBaseUrl, networkId, privateKey }, mode: { paymentPermitMaxPrice }, + accessControl, } = getBotConfig(); const logger = getLogger(); const payload = context.payload as Payload; @@ -35,9 +36,11 @@ export const handleIssueClosed = async () => { if (!issue) return; - const userHasPermission = await checkUserPermissionForRepoAndOrg(payload.sender.login, context); + if (accessControl.organization) { + const userHasPermission = await checkUserPermissionForRepoAndOrg(payload.sender.login, context); - if (!userHasPermission) return "Permit generation skipped because this issue has been closed by an external contributor."; + if (!userHasPermission) return "Permit generation skipped because this issue has been closed by an external contributor."; + } const comments = await getAllIssueComments(issue.number); diff --git a/src/types/config.ts b/src/types/config.ts index 02c7e7fcd..79db76d2b 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -95,6 +95,7 @@ export const WalletSchema = Type.Object({ export const AccessControlSchema = Type.Object({ label: Type.Boolean(), + organization: Type.Boolean(), }); export type AccessControl = Static; diff --git a/ubiquibot-config-default.json b/ubiquibot-config-default.json index ec34e90ae..d2cbad717 100644 --- a/ubiquibot-config-default.json +++ b/ubiquibot-config-default.json @@ -87,6 +87,7 @@ } }, "enable-access-control": { - "label": false + "label": false, + "organization": true } } From 9d45aabb50c7c9a49e030dab710a3cacdb92a025 Mon Sep 17 00:00:00 2001 From: rndquu <119500907+rndquu@users.noreply.github.com> Date: Fri, 1 Sep 2023 07:11:19 +0300 Subject: [PATCH 052/106] fix: enable assistive pricing when enable-access-control.label is disabled (#699) --- src/handlers/access/labels-access.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/access/labels-access.ts b/src/handlers/access/labels-access.ts index 78a9d1017..228aa5a38 100644 --- a/src/handlers/access/labels-access.ts +++ b/src/handlers/access/labels-access.ts @@ -5,7 +5,7 @@ import { Payload } from "../../types"; export const handleLabelsAccess = async () => { const { accessControl } = getBotConfig(); - if (!accessControl.label) return; + if (!accessControl.label) return true; const context = getBotContext(); const logger = getLogger(); From a964015eeae988ac176802b42cb5f0b620b2d317 Mon Sep 17 00:00:00 2001 From: rndquu Date: Fri, 1 Sep 2023 16:01:25 +0300 Subject: [PATCH 053/106] feat: support minute labels --- src/helpers/shared.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/helpers/shared.ts b/src/helpers/shared.ts index 646e0d8e9..f851c5220 100644 --- a/src/helpers/shared.ts +++ b/src/helpers/shared.ts @@ -25,6 +25,7 @@ export const calculateWeight = (label: LabelItem | undefined): number => { const matches = label.name.match(/\d+/); const number = matches && matches.length > 0 ? parseInt(matches[0]) || 0 : 0; if (label.name.toLowerCase().includes("priority")) return number; + if (label.name.toLowerCase().includes("minute")) return number * 0.002; if (label.name.toLowerCase().includes("hour")) return number * 0.125; if (label.name.toLowerCase().includes("day")) return 1 + (number - 1) * 0.25; if (label.name.toLowerCase().includes("week")) return number + 1; @@ -37,6 +38,7 @@ export const calculateDuration = (label: LabelItem): number => { const matches = label.name.match(/\d+/); if (label.name.toLowerCase().includes("priority")) return 0; const number = matches && matches.length > 0 ? parseInt(matches[0]) || 0 : 0; + if (label.name.toLowerCase().includes("minute")) return number * 60; if (label.name.toLowerCase().includes("hour")) return number * 3600; if (label.name.toLowerCase().includes("day")) return number * 86400; if (label.name.toLowerCase().includes("week")) return number * 604800; From 3eaedcc59d2813aaabb63533ae55477fbfc63ff0 Mon Sep 17 00:00:00 2001 From: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Fri, 1 Sep 2023 12:52:49 +0800 Subject: [PATCH 054/106] feat: apply price label --- src/handlers/pricing/action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/pricing/action.ts b/src/handlers/pricing/action.ts index cd9dfa8a4..0b8982eaa 100644 --- a/src/handlers/pricing/action.ts +++ b/src/handlers/pricing/action.ts @@ -25,7 +25,7 @@ export const pricingLabelLogic = async (): Promise => { } const valid = await handleLabelsAccess(); - if (!valid) { + if (!valid && config.accessControl.label) { return; } From eb1d942f4a4d033b856ebc20756b73eb162e20e3 Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Sat, 2 Sep 2023 08:05:23 +0000 Subject: [PATCH 055/106] feat: display permission level in /query --- src/handlers/comment/handlers/query.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/handlers/comment/handlers/query.ts b/src/handlers/comment/handlers/query.ts index efee499d1..69f117bfc 100644 --- a/src/handlers/comment/handlers/query.ts +++ b/src/handlers/comment/handlers/query.ts @@ -1,5 +1,6 @@ import { getWalletInfo } from "../../../adapters/supabase"; import { getBotContext, getLogger } from "../../../bindings"; +import { getUserPermission } from "../../../helpers"; import { Payload } from "../../../types"; export const query = async (body: string) => { @@ -22,13 +23,14 @@ export const query = async (body: string) => { const regex = /^\/query\s+@([\w-]+)\s*$/; const matches = body.match(regex); const user = matches?.[1]; + const permissionLevel = await getUserPermission(sender, context); if (user) { const walletInfo = await getWalletInfo(user, id?.toString()); if (!walletInfo?.address) { return `Error retrieving multiplier and wallet address for @${user}`; } else { - return `@${user}'s wallet address is ${walletInfo?.address} and multiplier is ${walletInfo?.multiplier}`; + return `@${user}'s wallet address is ${walletInfo?.address}, multiplier is ${walletInfo?.multiplier} and permission level is ${permissionLevel}`; } } else { logger.error("Invalid body for query command"); From f2a684b67af760a53ca7e16fbe404af8e0ee6a3b Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Sat, 2 Sep 2023 08:31:54 +0000 Subject: [PATCH 056/106] feat: disable bot follow up for pending reviews --- src/handlers/wildcard/unassign.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index 8e06ecebd..6d5feaaea 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -1,5 +1,5 @@ import { closePullRequestForAnIssue } from "../assign"; -import { getBotConfig, getLogger } from "../../bindings"; +import { getBotConfig, getBotContext, getLogger } from "../../bindings"; import { GLOBAL_STRINGS } from "../../configs/strings"; import { addCommentToIssue, @@ -9,7 +9,7 @@ import { listAllIssuesForRepo, removeAssignees, } from "../../helpers"; -import { Comment, Issue, IssueType } from "../../types"; +import { Comment, Issue, IssueType, Payload } from "../../types"; /** * @dev Check out the bounties which haven't been completed within the initial timeline @@ -31,6 +31,8 @@ export const checkBountiesToUnassign = async () => { }; const checkBountyToUnassign = async (issue: Issue): Promise => { + const context = getBotContext(); + const payload = context.payload as Payload; const logger = getLogger(); const { unassign: { followUpTime, disqualifyTime }, @@ -49,7 +51,18 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { const curTimestamp = new Date().getTime(); const lastActivity = await lastActivityTime(issue, comments); const passedDuration = curTimestamp - lastActivity.getTime(); + const pullRequest = await getOpenedPullRequestsForAnIssue(issue.number, issue.assignee); + const { data: reviews } = await context.octokit.pulls.listReviews({ + owner: payload.repository.owner.login, + repo: payload.repository.name, + pull_number: pullRequest[0].id, + }); + + const pendingReviews = reviews.filter((review) => review.state === "PENDING"); + if (pendingReviews.length > 0) { + return false; + } if (passedDuration >= disqualifyTime || passedDuration >= followUpTime) { if (passedDuration >= disqualifyTime) { logger.info( From c86011c0fca458ed53c02ba687674aa534ba15a6 Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Sun, 3 Sep 2023 06:56:34 +0000 Subject: [PATCH 057/106] feat: display access level in /query --- src/handlers/comment/handlers/query.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/handlers/comment/handlers/query.ts b/src/handlers/comment/handlers/query.ts index 69f117bfc..d9a07aac3 100644 --- a/src/handlers/comment/handlers/query.ts +++ b/src/handlers/comment/handlers/query.ts @@ -1,6 +1,5 @@ -import { getWalletInfo } from "../../../adapters/supabase"; +import { getAccessLevel, getWalletInfo } from "../../../adapters/supabase"; import { getBotContext, getLogger } from "../../../bindings"; -import { getUserPermission } from "../../../helpers"; import { Payload } from "../../../types"; export const query = async (body: string) => { @@ -23,14 +22,24 @@ export const query = async (body: string) => { const regex = /^\/query\s+@([\w-]+)\s*$/; const matches = body.match(regex); const user = matches?.[1]; - const permissionLevel = await getUserPermission(sender, context); + const repo = payload.repository; if (user) { const walletInfo = await getWalletInfo(user, id?.toString()); if (!walletInfo?.address) { return `Error retrieving multiplier and wallet address for @${user}`; } else { - return `@${user}'s wallet address is ${walletInfo?.address}, multiplier is ${walletInfo?.multiplier} and permission level is ${permissionLevel}`; + return `@${user}'s wallet address is ${walletInfo?.address}, multiplier is ${walletInfo?.multiplier} and access levels are +| access type | access level | +| ----------- | ------------ |access type access level +multiplier true +priority true +time true +price true +| multiplier | ${getAccessLevel(sender, repo.full_name, "multiplier")} | +| priority | ${getAccessLevel(sender, repo.full_name, "priority")} | +| time | ${getAccessLevel(sender, repo.full_name, "time")} | +| price | ${getAccessLevel(sender, repo.full_name, "price")} |`; } } else { logger.error("Invalid body for query command"); From cf55a97842ba9f59c86e1a7691116ba1efacfc9f Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Sun, 3 Sep 2023 06:57:29 +0000 Subject: [PATCH 058/106] feat: display access level in /query --- src/handlers/comment/handlers/query.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/handlers/comment/handlers/query.ts b/src/handlers/comment/handlers/query.ts index d9a07aac3..6bbc342d6 100644 --- a/src/handlers/comment/handlers/query.ts +++ b/src/handlers/comment/handlers/query.ts @@ -31,11 +31,6 @@ export const query = async (body: string) => { } else { return `@${user}'s wallet address is ${walletInfo?.address}, multiplier is ${walletInfo?.multiplier} and access levels are | access type | access level | -| ----------- | ------------ |access type access level -multiplier true -priority true -time true -price true | multiplier | ${getAccessLevel(sender, repo.full_name, "multiplier")} | | priority | ${getAccessLevel(sender, repo.full_name, "priority")} | | time | ${getAccessLevel(sender, repo.full_name, "time")} | From 8ce9053747e6e80278b68c77f9fbfdaedcb21340 Mon Sep 17 00:00:00 2001 From: HARALD Date: Mon, 4 Sep 2023 18:01:11 +0900 Subject: [PATCH 059/106] feat: fix for nonse in incentive pr review --- src/handlers/payout/post.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 92969028e..00326fe18 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -192,7 +192,7 @@ export const incentivizePullRequestReviews = async () => { const prReviews = await getAllPullRequestReviews(context, linkedPullRequest.number, "full"); logger.info(`Getting the PR reviews done. comments: ${JSON.stringify(prReviews)}`); - const prReviewsByUser: Record = {}; + const prReviewsByUser: Record = {}; for (const review of prReviews) { const user = review.user; if (!user) continue; @@ -202,9 +202,9 @@ export const incentivizePullRequestReviews = async () => { continue; } if (!prReviewsByUser[user.login]) { - prReviewsByUser[user.login] = []; + prReviewsByUser[user.login] = { id: user.node_id, comments: [] }; } - prReviewsByUser[user.login].push(review.body_html); + prReviewsByUser[user.login].comments.push(review.body_html); } const tokenSymbol = await getTokenSymbol(paymentToken, rpc); logger.info(`incentivizePullRequestReviews: Filtering by the user type done. commentsByUser: ${JSON.stringify(prReviewsByUser)}`); @@ -216,8 +216,8 @@ export const incentivizePullRequestReviews = async () => { const fallbackReward: Record = {}; let comment = `#### Reviewer Rewards\n`; for (const user of Object.keys(prReviewsByUser)) { - const comments = prReviewsByUser[user]; - const commentsByNode = await parseComments(comments, ItemsToExclude); + const commentByUser = prReviewsByUser[user]; + const commentsByNode = await parseComments(commentByUser.comments, ItemsToExclude); const rewardValue = calculateRewardValue(commentsByNode, incentives); if (rewardValue.equals(0)) { logger.info(`incentivizePullRequestReviews: Skipping to generate a permit url because the reward value is 0. user: ${user}`); @@ -231,7 +231,7 @@ export const incentivizePullRequestReviews = async () => { continue; } if (account) { - const { payoutUrl } = await generatePermit2Signature(account, amountInETH, issue.node_id); + const { payoutUrl } = await generatePermit2Signature(account, amountInETH, issue.node_id, commentByUser.id, "ISSUE_COMMENTER"); comment = `${comment}### [ **${user}: [ CLAIM ${amountInETH} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n`; reward[user] = payoutUrl; } else { From 2709407cb09acc32fe1b58b426d26c39dc69dd6e Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Mon, 4 Sep 2023 12:59:18 +0000 Subject: [PATCH 060/106] feat: display access levels in /query --- src/adapters/supabase/helpers/client.ts | 21 +++++++++++++++++++++ src/handlers/comment/handlers/query.ts | 14 +++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index cbd0629f7..7e54f904f 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -5,6 +5,13 @@ import { Database } from "../types"; import { InsertPermit, Permit } from "../../../helpers"; import { BigNumber, BigNumberish } from "ethers"; +interface accessLevels { + multiplier: boolean; + price: boolean; + priority: boolean; + time: boolean; +} + /** * @dev Creates a typescript client which will be used to interact with supabase platform * @@ -340,6 +347,20 @@ export const getAccessLevel = async (username: string, repository: string, label return accessValues; }; +export const getAllAccessLevels = async (username: string, repository: string): Promise => { + const logger = getLogger(); + const { supabase } = getAdapters(); + + const { data } = await supabase.from("access").select("*").eq("user_name", username).eq("repository", repository).single(); + + if (!data) { + logger.info(`Access not found on the database`); + // no access + return false; + } + return { multiplier: data["multiplier_access"], time: data["time_access"], priority: data["priority_access"], price: data["price_access"] }; +}; + /** * Queries the wallet address registered previously * diff --git a/src/handlers/comment/handlers/query.ts b/src/handlers/comment/handlers/query.ts index 6bbc342d6..e75e255f4 100644 --- a/src/handlers/comment/handlers/query.ts +++ b/src/handlers/comment/handlers/query.ts @@ -1,4 +1,4 @@ -import { getAccessLevel, getWalletInfo } from "../../../adapters/supabase"; +import { getAccessLevel, getAllAccessLevels, getWalletInfo } from "../../../adapters/supabase"; import { getBotContext, getLogger } from "../../../bindings"; import { Payload } from "../../../types"; @@ -25,16 +25,20 @@ export const query = async (body: string) => { const repo = payload.repository; if (user) { + let data = await getAllAccessLevels(user, repo.full_name); + if (data === false) { + return `Error retrieving access for @${user}`; + } const walletInfo = await getWalletInfo(user, id?.toString()); if (!walletInfo?.address) { return `Error retrieving multiplier and wallet address for @${user}`; } else { return `@${user}'s wallet address is ${walletInfo?.address}, multiplier is ${walletInfo?.multiplier} and access levels are | access type | access level | -| multiplier | ${getAccessLevel(sender, repo.full_name, "multiplier")} | -| priority | ${getAccessLevel(sender, repo.full_name, "priority")} | -| time | ${getAccessLevel(sender, repo.full_name, "time")} | -| price | ${getAccessLevel(sender, repo.full_name, "price")} |`; +| multiplier | ${data["multiplier"]} | +| priority | ${data["priority"]} | +| time | ${data["time"]} | +| price | ${data["price"]} |`; } } else { logger.error("Invalid body for query command"); From 7fd7d3b83aaab0b19673d9c601937d9fdeb3f18c Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 5 Sep 2023 09:04:20 +0100 Subject: [PATCH 061/106] fix: warning header allowing zero input and changed warning --- src/bindings/config.ts | 4 ++-- src/configs/shared.ts | 1 - src/handlers/comment/handlers/assign.ts | 4 ++-- src/handlers/comment/handlers/table.ts | 13 ++++++------- src/types/config.ts | 2 +- src/utils/private.ts | 4 +--- ubiquibot-config-default.json | 18 +++++++++--------- yarn.lock | 18 +++++++++--------- 8 files changed, 30 insertions(+), 34 deletions(-) diff --git a/src/bindings/config.ts b/src/bindings/config.ts index 66cf7e64e..fcc980075 100644 --- a/src/bindings/config.ts +++ b/src/bindings/config.ts @@ -1,7 +1,7 @@ import ms from "ms"; import { BotConfig, BotConfigSchema } from "../types"; -import { DEFAULT_BOT_DELAY, DEFAULT_DISQUALIFY_TIME, DEFAULT_FOLLOWUP_TIME, DEFAULT_PERMIT_BASE_URL, STALE_BOUNTY_TIME } from "../configs"; +import { DEFAULT_BOT_DELAY, DEFAULT_DISQUALIFY_TIME, DEFAULT_FOLLOWUP_TIME, DEFAULT_PERMIT_BASE_URL } from "../configs"; import { getPayoutConfigByNetworkId } from "../helpers"; import { ajv } from "../utils"; import { Context } from "probot"; @@ -77,7 +77,7 @@ export const loadConfig = async (context: Context): Promise => { command: commandSettings, assign: { bountyHunterMax: bountyHunterMax, - staleBountyTime: staleBountyTime ? staleBountyTime : ms(STALE_BOUNTY_TIME), + staleBountyTime: staleBountyTime ? String(ms(staleBountyTime)) : "0", }, sodium: { privateKey: process.env.X25519_PRIVATE_KEY ?? "", diff --git a/src/configs/shared.ts b/src/configs/shared.ts index b90b587ff..2d817b7a0 100644 --- a/src/configs/shared.ts +++ b/src/configs/shared.ts @@ -22,7 +22,6 @@ export const ASSIGN_COMMAND_ENABLED = true; */ export const DEFAULT_FOLLOWUP_TIME = "4 days"; // 4 days export const DEFAULT_DISQUALIFY_TIME = "7 days"; // 7 days -export const STALE_BOUNTY_TIME = "30d"; // 30 days export const DEFAULT_NETWORK_ID = 1; // ethereum export const DEFAULT_RPC_ENDPOINT = "https://rpc-bot.ubq.fi/v1/mainnet"; diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index cbb7df465..d3cf1ef41 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -18,7 +18,7 @@ export const assign = async (body: string) => { const id = organization?.id || repository?.id; // repository?.id as fallback - const staleBounty = config.assign.staleBountyTime; + const staleBounty = Number(config.assign.staleBountyTime); logger.info(`Received '/start' command from user: ${payload.sender.login}, body: ${body}`); const issue = (_payload as Payload).issue; @@ -112,8 +112,8 @@ export const assign = async (body: string) => { await addAssignees(issue.number, [payload.sender.login]); } - const isBountyStale = new Date().getTime() - new Date(issue.created_at).getTime() > staleBounty; const days = Math.floor((new Date().getTime() - new Date(issue.created_at).getTime()) / (1000 * 60 * 60 * 24)); + const isBountyStale = staleBounty == 0 ? null : staleBounty > days ? false : true; // double check whether the assign message has been already posted or not logger.info(`Creating an issue comment: ${comment.commit}`); diff --git a/src/handlers/comment/handlers/table.ts b/src/handlers/comment/handlers/table.ts index bc2349cdf..a130eb2c3 100644 --- a/src/handlers/comment/handlers/table.ts +++ b/src/handlers/comment/handlers/table.ts @@ -12,20 +12,19 @@ export const tableComment = ({ multiplier?: string; reason?: string; bounty?: string; - isBountyStale?: boolean; + isBountyStale?: boolean | null; days?: number; }) => { return `
Ready to begin?You can start right away!This task was created over ${days} days ago. Please verify that it is still current.You can start right away!This task was created over ${days} days ago. Please verify that it is still current before starting work.
Deadline
- - ${ - !isBountyStale - ? `` - : `` + isBountyStale == null + ? null + : isBountyStale + ? `` + : `` } - diff --git a/src/types/config.ts b/src/types/config.ts index 373262377..ff642e221 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -69,7 +69,7 @@ export const ModeSchema = Type.Object({ export const AssignSchema = Type.Object({ bountyHunterMax: Type.Number(), - staleBountyTime: Type.Number(), + staleBountyTime: Type.String(), }); export const LogConfigSchema = Type.Object({ diff --git a/src/utils/private.ts b/src/utils/private.ts index d10ac378e..5adf2adc9 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -21,8 +21,6 @@ import { } from "./helpers"; import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json"; -import { STALE_BOUNTY_TIME } from "../configs"; -import ms from "ms"; const CONFIG_REPO = "ubiquibot-config"; const CONFIG_PATH = ".github/ubiquibot-config.yml"; @@ -187,7 +185,7 @@ export const getWideConfig = async (context: Context) => { defaultLabels: getDefaultLabels(configs), promotionComment: getPromotionComment(configs), registerWalletWithVerification: getRegisterWalletWithVerification(configs), - staleBountyTime: parsedDefault["stale-bounty-time"] == "0" ? ms(STALE_BOUNTY_TIME) : parsedDefault["stale-bounty-time"], + staleBountyTime: parsedDefault["stale-bounty-time"], }; return configData; diff --git a/ubiquibot-config-default.json b/ubiquibot-config-default.json index 5271a5d7f..4aa7ff9ba 100644 --- a/ubiquibot-config-default.json +++ b/ubiquibot-config-default.json @@ -8,7 +8,7 @@ "disable-analytics": false, "comment-incentives": false, "register-wallet-with-verification": false, - "stale-bounty-time": 42060, + "stale-bounty-time": "30d", "promotion-comment": "\n
If you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot!
", "default-labels": [], "time-labels": [ @@ -63,35 +63,35 @@ "command-settings": [ { "name": "start", - "enabled": true + "enabled": false }, { "name": "stop", - "enabled": true + "enabled": false }, { "name": "wallet", - "enabled": true + "enabled": false }, { "name": "payout", - "enabled": true + "enabled": false }, { "name": "multiplier", - "enabled": true + "enabled": false }, { "name": "query", - "enabled": true + "enabled": false }, { "name": "allow", - "enabled": true + "enabled": false }, { "name": "autopay", - "enabled": true + "enabled": false } ], "incentives": { diff --git a/yarn.lock b/yarn.lock index 2d930cf18..59de7f019 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1968,10 +1968,10 @@ dependencies: cross-fetch "^3.1.5" -"@supabase/realtime-js@^2.7.3": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@supabase/realtime-js/-/realtime-js-2.7.3.tgz#cbcb84181add681ab99c87032bfe88101c6863b3" - integrity sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw== +"@supabase/realtime-js@^2.7.4": + version "2.7.4" + resolved "https://registry.yarnpkg.com/@supabase/realtime-js/-/realtime-js-2.7.4.tgz#de41195bd3f2cdd6db82d9f93c4c5b8fae9f809b" + integrity sha512-FzSzs1k9ruh/uds5AJ95Nc3beiMCCIhougExJ3O98CX1LMLAKUKFy5FivKLvcNhXnNfUEL0XUfGMb4UH2J7alg== dependencies: "@types/phoenix" "^1.5.4" "@types/websocket" "^1.0.3" @@ -1984,15 +1984,15 @@ dependencies: cross-fetch "^3.1.5" -"@supabase/supabase-js@^2.32.0": - version "2.32.0" - resolved "https://registry.yarnpkg.com/@supabase/supabase-js/-/supabase-js-2.32.0.tgz#863c636d83232c6a2e9ba5932e0d7c1bf80bc436" - integrity sha512-1ShFhuOI5Du7604nlCelBsRD61daXk2O0qwjumoz35bqrYThnSPPtpJqZOHw6Mg6o7mLjIInYLh/DBlh8UvzRg== +"@supabase/supabase-js@^2.4.0": + version "2.33.1" + resolved "https://registry.yarnpkg.com/@supabase/supabase-js/-/supabase-js-2.33.1.tgz#2407861afe63c2817d030514c87a745f78dfe68a" + integrity sha512-jA00rquPTppPOHpBB6KABW98lfg0gYXcuGqP3TB1iiduznRVsi3GGk2qBKXPDLMYSe0kRlQp5xCwWWthaJr8eA== dependencies: "@supabase/functions-js" "^2.1.0" "@supabase/gotrue-js" "^2.46.1" "@supabase/postgrest-js" "^1.8.0" - "@supabase/realtime-js" "^2.7.3" + "@supabase/realtime-js" "^2.7.4" "@supabase/storage-js" "^2.5.1" cross-fetch "^3.1.5" From dc1d7ec75c5ab4cd374545d92be31ff918e11615 Mon Sep 17 00:00:00 2001 From: ByteBallet Date: Tue, 5 Sep 2023 04:02:06 -0500 Subject: [PATCH 062/106] fix: ignore param order for allow --- src/handlers/comment/handlers/allow.ts | 33 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/handlers/comment/handlers/allow.ts b/src/handlers/comment/handlers/allow.ts index a4fb44324..543c88047 100644 --- a/src/handlers/comment/handlers/allow.ts +++ b/src/handlers/comment/handlers/allow.ts @@ -20,12 +20,39 @@ export const setAccess = async (body: string) => { return; } - const regex = /^\/allow set-(\S+)\s@(\S+)\s(true|false)$/; - + const regex = + /^\/allow (?:set-(\S+)\s@(\w+)\s(true|false)|set-(\S+)\s(true|false)\s@(\w+)|(true|false)\sset-(\S+)\s@(\w+)|(true|false)\s@(\w+)\sset-(\S+)|@(\w+)\s(true|false)\sset-(\S+)|@(\w+)\sset-(\S+)\s(true|false))$/; const matches = body.match(regex); if (matches) { - const [, accessType, username, bool] = matches; + let accessType, username, bool; + if (matches[1]) { + accessType = matches[1]; + username = matches[2]; + bool = matches[3]; + } else if (matches[4]) { + username = matches[4]; + accessType = matches[5]; + bool = matches[6]; + } else if (matches[7]) { + bool = matches[7]; + accessType = matches[8]; + username = matches[9]; + } else if (matches[10]) { + accessType = matches[10]; + bool = matches[11]; + username = matches[12]; + } else if (matches[13]) { + username = matches[13]; + bool = matches[14]; + accessType = matches[15]; + } else if (matches[16]) { + bool = matches[16]; + username = matches[17]; + accessType = matches[18]; + } else { + bool = username = accessType = ""; + } // Check if access control demand is valid if (!validAccessString.includes(accessType)) { From 4d98c130689997385e24a97a9bfb1dd3377cb45b Mon Sep 17 00:00:00 2001 From: ByteBallet Date: Tue, 5 Sep 2023 04:18:03 -0500 Subject: [PATCH 063/106] fix: ignore param order for allow --- src/handlers/comment/handlers/allow.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/handlers/comment/handlers/allow.ts b/src/handlers/comment/handlers/allow.ts index 543c88047..bc7e86d4c 100644 --- a/src/handlers/comment/handlers/allow.ts +++ b/src/handlers/comment/handlers/allow.ts @@ -31,25 +31,25 @@ export const setAccess = async (body: string) => { username = matches[2]; bool = matches[3]; } else if (matches[4]) { - username = matches[4]; - accessType = matches[5]; - bool = matches[6]; + accessType = matches[4]; + bool = matches[5]; + username = matches[6]; } else if (matches[7]) { bool = matches[7]; accessType = matches[8]; username = matches[9]; } else if (matches[10]) { - accessType = matches[10]; - bool = matches[11]; - username = matches[12]; + bool = matches[10]; + username = matches[11]; + accessType = matches[12]; } else if (matches[13]) { username = matches[13]; bool = matches[14]; accessType = matches[15]; } else if (matches[16]) { - bool = matches[16]; - username = matches[17]; - accessType = matches[18]; + username = matches[16]; + accessType = matches[17]; + bool = matches[18]; } else { bool = username = accessType = ""; } From 74d1b4ae682ff2dcf3e6d5cfa087a19a0de048e8 Mon Sep 17 00:00:00 2001 From: ByteBallet Date: Tue, 5 Sep 2023 06:53:24 -0500 Subject: [PATCH 064/106] fix: check both pr and issue to verify first comment --- src/handlers/comment/handlers/first.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/handlers/comment/handlers/first.ts b/src/handlers/comment/handlers/first.ts index c14d50702..1e7400a35 100644 --- a/src/handlers/comment/handlers/first.ts +++ b/src/handlers/comment/handlers/first.ts @@ -10,14 +10,19 @@ export const verifyFirstCheck = async (): Promise => { if (!payload.issue) return; try { - const response = await context.octokit.rest.search.issuesAndPullRequests({ + const response_issue = await context.octokit.rest.search.issuesAndPullRequests({ q: `is:issue repo:${payload.repository.owner.login}/${payload.repository.name} commenter:${payload.sender.login}`, per_page: 2, }); - if (response.data.total_count === 1) { + const response_pr = await context.octokit.rest.search.issuesAndPullRequests({ + q: `is:pull-request repo:${payload.repository.owner.login}/${payload.repository.name} commenter:${payload.sender.login}`, + per_page: 2, + }); + if (response_issue.data.total_count + response_pr.data.total_count === 1) { //continue_first_search + const data = response_issue.data.total_count > 0 ? response_issue.data : response_pr.data; const resp = await context.octokit.rest.issues.listComments({ - issue_number: response.data.items[0].number, + issue_number: data.items[0].number, owner: payload.repository.owner.login, repo: payload.repository.name, per_page: 100, From 2855d0bc61da6c3acfa9ca0d395af80e5bc4e7fe Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:53:37 +0100 Subject: [PATCH 065/106] fix: config extraction following extraction pattern --- src/handlers/comment/handlers/assign.ts | 3 ++- src/utils/helpers.ts | 11 +++++++++++ src/utils/private.ts | 3 ++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index d3cf1ef41..31441f160 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -113,7 +113,8 @@ export const assign = async (body: string) => { } const days = Math.floor((new Date().getTime() - new Date(issue.created_at).getTime()) / (1000 * 60 * 60 * 24)); - const isBountyStale = staleBounty == 0 ? null : staleBounty > days ? false : true; + const staleToDays = Math.floor(staleBounty / (1000 * 60 * 60 * 24)); + const isBountyStale = staleBounty == 0 ? null : staleToDays > days ? false : true; // double check whether the assign message has been already posted or not logger.info(`Creating an issue comment: ${comment.commit}`); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index e3965208b..46531df7c 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -183,3 +183,14 @@ export const getRegisterWalletWithVerification = ({ parsedRepo, parsedOrg, parse return Boolean(parsedDefault["register-wallet-with-verification"]); } }; + +// we use ms library so the value is a a string like "30d" which we convert to a number inside the business logic +export const getStaleBountyTime = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): string => { + if (parsedRepo && parsedRepo["stale-bounty-time"] !== undefined && typeof parsedRepo["stale-bounty-time"] === "string") { + return parsedRepo["stale-bounty-time"]; + } else if (parsedOrg && parsedOrg["stale-bounty-time"] !== undefined && typeof parsedOrg["stale-bounty-time"] === "string") { + return parsedOrg["stale-bounty-time"]; + } else { + return parsedDefault["stale-bounty-time"] as string; + } +}; diff --git a/src/utils/private.ts b/src/utils/private.ts index 5adf2adc9..acdcb0665 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -18,6 +18,7 @@ import { getAssistivePricing, getCommandSettings, getRegisterWalletWithVerification, + getStaleBountyTime, } from "./helpers"; import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json"; @@ -185,7 +186,7 @@ export const getWideConfig = async (context: Context) => { defaultLabels: getDefaultLabels(configs), promotionComment: getPromotionComment(configs), registerWalletWithVerification: getRegisterWalletWithVerification(configs), - staleBountyTime: parsedDefault["stale-bounty-time"], + staleBountyTime: getStaleBountyTime(configs), }; return configData; From 5eb77097456e044c6334278c71dafd7696c76b11 Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Wed, 6 Sep 2023 06:02:07 +0000 Subject: [PATCH 066/106] feat: disable bot follow up for pending reviews --- src/handlers/wildcard/unassign.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index 6d5feaaea..c75896c51 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -6,6 +6,7 @@ import { getAllIssueComments, getCommitsOnPullRequest, getOpenedPullRequestsForAnIssue, + getPullRequestReviews, listAllIssuesForRepo, removeAssignees, } from "../../helpers"; @@ -53,11 +54,7 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { const passedDuration = curTimestamp - lastActivity.getTime(); const pullRequest = await getOpenedPullRequestsForAnIssue(issue.number, issue.assignee); - const { data: reviews } = await context.octokit.pulls.listReviews({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - pull_number: pullRequest[0].id, - }); + const reviews = await getPullRequestReviews(context, pullRequest[0].id, 30, 1); const pendingReviews = reviews.filter((review) => review.state === "PENDING"); if (pendingReviews.length > 0) { From 9479e6255ef92277a95bc8e452559306b9c69b82 Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Wed, 6 Sep 2023 06:06:42 +0000 Subject: [PATCH 067/106] feat: display access levels in /query --- src/adapters/supabase/helpers/client.ts | 6 +++--- src/handlers/comment/handlers/query.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index 7e54f904f..427fc1a04 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -5,7 +5,7 @@ import { Database } from "../types"; import { InsertPermit, Permit } from "../../../helpers"; import { BigNumber, BigNumberish } from "ethers"; -interface accessLevels { +interface AccessLevels { multiplier: boolean; price: boolean; priority: boolean; @@ -347,7 +347,7 @@ export const getAccessLevel = async (username: string, repository: string, label return accessValues; }; -export const getAllAccessLevels = async (username: string, repository: string): Promise => { +export const getAllAccessLevels = async (username: string, repository: string): Promise => { const logger = getLogger(); const { supabase } = getAdapters(); @@ -356,7 +356,7 @@ export const getAllAccessLevels = async (username: string, repository: string): if (!data) { logger.info(`Access not found on the database`); // no access - return false; + return null; } return { multiplier: data["multiplier_access"], time: data["time_access"], priority: data["priority_access"], price: data["price_access"] }; }; diff --git a/src/handlers/comment/handlers/query.ts b/src/handlers/comment/handlers/query.ts index e75e255f4..74d37c8ce 100644 --- a/src/handlers/comment/handlers/query.ts +++ b/src/handlers/comment/handlers/query.ts @@ -26,7 +26,7 @@ export const query = async (body: string) => { if (user) { let data = await getAllAccessLevels(user, repo.full_name); - if (data === false) { + if (!data) { return `Error retrieving access for @${user}`; } const walletInfo = await getWalletInfo(user, id?.toString()); @@ -35,10 +35,10 @@ export const query = async (body: string) => { } else { return `@${user}'s wallet address is ${walletInfo?.address}, multiplier is ${walletInfo?.multiplier} and access levels are | access type | access level | -| multiplier | ${data["multiplier"]} | -| priority | ${data["priority"]} | -| time | ${data["time"]} | -| price | ${data["price"]} |`; +| multiplier | ${data.multiplier} | +| priority | ${data.priority} | +| time | ${data.time} | +| price | ${data.price} |`; } } else { logger.error("Invalid body for query command"); From ab87dd4f034ec35256b58408269558253e68ce02 Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Wed, 6 Sep 2023 06:08:25 +0000 Subject: [PATCH 068/106] feat: display access levels in /query --- src/handlers/comment/handlers/query.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/comment/handlers/query.ts b/src/handlers/comment/handlers/query.ts index 74d37c8ce..3f5a9f0cf 100644 --- a/src/handlers/comment/handlers/query.ts +++ b/src/handlers/comment/handlers/query.ts @@ -1,4 +1,4 @@ -import { getAccessLevel, getAllAccessLevels, getWalletInfo } from "../../../adapters/supabase"; +import { getAllAccessLevels, getWalletInfo } from "../../../adapters/supabase"; import { getBotContext, getLogger } from "../../../bindings"; import { Payload } from "../../../types"; From 467d7ebd1def83cd2823dfa3069a7d09d1d462e2 Mon Sep 17 00:00:00 2001 From: ByteBallet Date: Wed, 6 Sep 2023 02:35:47 -0500 Subject: [PATCH 069/106] fix: update method for params --- src/handlers/comment/handlers/allow.ts | 38 ++++++-------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/src/handlers/comment/handlers/allow.ts b/src/handlers/comment/handlers/allow.ts index bc7e86d4c..aa867985c 100644 --- a/src/handlers/comment/handlers/allow.ts +++ b/src/handlers/comment/handlers/allow.ts @@ -20,40 +20,20 @@ export const setAccess = async (body: string) => { return; } - const regex = - /^\/allow (?:set-(\S+)\s@(\w+)\s(true|false)|set-(\S+)\s(true|false)\s@(\w+)|(true|false)\sset-(\S+)\s@(\w+)|(true|false)\s@(\w+)\sset-(\S+)|@(\w+)\s(true|false)\sset-(\S+)|@(\w+)\sset-(\S+)\s(true|false))$/; + const regex = /\/allow\s+(\S+)\s+(\S+)\s+(\S+)/; const matches = body.match(regex); if (matches) { let accessType, username, bool; - if (matches[1]) { - accessType = matches[1]; - username = matches[2]; - bool = matches[3]; - } else if (matches[4]) { - accessType = matches[4]; - bool = matches[5]; - username = matches[6]; - } else if (matches[7]) { - bool = matches[7]; - accessType = matches[8]; - username = matches[9]; - } else if (matches[10]) { - bool = matches[10]; - username = matches[11]; - accessType = matches[12]; - } else if (matches[13]) { - username = matches[13]; - bool = matches[14]; - accessType = matches[15]; - } else if (matches[16]) { - username = matches[16]; - accessType = matches[17]; - bool = matches[18]; - } else { - bool = username = accessType = ""; + matches.slice(1).forEach((part) => { + if (part.startsWith("@")) username = part.slice(1); + else if (part.startsWith("set-")) accessType = part.slice(4); + else if (part === "true" || part === "false") bool = part; + }); + if (!accessType || !username || !bool) { + logger.error("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 if (!validAccessString.includes(accessType)) { logger.info(`Access control setting for ${accessType} does not exist.`); From edbbe97504315fc58b055977311f3ac5f45e1f6d Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Thu, 7 Sep 2023 09:12:13 +0100 Subject: [PATCH 070/106] fix: conditional removed null and storing time with number --- src/bindings/config.ts | 2 +- src/handlers/comment/handlers/assign.ts | 16 ++++++++++++---- src/handlers/comment/handlers/table.ts | 6 ++---- src/types/config.ts | 2 +- ubiquibot-config-default.json | 2 +- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/bindings/config.ts b/src/bindings/config.ts index b8f95fff7..94cc00301 100644 --- a/src/bindings/config.ts +++ b/src/bindings/config.ts @@ -91,7 +91,7 @@ export const loadConfig = async (context: Context): Promise => { command: commandSettings, assign: { bountyHunterMax: bountyHunterMax, - staleBountyTime: staleBountyTime ? String(ms(staleBountyTime)) : "0", + staleBountyTime: ms(staleBountyTime), }, sodium: { privateKey: process.env.X25519_PRIVATE_KEY ?? "", diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index 742335969..8fbf9920d 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -18,7 +18,7 @@ export const assign = async (body: string) => { const id = organization?.id || repository?.id; // repository?.id as fallback - const staleBounty = Number(config.assign.staleBountyTime); + const staleBounty = config.assign.staleBountyTime; logger.info(`Received '/start' command from user: ${payload.sender.login}, body: ${body}`); const issue = (_payload as Payload).issue; @@ -112,9 +112,17 @@ export const assign = async (body: string) => { await addAssignees(issue.number, [payload.sender.login]); } - const days = Math.floor((new Date().getTime() - new Date(issue.created_at).getTime()) / (1000 * 60 * 60 * 24)); - const staleToDays = Math.floor(staleBounty / (1000 * 60 * 60 * 24)); - const isBountyStale = staleBounty == 0 ? null : staleToDays > days ? false : true; + let days; + Number; + let staleToDays; + Number; + let isBountyStale = false; + + if (staleBounty !== 0) { + days = Math.floor((new Date().getTime() - new Date(issue.created_at).getTime()) / (1000 * 60 * 60 * 24)); + staleToDays = Math.floor(staleBounty / (1000 * 60 * 60 * 24)); + isBountyStale = staleToDays > days ? false : true; + } // double check whether the assign message has been already posted or not logger.info(`Creating an issue comment: ${comment.commit}`); diff --git a/src/handlers/comment/handlers/table.ts b/src/handlers/comment/handlers/table.ts index a130eb2c3..00093ce4e 100644 --- a/src/handlers/comment/handlers/table.ts +++ b/src/handlers/comment/handlers/table.ts @@ -12,16 +12,14 @@ export const tableComment = ({ multiplier?: string; reason?: string; bounty?: string; - isBountyStale?: boolean | null; + isBountyStale?: boolean; days?: number; }) => { return `
Ready to begin?You can start right away!This task was created over ${days} days ago. Please verify that it is still current before starting work.Warning! This task was created over ${days} days ago. Please confirm that this issue specification is accurate before starting.
Deadline ${deadline}
${ - isBountyStale == null - ? null - : isBountyStale + isBountyStale ? `` : `` } diff --git a/src/types/config.ts b/src/types/config.ts index aa297232f..f31954f17 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -69,7 +69,7 @@ export const ModeSchema = Type.Object({ export const AssignSchema = Type.Object({ bountyHunterMax: Type.Number(), - staleBountyTime: Type.String(), + staleBountyTime: Type.Number(), }); export const LogConfigSchema = Type.Object({ diff --git a/ubiquibot-config-default.json b/ubiquibot-config-default.json index a05e26020..eb9496538 100644 --- a/ubiquibot-config-default.json +++ b/ubiquibot-config-default.json @@ -8,7 +8,7 @@ "disable-analytics": false, "comment-incentives": false, "register-wallet-with-verification": false, - "stale-bounty-time": "30d", + "stale-bounty-time": "0d", "promotion-comment": "\n
If you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot!
", "default-labels": [], "time-labels": [ From 408a3d9e632752d03a1e9ab05a50f88eaea4bdef Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Thu, 7 Sep 2023 09:00:15 +0000 Subject: [PATCH 071/106] feat: display access level in /qurey --- src/handlers/comment/handlers/query.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/handlers/comment/handlers/query.ts b/src/handlers/comment/handlers/query.ts index 3f5a9f0cf..baf9467a4 100644 --- a/src/handlers/comment/handlers/query.ts +++ b/src/handlers/comment/handlers/query.ts @@ -25,7 +25,7 @@ export const query = async (body: string) => { const repo = payload.repository; if (user) { - let data = await getAllAccessLevels(user, repo.full_name); + const data = await getAllAccessLevels(user, repo.full_name); if (!data) { return `Error retrieving access for @${user}`; } @@ -35,10 +35,10 @@ export const query = async (body: string) => { } else { return `@${user}'s wallet address is ${walletInfo?.address}, multiplier is ${walletInfo?.multiplier} and access levels are | access type | access level | -| multiplier | ${data.multiplier} | -| priority | ${data.priority} | -| time | ${data.time} | -| price | ${data.price} |`; +| multiplier | ${data.multiplier} | +| priority | ${data.priority} | +| time | ${data.time} | +| price | ${data.price} |`; } } else { logger.error("Invalid body for query command"); From f3e557d85127af98254834b20f2cf95eaa40295b Mon Sep 17 00:00:00 2001 From: whilefoo <139262667+whilefoo@users.noreply.github.com> Date: Thu, 7 Sep 2023 10:59:40 +0200 Subject: [PATCH 072/106] fix: types --- src/handlers/comment/handlers/assign.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index 8fbf9920d..e0a79c5ec 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -112,10 +112,8 @@ export const assign = async (body: string) => { await addAssignees(issue.number, [payload.sender.login]); } - let days; - Number; - let staleToDays; - Number; + let days: Number; + let staleToDays: Number; let isBountyStale = false; if (staleBounty !== 0) { From 5e67a76a1bded48465f0cc7ccda38604b0aaebce Mon Sep 17 00:00:00 2001 From: whilefoo <139262667+whilefoo@users.noreply.github.com> Date: Thu, 7 Sep 2023 11:05:15 +0200 Subject: [PATCH 073/106] fix: types and explicit
--- src/handlers/comment/handlers/assign.ts | 4 ++-- src/handlers/comment/handlers/table.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index e0a79c5ec..2c9b57586 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -112,8 +112,8 @@ export const assign = async (body: string) => { await addAssignees(issue.number, [payload.sender.login]); } - let days: Number; - let staleToDays: Number; + let days: number | undefined; + let staleToDays: number | undefined; let isBountyStale = false; if (staleBounty !== 0) { diff --git a/src/handlers/comment/handlers/table.ts b/src/handlers/comment/handlers/table.ts index 00093ce4e..8f22d664f 100644 --- a/src/handlers/comment/handlers/table.ts +++ b/src/handlers/comment/handlers/table.ts @@ -20,7 +20,7 @@ export const tableComment = ({
Warning! This task was created over ${days} days ago. Please confirm that this issue specification is accurate before starting.
${ isBountyStale - ? `` + ? `` : `` } From e628f63abe7d3e740c12bf1d21af324c8a90a62d Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Thu, 7 Sep 2023 09:29:36 +0000 Subject: [PATCH 074/106] feat: display access level in /qurey --- src/handlers/comment/handlers/query.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/handlers/comment/handlers/query.ts b/src/handlers/comment/handlers/query.ts index baf9467a4..8f78c537f 100644 --- a/src/handlers/comment/handlers/query.ts +++ b/src/handlers/comment/handlers/query.ts @@ -34,11 +34,14 @@ export const query = async (body: string) => { return `Error retrieving multiplier and wallet address for @${user}`; } else { return `@${user}'s wallet address is ${walletInfo?.address}, multiplier is ${walletInfo?.multiplier} and access levels are -| access type | access level | -| multiplier | ${data.multiplier} | -| priority | ${data.priority} | -| time | ${data.time} | -| price | ${data.price} |`; + +| access type | access level | +| ----------- | ------------------- | +| multiplier | ${data.multiplier} | +| priority | ${data.priority} | +| time | ${data.time} | +| price | ${data.price} | + `; } } else { logger.error("Invalid body for query command"); From 747beccde04a29e09411062a9e24623862b624e9 Mon Sep 17 00:00:00 2001 From: whilefoo <139262667+whilefoo@users.noreply.github.com> Date: Thu, 7 Sep 2023 11:55:27 +0200 Subject: [PATCH 075/106] chore: refactor --- src/handlers/comment/handlers/assign.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index 2c9b57586..16dc69f9b 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -119,7 +119,7 @@ export const assign = async (body: string) => { if (staleBounty !== 0) { days = Math.floor((new Date().getTime() - new Date(issue.created_at).getTime()) / (1000 * 60 * 60 * 24)); staleToDays = Math.floor(staleBounty / (1000 * 60 * 60 * 24)); - isBountyStale = staleToDays > days ? false : true; + isBountyStale = days >= staleToDays; } // double check whether the assign message has been already posted or not From 08b7cc0c8864aa9370a267c961f6c5275578d11b Mon Sep 17 00:00:00 2001 From: whilefoo <139262667+whilefoo@users.noreply.github.com> Date: Thu, 7 Sep 2023 13:46:14 +0200 Subject: [PATCH 076/106] chore: refactor --- src/adapters/supabase/helpers/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index 427fc1a04..eaa7d3d3f 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -358,7 +358,7 @@ export const getAllAccessLevels = async (username: string, repository: string): // no access return null; } - return { multiplier: data["multiplier_access"], time: data["time_access"], priority: data["priority_access"], price: data["price_access"] }; + return { multiplier: data.multiplier_access, time: data.time_access, priority: data.priority_access, price: data.price_access }; }; /** From d959564b332e99f57e1f2aad21718b7cad8234b7 Mon Sep 17 00:00:00 2001 From: me505 <62057938+me505@users.noreply.github.com> Date: Thu, 7 Sep 2023 12:23:17 +0000 Subject: [PATCH 077/106] chore: reword ot comment on issues not closed as completed --- src/handlers/payout/action.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/payout/action.ts b/src/handlers/payout/action.ts index 76503d662..237a374b6 100644 --- a/src/handlers/payout/action.ts +++ b/src/handlers/payout/action.ts @@ -98,8 +98,8 @@ export const handleIssueClosed = async () => { return "Permit generation skipped because wallet private key is not set"; } if (issue.state_reason !== StateReason.COMPLETED) { - logger.info("Permit generation skipped because the issue was not closed as completed"); - return "Permit generation skipped because the issue was not closed as completed"; + logger.info("Permit generation skipped because this is marked as unplanned."); + return "Permit generation skipped because this is marked as unplanned."; } logger.info(`Checking if the issue is a parent issue.`); From c02596b8bf37e9ed5189e166eceb4d8bc831adc1 Mon Sep 17 00:00:00 2001 From: 0xCodercrane <108444211+0xcodercrane@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:05:02 +0800 Subject: [PATCH 078/106] chore: update src/adapters/supabase/helpers/client.ts --- src/adapters/supabase/helpers/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index a833d40cc..cd1063d76 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -188,7 +188,7 @@ export const upsertWalletAddress = async (username: string, address: string): Pr logger.error(`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)} }`); + logger.info(`Creating a new wallet_table record done, { data: ${JSON.stringify(data)}, address: $address }`); } }; From 4196f309256e20b79752fbec99603e6acbc9e161 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Fri, 8 Sep 2023 09:27:23 +0100 Subject: [PATCH 079/106] fix: skip zero penalty skips penalty and comment if zero --- src/handlers/comment/handlers/index.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/handlers/comment/handlers/index.ts b/src/handlers/comment/handlers/index.ts index 5470b3062..eb02da55e 100644 --- a/src/handlers/comment/handlers/index.ts +++ b/src/handlers/comment/handlers/index.ts @@ -176,18 +176,22 @@ export const issueReopenedCallback = async (): Promise => { } const assignee = events[0].assignee.login; - // write penalty to db - try { - await addPenalty(assignee, repository.full_name, tokenAddress, networkId.toString(), amount); - } catch (err) { - logger.error(`Error writing penalty to db: ${err}`); - return; - } + if (formattedAmount != "0.0") { + // write penalty to db + try { + await addPenalty(assignee, repository.full_name, tokenAddress, networkId.toString(), amount); + } catch (err) { + logger.error(`Error writing penalty to db: ${err}`); + return; + } - await addCommentToIssue( - `@${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 - ); + await addCommentToIssue( + `@${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 + ); + } else { + logger.info(`Skipped penalty because amount is 0`); + } } catch (err: unknown) { await addCommentToIssue(`Error: ${err}`, issue.number); } From 0cbf1885fd04246f208cd4adf3958143213cc657 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Fri, 8 Sep 2023 09:37:06 +0100 Subject: [PATCH 080/106] fix: error diff commenting errors using diff --- src/handlers/comment/action.ts | 3 ++- src/handlers/comment/handlers/index.ts | 7 ++++--- src/utils/helpers.ts | 6 +++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/handlers/comment/action.ts b/src/handlers/comment/action.ts index 84c54e6d5..08f7ebefe 100644 --- a/src/handlers/comment/action.ts +++ b/src/handlers/comment/action.ts @@ -1,5 +1,6 @@ import { getBotConfig, getBotContext, getLogger } from "../../bindings"; import { Payload } from "../../types"; +import { ErrorDiff } from "../../utils/helpers"; import { IssueCommentCommands } from "./commands"; import { commentParser, userCommands } from "./handlers"; import { verifyFirstCheck } from "./handlers/first"; @@ -54,7 +55,7 @@ export const handleComment = async (): Promise => { if (failureComment) { await callback(issue.number, failureComment, payload.action, payload.comment); } - await callback(issue.number, `Error: ${err}`, payload.action, payload.comment); + await callback(issue.number, ErrorDiff(err), payload.action, payload.comment); } } else { logger.info(`Skipping for a command: ${command}`); diff --git a/src/handlers/comment/handlers/index.ts b/src/handlers/comment/handlers/index.ts index eb02da55e..9c4f3900b 100644 --- a/src/handlers/comment/handlers/index.ts +++ b/src/handlers/comment/handlers/index.ts @@ -27,6 +27,7 @@ import { handleIssueClosed } from "../../payout"; import { query } from "./query"; import { autoPay } from "./payout"; import { getTargetPriceLabel } from "../../shared"; +import { ErrorDiff } from "../../../utils/helpers"; export * from "./assign"; export * from "./wallet"; @@ -71,7 +72,7 @@ export const issueClosedCallback = async (): Promise => { const comment = await handleIssueClosed(); if (comment) await addCommentToIssue(comment + comments.promotionComment, issue.number); } catch (err: unknown) { - return await addCommentToIssue(`Error: ${err}`, issue.number); + return await addCommentToIssue(ErrorDiff(err), issue.number); } }; @@ -112,7 +113,7 @@ export const issueCreatedCallback = async (): Promise => { await addLabelToIssue(targetPriceLabel); } } catch (err: unknown) { - await addCommentToIssue(`Error: ${err}`, issue.number); + await addCommentToIssue(ErrorDiff(err), issue.number); } }; @@ -193,7 +194,7 @@ export const issueReopenedCallback = async (): Promise => { logger.info(`Skipped penalty because amount is 0`); } } catch (err: unknown) { - await addCommentToIssue(`Error: ${err}`, issue.number); + await addCommentToIssue(ErrorDiff(err), issue.number); } }; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 33261629b..61949e6ca 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -19,4 +19,8 @@ export const getNumericLevel = (level: Level) => { default: return -1; // Invalid level } -}; \ No newline at end of file +}; + +export const ErrorDiff = (message: unknown) => { + return ` ` + "```diff \n" + `- ${message}` + "```" + ` `; +}; From e3529bbd3291d2ba82eac3620868470a64d3bfa3 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Fri, 8 Sep 2023 11:22:58 +0100 Subject: [PATCH 081/106] fix: rendering fixed --- src/utils/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 61949e6ca..aa5a377ff 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -22,5 +22,5 @@ export const getNumericLevel = (level: Level) => { }; export const ErrorDiff = (message: unknown) => { - return ` ` + "```diff \n" + `- ${message}` + "```" + ` `; + return "```diff\n- " + message + "\n```"; }; From b1e72325fd883cddcfd8380e03487aefe2781745 Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Fri, 8 Sep 2023 10:32:24 +0000 Subject: [PATCH 082/106] feat: disable bot follow up if there are pending reviews --- src/handlers/wildcard/unassign.ts | 12 +++++++----- src/helpers/issue.ts | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index c75896c51..a24a732cd 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -6,7 +6,7 @@ import { getAllIssueComments, getCommitsOnPullRequest, getOpenedPullRequestsForAnIssue, - getPullRequestReviews, + getAllReviewRequests, listAllIssuesForRepo, removeAssignees, } from "../../helpers"; @@ -54,10 +54,12 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { const passedDuration = curTimestamp - lastActivity.getTime(); const pullRequest = await getOpenedPullRequestsForAnIssue(issue.number, issue.assignee); - const reviews = await getPullRequestReviews(context, pullRequest[0].id, 30, 1); - - const pendingReviews = reviews.filter((review) => review.state === "PENDING"); - if (pendingReviews.length > 0) { + const requestedReviewers = await getAllReviewRequests(context, pullRequest[0].id); + if (!requestedReviewers) { + logger.debug("Error: could not get requested reviewers"); + return false; + } + if (requestedReviewers.users?.length > 0) { return false; } if (passedDuration >= disqualifyTime || passedDuration >= followUpTime) { diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index 00ccd0e93..ca64cb423 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -532,6 +532,23 @@ export const getAllPullRequestReviews = async (context: Context, pull_number: nu return prArr; }; +export const getAllReviewRequests = async (context: Context, pull_number: number) => { + const payload = context.payload as Payload; + const response = await context.octokit.request("GET /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers", { + owner: payload.repository.owner.login, + repo: payload.repository.full_name, + pull_number: pull_number, + headers: { + "X-GitHub-Api-Version": "2022-11-28", + }, + }); + if (response.status === 200) { + return response.data; + } else { + return null; + } +}; + export const getPullRequestReviews = async (context: Context, pull_number: number, per_page: number, page: number) => { const logger = getLogger(); const payload = context.payload as Payload; From 54b1a8bfee5d78ad028e18583b9e377664226754 Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Fri, 8 Sep 2023 12:32:46 +0000 Subject: [PATCH 083/106] feat: disable bot reveiw on pending reviews --- src/handlers/wildcard/unassign.ts | 12 ++++++++---- src/helpers/issue.ts | 17 ----------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index a24a732cd..d1946ca60 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -6,7 +6,6 @@ import { getAllIssueComments, getCommitsOnPullRequest, getOpenedPullRequestsForAnIssue, - getAllReviewRequests, listAllIssuesForRepo, removeAssignees, } from "../../helpers"; @@ -54,12 +53,17 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { const passedDuration = curTimestamp - lastActivity.getTime(); const pullRequest = await getOpenedPullRequestsForAnIssue(issue.number, issue.assignee); - const requestedReviewers = await getAllReviewRequests(context, pullRequest[0].id); - if (!requestedReviewers) { + const response = await context.octokit.pulls.listRequestedReviewers({ + owner: payload.repository.owner.login, + repo: payload.repository.full_name, + pull_number: pullRequest[0].id, + }); + const data = response.status == 200 ? response.data : null; + if (!data) { logger.debug("Error: could not get requested reviewers"); return false; } - if (requestedReviewers.users?.length > 0) { + if (data.users?.length > 0) { return false; } if (passedDuration >= disqualifyTime || passedDuration >= followUpTime) { diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index ca64cb423..00ccd0e93 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -532,23 +532,6 @@ export const getAllPullRequestReviews = async (context: Context, pull_number: nu return prArr; }; -export const getAllReviewRequests = async (context: Context, pull_number: number) => { - const payload = context.payload as Payload; - const response = await context.octokit.request("GET /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers", { - owner: payload.repository.owner.login, - repo: payload.repository.full_name, - pull_number: pull_number, - headers: { - "X-GitHub-Api-Version": "2022-11-28", - }, - }); - if (response.status === 200) { - return response.data; - } else { - return null; - } -}; - export const getPullRequestReviews = async (context: Context, pull_number: number, per_page: number, page: number) => { const logger = getLogger(); const payload = context.payload as Payload; From 79e5f584cf24cf83776601564b86c3b0567a8957 Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Fri, 8 Sep 2023 14:33:08 +0000 Subject: [PATCH 084/106] feat: disable bot follow up on pending reviews --- src/handlers/wildcard/unassign.ts | 76 +++++++++++++++---------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index d1946ca60..e57461eb9 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -52,49 +52,49 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { const lastActivity = await lastActivityTime(issue, comments); const passedDuration = curTimestamp - lastActivity.getTime(); const pullRequest = await getOpenedPullRequestsForAnIssue(issue.number, issue.assignee); - - const response = await context.octokit.pulls.listRequestedReviewers({ - owner: payload.repository.owner.login, - repo: payload.repository.full_name, - pull_number: pullRequest[0].id, - }); - const data = response.status == 200 ? response.data : null; - if (!data) { - logger.debug("Error: could not get requested reviewers"); - return false; - } - if (data.users?.length > 0) { - return false; - } - if (passedDuration >= disqualifyTime || passedDuration >= followUpTime) { - if (passedDuration >= disqualifyTime) { - logger.info( - `Unassigning... lastActivityTime: ${lastActivity.getTime()}, curTime: ${curTimestamp}, passedDuration: ${passedDuration}, followUpTime: ${followUpTime}, disqualifyTime: ${disqualifyTime}` - ); - await closePullRequestForAnIssue(); - // remove assignees from the issue - await removeAssignees(issue.number, assignees); - await addCommentToIssue(`@${assignees[0]} - ${unassignComment} \nLast activity time: ${lastActivity}`, issue.number); - - return true; - } else if (passedDuration >= followUpTime) { - logger.info( - `Asking for updates... lastActivityTime: ${lastActivity.getTime()}, curTime: ${curTimestamp}, passedDuration: ${passedDuration}, followUpTime: ${followUpTime}, disqualifyTime: ${disqualifyTime}` - ); - - if (lastAskTime > lastActivity.getTime()) { + try { + const response = await context.octokit.pulls.listRequestedReviewers({ + owner: payload.repository.owner.login, + repo: payload.repository.full_name, + pull_number: pullRequest[0].id, + }); + const data = response.data; + if (data.users?.length > 0) { + return false; + } + if (passedDuration >= disqualifyTime || passedDuration >= followUpTime) { + if (passedDuration >= disqualifyTime) { logger.info( - `Skipping posting an update message cause its been already asked, lastAskTime: ${lastAskTime}, lastActivityTime: ${lastActivity.getTime()}` + `Unassigning... lastActivityTime: ${lastActivity.getTime()}, curTime: ${curTimestamp}, passedDuration: ${passedDuration}, followUpTime: ${followUpTime}, disqualifyTime: ${disqualifyTime}` ); - } else - await addCommentToIssue( - `${askUpdate} @${assignees[0]}? If you would like to release the bounty back to the DevPool, please comment \`/stop\` \nLast activity time: ${lastActivity}`, - issue.number + await closePullRequestForAnIssue(); + // remove assignees from the issue + await removeAssignees(issue.number, assignees); + await addCommentToIssue(`@${assignees[0]} - ${unassignComment} \nLast activity time: ${lastActivity}`, issue.number); + + return true; + } else if (passedDuration >= followUpTime) { + logger.info( + `Asking for updates... lastActivityTime: ${lastActivity.getTime()}, curTime: ${curTimestamp}, passedDuration: ${passedDuration}, followUpTime: ${followUpTime}, disqualifyTime: ${disqualifyTime}` ); + + if (lastAskTime > lastActivity.getTime()) { + logger.info( + `Skipping posting an update message cause its been already asked, lastAskTime: ${lastAskTime}, lastActivityTime: ${lastActivity.getTime()}` + ); + } else { + await addCommentToIssue( + `${askUpdate} @${assignees[0]}? If you would like to release the bounty back to the DevPool, please comment \`/stop\` \nLast activity time: ${lastActivity}`, + issue.number + ); + } + } } + return false; + } catch (e) { + logger.debug(`Error: could not get requested reviewers Error: ${e}`); + return false; } - - return false; }; const lastActivityTime = async (issue: Issue, comments: Comment[]): Promise => { From 683a68196ac865da28b3f82f019d586a70d3bb96 Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Fri, 8 Sep 2023 14:52:00 +0000 Subject: [PATCH 085/106] feat: disable bot follow up on pending reviews --- src/handlers/wildcard/unassign.ts | 69 ++++++++++++++----------------- src/helpers/issue.ts | 14 +++++++ 2 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index e57461eb9..bed1bd4b3 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -6,6 +6,7 @@ import { getAllIssueComments, getCommitsOnPullRequest, getOpenedPullRequestsForAnIssue, + getReviewRequests, listAllIssuesForRepo, removeAssignees, } from "../../helpers"; @@ -52,49 +53,43 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { const lastActivity = await lastActivityTime(issue, comments); const passedDuration = curTimestamp - lastActivity.getTime(); const pullRequest = await getOpenedPullRequestsForAnIssue(issue.number, issue.assignee); - try { - const response = await context.octokit.pulls.listRequestedReviewers({ - owner: payload.repository.owner.login, - repo: payload.repository.full_name, - pull_number: pullRequest[0].id, - }); - const data = response.data; - if (data.users?.length > 0) { - return false; - } - if (passedDuration >= disqualifyTime || passedDuration >= followUpTime) { - if (passedDuration >= disqualifyTime) { - logger.info( - `Unassigning... lastActivityTime: ${lastActivity.getTime()}, curTime: ${curTimestamp}, passedDuration: ${passedDuration}, followUpTime: ${followUpTime}, disqualifyTime: ${disqualifyTime}` - ); - await closePullRequestForAnIssue(); - // remove assignees from the issue - await removeAssignees(issue.number, assignees); - await addCommentToIssue(`@${assignees[0]} - ${unassignComment} \nLast activity time: ${lastActivity}`, issue.number); - return true; - } else if (passedDuration >= followUpTime) { + const response = await getReviewRequests(context, pullRequest[0].id, payload.repository.owner.login, payload.repository.full_name); + if (!response) return false; + + if (response.users?.length > 0) { + return false; + } + if (passedDuration >= disqualifyTime || passedDuration >= followUpTime) { + if (passedDuration >= disqualifyTime) { + logger.info( + `Unassigning... lastActivityTime: ${lastActivity.getTime()}, curTime: ${curTimestamp}, passedDuration: ${passedDuration}, followUpTime: ${followUpTime}, disqualifyTime: ${disqualifyTime}` + ); + await closePullRequestForAnIssue(); + // remove assignees from the issue + await removeAssignees(issue.number, assignees); + await addCommentToIssue(`@${assignees[0]} - ${unassignComment} \nLast activity time: ${lastActivity}`, issue.number); + + return true; + } else if (passedDuration >= followUpTime) { + logger.info( + `Asking for updates... lastActivityTime: ${lastActivity.getTime()}, curTime: ${curTimestamp}, passedDuration: ${passedDuration}, followUpTime: ${followUpTime}, disqualifyTime: ${disqualifyTime}` + ); + + if (lastAskTime > lastActivity.getTime()) { logger.info( - `Asking for updates... lastActivityTime: ${lastActivity.getTime()}, curTime: ${curTimestamp}, passedDuration: ${passedDuration}, followUpTime: ${followUpTime}, disqualifyTime: ${disqualifyTime}` + `Skipping posting an update message cause its been already asked, lastAskTime: ${lastAskTime}, lastActivityTime: ${lastActivity.getTime()}` + ); + } else { + await addCommentToIssue( + `${askUpdate} @${assignees[0]}? If you would like to release the bounty back to the DevPool, please comment \`/stop\` \nLast activity time: ${lastActivity}`, + issue.number ); - - if (lastAskTime > lastActivity.getTime()) { - logger.info( - `Skipping posting an update message cause its been already asked, lastAskTime: ${lastAskTime}, lastActivityTime: ${lastActivity.getTime()}` - ); - } else { - await addCommentToIssue( - `${askUpdate} @${assignees[0]}? If you would like to release the bounty back to the DevPool, please comment \`/stop\` \nLast activity time: ${lastActivity}`, - issue.number - ); - } } } - return false; - } catch (e) { - logger.debug(`Error: could not get requested reviewers Error: ${e}`); - return false; } + + return false; }; const lastActivityTime = async (issue: Issue, comments: Comment[]): Promise => { diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index 00ccd0e93..aff228675 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -550,6 +550,20 @@ export const getPullRequestReviews = async (context: Context, pull_number: numbe } }; +export const getReviewRequests = async (context: Context, pull_number: number, owner: string, repo: string) => { + const logger = getLogger(); + try { + const response = await context.octokit.pulls.listRequestedReviewers({ + owner: owner, + repo: repo, + pull_number: pull_number, + }); + return response.data; + } catch (e: unknown) { + logger.debug(`Error: could not get requested reviewers, reason: ${e}`); + return null; + } +}; // Get issues by issue number export const getIssueByNumber = async (context: Context, issue_number: number) => { const logger = getLogger(); From 1329f12fcb691077e9d71ea1f80d559c71a20687 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Sat, 9 Sep 2023 08:52:24 +0100 Subject: [PATCH 086/106] fix: table comment fix table so no whitespace --- src/handlers/comment/handlers/table.ts | 31 +++++++++++++------------- ubiquibot-config-default.json | 6 ++--- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/handlers/comment/handlers/table.ts b/src/handlers/comment/handlers/table.ts index 8f22d664f..8299b6284 100644 --- a/src/handlers/comment/handlers/table.ts +++ b/src/handlers/comment/handlers/table.ts @@ -16,24 +16,23 @@ export const tableComment = ({ days?: number; }) => { return ` - -
Warning! This task was created over ${days} days ago. Please confirm that this issue specification is accurate before starting.
Warning! This task was created over ${days} days ago. Please confirm that this issue specification is accurate before starting.
+ + + + ${ isBountyStale - ? `` + ? `| Warning! | This task was created over ${days} days ago. Please confirm that this issue specification is accurate before starting. |\n |--|--|` : `` } - - - - - - - - - ${multiplier ? `` : ``} - ${reason ? `` : ``} - ${bounty ? `` : ``} -
Warning! This task was created over ${days} days ago. Please confirm that this issue specification is accurate before starting.
Deadline${deadline}
Registered Wallet${wallet}
Payment Multiplier${multiplier}
Multiplier Reason${reason}
Total Bounty${bounty}
-`; + ${!isBountyStale ? `| Deadline | ${deadline} |\n |--|--|` : `| Deadline | ${deadline} |`} + | Registered Wallet | ${wallet} | + ${multiplier ? `| Payment Multiplier | ${multiplier} |` : ``} + ${reason ? `| Multiplier Reason | ${reason} |` : ``} + ${bounty ? `| Total Bounty | ${bounty} |` : ``} + + + + +`; }; diff --git a/ubiquibot-config-default.json b/ubiquibot-config-default.json index eb9496538..9f3cfeae4 100644 --- a/ubiquibot-config-default.json +++ b/ubiquibot-config-default.json @@ -8,7 +8,7 @@ "disable-analytics": false, "comment-incentives": false, "register-wallet-with-verification": false, - "stale-bounty-time": "0d", + "stale-bounty-time": "15d", "promotion-comment": "\n
If you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot!
", "default-labels": [], "time-labels": [ @@ -48,11 +48,11 @@ "command-settings": [ { "name": "start", - "enabled": false + "enabled": true }, { "name": "stop", - "enabled": false + "enabled": true }, { "name": "wallet", From bc2c4fdab974a7b5b3cbaa35de52eaf3d565ad3d Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Sat, 9 Sep 2023 08:53:15 +0100 Subject: [PATCH 087/106] fix: default set to false --- ubiquibot-config-default.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ubiquibot-config-default.json b/ubiquibot-config-default.json index 9f3cfeae4..eb9496538 100644 --- a/ubiquibot-config-default.json +++ b/ubiquibot-config-default.json @@ -8,7 +8,7 @@ "disable-analytics": false, "comment-incentives": false, "register-wallet-with-verification": false, - "stale-bounty-time": "15d", + "stale-bounty-time": "0d", "promotion-comment": "\n
If you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot!
", "default-labels": [], "time-labels": [ @@ -48,11 +48,11 @@ "command-settings": [ { "name": "start", - "enabled": true + "enabled": false }, { "name": "stop", - "enabled": true + "enabled": false }, { "name": "wallet", From 678551fa3c124b1a84a79749f47fa56399873459 Mon Sep 17 00:00:00 2001 From: EtherealGlow <139999816+EtherealGlow@users.noreply.github.com> Date: Sat, 9 Sep 2023 14:10:26 +0530 Subject: [PATCH 088/106] Update src/helpers/issue.ts Co-authored-by: whilefoo <139262667+whilefoo@users.noreply.github.com> --- src/helpers/issue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index aff228675..bb9c80c55 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -560,7 +560,7 @@ export const getReviewRequests = async (context: Context, pull_number: number, o }); return response.data; } catch (e: unknown) { - logger.debug(`Error: could not get requested reviewers, reason: ${e}`); + logger.error(`Error: could not get requested reviewers, reason: ${e}`); return null; } }; From 542c429e558eb88748f7739d0cbc562aaeea2eb9 Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Sat, 9 Sep 2023 08:45:49 +0000 Subject: [PATCH 089/106] feat: disable bot follow up on pending reviews --- src/handlers/wildcard/unassign.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index bed1bd4b3..fa2788c2f 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -54,10 +54,8 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { const passedDuration = curTimestamp - lastActivity.getTime(); const pullRequest = await getOpenedPullRequestsForAnIssue(issue.number, issue.assignee); - const response = await getReviewRequests(context, pullRequest[0].id, payload.repository.owner.login, payload.repository.full_name); - if (!response) return false; - - if (response.users?.length > 0) { + const reviewRequests = await getReviewRequests(context, pullRequest[0].id, payload.repository.owner.login, payload.repository.full_name); + if (!reviewRequests || reviewRequests.users?.length > 0) { return false; } if (passedDuration >= disqualifyTime || passedDuration >= followUpTime) { From d31ec44758c23e322194f171f715ca7c48bf3286 Mon Sep 17 00:00:00 2001 From: EtherealGlow Date: Sat, 9 Sep 2023 10:57:40 +0000 Subject: [PATCH 090/106] feat: disable bot follow up for pending reviews --- src/handlers/wildcard/unassign.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index fa2788c2f..f71369b47 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -52,12 +52,15 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { const curTimestamp = new Date().getTime(); const lastActivity = await lastActivityTime(issue, comments); const passedDuration = curTimestamp - lastActivity.getTime(); - const pullRequest = await getOpenedPullRequestsForAnIssue(issue.number, issue.assignee); + const pullRequest = await getOpenedPullRequestsForAnIssue(issue.number, issue.assignee.login); - const reviewRequests = await getReviewRequests(context, pullRequest[0].id, payload.repository.owner.login, payload.repository.full_name); - if (!reviewRequests || reviewRequests.users?.length > 0) { - return false; + if (pullRequest.length > 0) { + const reviewRequests = await getReviewRequests(context, pullRequest[0].number, payload.repository.owner.login, payload.repository.name); + if (!reviewRequests || reviewRequests.users?.length > 0) { + return false; + } } + if (passedDuration >= disqualifyTime || passedDuration >= followUpTime) { if (passedDuration >= disqualifyTime) { logger.info( From 9f398e6322cceecc07758eb67a5be3a34b2907cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= <4975670+pavlovcik@users.noreply.github.com> Date: Sun, 10 Sep 2023 21:16:23 +0900 Subject: [PATCH 091/106] Update ubiquibot-config.yml --- .github/ubiquibot-config.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/ubiquibot-config.yml b/.github/ubiquibot-config.yml index 609d52ba1..336f1f1a4 100644 --- a/.github/ubiquibot-config.yml +++ b/.github/ubiquibot-config.yml @@ -1,4 +1 @@ -price-multiplier: 1.5 -default-labels: - - "Time: <1 Hour" - - "Priority: 1 (Normal)" +price-multiplier: 1.5 \ No newline at end of file From 5aa2908039deee6db44e8906518824c9713d0dc7 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Sun, 10 Sep 2023 18:31:28 +0100 Subject: [PATCH 092/106] fix: table and check parsing as float and removed whitespace from table --- src/handlers/comment/handlers/index.ts | 2 +- src/handlers/comment/handlers/table.ts | 38 +++++++++++++------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/handlers/comment/handlers/index.ts b/src/handlers/comment/handlers/index.ts index 9c4f3900b..3a58979a1 100644 --- a/src/handlers/comment/handlers/index.ts +++ b/src/handlers/comment/handlers/index.ts @@ -177,7 +177,7 @@ export const issueReopenedCallback = async (): Promise => { } const assignee = events[0].assignee.login; - if (formattedAmount != "0.0") { + if (parseFloat(formattedAmount) > 0) { // write penalty to db try { await addPenalty(assignee, repository.full_name, tokenAddress, networkId.toString(), amount); diff --git a/src/handlers/comment/handlers/table.ts b/src/handlers/comment/handlers/table.ts index 8299b6284..97f154e0b 100644 --- a/src/handlers/comment/handlers/table.ts +++ b/src/handlers/comment/handlers/table.ts @@ -16,23 +16,23 @@ export const tableComment = ({ days?: number; }) => { return ` - - - - - ${ - isBountyStale - ? `| Warning! | This task was created over ${days} days ago. Please confirm that this issue specification is accurate before starting. |\n |--|--|` - : `` - } - ${!isBountyStale ? `| Deadline | ${deadline} |\n |--|--|` : `| Deadline | ${deadline} |`} - | Registered Wallet | ${wallet} | - ${multiplier ? `| Payment Multiplier | ${multiplier} |` : ``} - ${reason ? `| Multiplier Reason | ${reason} |` : ``} - ${bounty ? `| Total Bounty | ${bounty} |` : ``} - - - - -`; + + +${ + isBountyStale + ? `` + : `` +} + + + + + + + + +${multiplier ? `` : ``} +${reason ? `` : ``} +${bounty ? `` : ``} +
Warning! This task was created over ${days} days ago. Please confirm that this issue specification is accurate before starting.
Deadline${deadline}
Registered Wallet${wallet}
Payment Multiplier${multiplier}
Multiplier Reason${reason}
Total Bounty${bounty}
`; }; From a60a0173b411fedabb69f6d2888fbef1f4a848f3 Mon Sep 17 00:00:00 2001 From: Wholesomebruh <109906215+Wholesomebruh@users.noreply.github.com> Date: Sun, 10 Sep 2023 23:13:39 +0530 Subject: [PATCH 093/106] Fix : skipped->disabled , since -> because --- src/handlers/comment/action.ts | 4 +-- src/handlers/comment/handlers/assign.ts | 2 +- src/handlers/comment/handlers/index.ts | 2 +- src/handlers/payout/action.ts | 38 ++++++++++++------------- src/handlers/payout/post.ts | 4 +-- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/handlers/comment/action.ts b/src/handlers/comment/action.ts index 84c54e6d5..5341aed76 100644 --- a/src/handlers/comment/action.ts +++ b/src/handlers/comment/action.ts @@ -40,8 +40,8 @@ export const handleComment = async (): Promise => { const feature = config.command.find((e) => e.name === id.split("/")[1]); 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); + 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); continue; } diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts index 16dc69f9b..e50831a64 100644 --- a/src/handlers/comment/handlers/assign.ts +++ b/src/handlers/comment/handlers/assign.ts @@ -152,7 +152,7 @@ const getMultiplierInfoToDisplay = async (senderLogin: string, org_id: string, i } else { _multiplierToDisplay = multiplier; _reasonToDisplay = reason; - _bountyToDisplay = `Permit generation skipped since price label is not set`; + _bountyToDisplay = `Permit generation disabled because price label is not set.`; const issueDetailed = bountyInfo(issue); if (issueDetailed.priceLabel) { _bountyToDisplay = (+issueDetailed.priceLabel.substring(7, issueDetailed.priceLabel.length - 4) * value).toString() + " USD"; diff --git a/src/handlers/comment/handlers/index.ts b/src/handlers/comment/handlers/index.ts index 5470b3062..fea8fb943 100644 --- a/src/handlers/comment/handlers/index.ts +++ b/src/handlers/comment/handlers/index.ts @@ -90,7 +90,7 @@ export const issueCreatedCallback = async (): Promise => { const { assistivePricing } = config.mode; if (!assistivePricing) { - logger.info("Skipping adding label to issue because assistive pricing is disabled"); + logger.info("Skipping adding label to issue because assistive pricing is disabled."); return; } diff --git a/src/handlers/payout/action.ts b/src/handlers/payout/action.ts index 237a374b6..bd84daf13 100644 --- a/src/handlers/payout/action.ts +++ b/src/handlers/payout/action.ts @@ -39,7 +39,7 @@ export const handleIssueClosed = async () => { if (accessControl.organization) { const userHasPermission = await checkUserPermissionForRepoAndOrg(payload.sender.login, context); - if (!userHasPermission) return "Permit generation skipped because this issue has been closed by an external contributor."; + if (!userHasPermission) return "Permit generation disabled because this issue has been closed by an external contributor."; } const comments = await getAllIssueComments(issue.number); @@ -94,19 +94,19 @@ export const handleIssueClosed = async () => { return; } if (privateKey == "") { - logger.info("Permit generation skipped because wallet private key is not set"); - return "Permit generation skipped because wallet private key is not set"; + logger.info("Permit generation disabled because wallet private key is not set."); + return "Permit generation disabled because wallet private key is not set."; } if (issue.state_reason !== StateReason.COMPLETED) { - logger.info("Permit generation skipped because this is marked as unplanned."); - return "Permit generation skipped because this is marked as unplanned."; + logger.info("Permit generation disabled because this is marked as unplanned."); + return "Permit generation disabled because this is marked as unplanned."; } logger.info(`Checking if the issue is a parent issue.`); if (issue.body && isParentIssue(issue.body)) { - logger.error("Permit generation skipped since the issue is identified as parent issue."); + logger.error("Permit generation disabled because this is a collection of issues."); await clearAllPriceLabelsOnIssue(); - return "Permit generation skipped since the issue is identified as parent issue."; + return "Permit generation disabled because this is a collection of issues."; } logger.info(`Handling issues.closed event, issue: ${issue.number}`); @@ -118,7 +118,7 @@ export const handleIssueClosed = async () => { if (res) { if (res[1] === "false") { logger.info(`Skipping to generate permit2 url, reason: autoPayMode for this issue: false`); - return `Permit generation skipped since automatic payment for this issue is disabled.`; + return `Permit generation disabled because automatic payment for this issue is disabled.`; } break; } @@ -127,25 +127,25 @@ export const handleIssueClosed = async () => { if (paymentPermitMaxPrice == 0 || !paymentPermitMaxPrice) { logger.info(`Skipping to generate permit2 url, reason: { paymentPermitMaxPrice: ${paymentPermitMaxPrice}}`); - return `Permit generation skipped since paymentPermitMaxPrice is 0`; + return `Permit generation disabled because paymentPermitMaxPrice is 0.`; } const issueDetailed = bountyInfo(issue); if (!issueDetailed.isBounty) { - logger.info(`Skipping... its not a bounty`); - return `Permit generation skipped since this issue didn't qualify as bounty`; + logger.info(`Skipping... its not a bounty.`); + return `Permit generation disabled because this issue didn't qualify as bounty.`; } const assignees = issue?.assignees ?? []; const assignee = assignees.length > 0 ? assignees[0] : undefined; if (!assignee) { - logger.info("Skipping to proceed the payment because `assignee` is undefined"); - return `Permit generation skipped since assignee is undefined`; + logger.info("Skipping to proceed the payment because `assignee` is undefined."); + return `Permit generation disabled because assignee is undefined.`; } if (!issueDetailed.priceLabel) { logger.info("Skipping to proceed the payment because price not set"); - return `Permit generation skipped since price label is not set`; + return `Permit generation disabled because price label is not set.`; } const recipient = await getWalletAddress(assignee.login); @@ -164,8 +164,8 @@ export const handleIssueClosed = async () => { let priceInEth = new Decimal(issueDetailed.priceLabel.substring(7, issueDetailed.priceLabel.length - 4)).mul(multiplier); if (priceInEth.gt(paymentPermitMaxPrice)) { - logger.info("Skipping to proceed the payment because bounty payout is higher than paymentPermitMaxPrice"); - return `Permit generation skipped since issue's bounty is higher than ${paymentPermitMaxPrice}`; + logger.info("Skipping to proceed the payment because bounty payout is higher than paymentPermitMaxPrice."); + return `Permit generation disabled because issue's bounty is higher than ${paymentPermitMaxPrice}.`; } // if bounty hunter has any penalty then deduct it from the bounty @@ -176,7 +176,7 @@ export const handleIssueClosed = async () => { const bountyAmountAfterPenalty = bountyAmount.sub(penaltyAmount); if (bountyAmountAfterPenalty.lte(0)) { await removePenalty(assignee.login, payload.repository.full_name, paymentToken, networkId.toString(), bountyAmount); - const msg = `Permit generation skipped because bounty amount after penalty is 0`; + const msg = `Permit generation disabled because bounty amount after penalty is 0.`; logger.info(msg); return msg; } @@ -191,8 +191,8 @@ export const handleIssueClosed = async () => { `#### Task Assignee Reward\n### [ **[ CLAIM ${priceInEth} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n` + "```" + shortenRecipient + "```"; const permitComments = comments.filter((content) => content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot); if (permitComments.length > 0) { - logger.info(`Skip to generate a permit url because it has been already posted`); - return `Permit generation skipped because it was already posted to this issue.`; + logger.info(`Skip to generate a permit url because it has been already posted.`); + return `Permit generation disabled because it was already posted to this issue.`; } await deleteLabel(issueDetailed.priceLabel); await addLabelToIssue("Permitted"); diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 371c3d68a..eb321dcc6 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -31,7 +31,7 @@ export const incentivizeComments = async () => { } if (issue.state_reason !== StateReason.COMPLETED) { - logger.info("incentivizeComments: comment incentives skipped because the issue was not closed as completed"); + logger.info("incentivizeComments: comment incentives disabled because the issue was not closed as completed."); return; } @@ -143,7 +143,7 @@ export const incentivizeCreatorComment = async () => { } if (issue.state_reason !== StateReason.COMPLETED) { - logger.info("incentivizeCreatorComment: comment incentives skipped because the issue was not closed as completed"); + logger.info("incentivizeCreatorComment: comment incentives disabled because the issue was not closed as completed."); return; } From 4e8158433abc71bb9e4e1c9f24a0fc0b12962214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= <4975670+pavlovcik@users.noreply.github.com> Date: Mon, 11 Sep 2023 17:48:52 +0900 Subject: [PATCH 094/106] Update pull_request_template.md --- .github/pull_request_template.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 9d7bbd50f..5804d2b1e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,10 @@ -Resolves # - - + +Resolves # +QA: From 7a42059ff55f9ff21d654c314416896cf91c03f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= <4975670+pavlovcik@users.noreply.github.com> Date: Mon, 11 Sep 2023 17:50:38 +0900 Subject: [PATCH 095/106] Update pull_request_template.md --- .github/pull_request_template.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5804d2b1e..b1b688448 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,10 +1,11 @@ Resolves # -QA: + +Quality Assurance: From c6b6709b5af1447dddcb5a9ab231f00fcd16d33e Mon Sep 17 00:00:00 2001 From: HARALD Date: Mon, 11 Sep 2023 22:48:51 -0400 Subject: [PATCH 096/106] feat: include comments on pr --- src/handlers/assign/auto.ts | 8 ++++---- src/handlers/payout/post.ts | 15 +++++++++++++++ src/helpers/parser.ts | 21 +++++++++++++-------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/handlers/assign/auto.ts b/src/handlers/assign/auto.ts index 7c648b5e7..380b59983 100644 --- a/src/handlers/assign/auto.ts +++ b/src/handlers/assign/auto.ts @@ -18,14 +18,14 @@ export const checkPullRequests = async () => { // Loop through the pull requests and assign them to their respective issues if needed for (const pull of pulls) { - const pullRequestLinked = await gitLinkedIssueParser({ + const linkedIssue = await gitLinkedIssueParser({ owner: payload.repository.owner.login, repo: payload.repository.name, - issue_number: pull.number, + pull_number: pull.number, }); // if pullRequestLinked is empty, continue - if (pullRequestLinked == "" || !pull.user) { + if (linkedIssue == "" || !pull.user || !linkedIssue) { continue; } @@ -37,7 +37,7 @@ export const checkPullRequests = async () => { continue; } - const linkedIssueNumber = pullRequestLinked.substring(pullRequestLinked.lastIndexOf("/") + 1); + const linkedIssueNumber = linkedIssue.substring(linkedIssue.lastIndexOf("/") + 1); // Check if the pull request opener is assigned to the issue const opener = pull.user.login; diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index de2f1d3aa..e43f9d4c5 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -191,6 +191,7 @@ export const incentivizePullRequestReviews = async () => { } const prReviews = await getAllPullRequestReviews(context, linkedPullRequest.number, "full"); + const prComments = await getAllIssueComments(linkedPullRequest.number, "full"); logger.info(`Getting the PR reviews done. comments: ${JSON.stringify(prReviews)}`); const prReviewsByUser: Record = {}; for (const review of prReviews) { @@ -206,6 +207,20 @@ export const incentivizePullRequestReviews = async () => { } prReviewsByUser[user.login].comments.push(review.body_html); } + + for (const comment of prComments) { + const user = comment.user; + if (!user) continue; + if (user.type == UserType.Bot || user.login == assignee) continue; + if (!comment.body_html) { + logger.info(`incentivizePullRequestReviews: Skipping to parse the comment because body_html is undefined. comment: ${JSON.stringify(comment)}`); + continue; + } + if (!prReviewsByUser[user.login]) { + prReviewsByUser[user.login] = { id: user.node_id, comments: [] }; + } + prReviewsByUser[user.login].comments.push(comment.body_html); + } const tokenSymbol = await getTokenSymbol(paymentToken, rpc); logger.info(`incentivizePullRequestReviews: Filtering by the user type done. commentsByUser: ${JSON.stringify(prReviewsByUser)}`); diff --git a/src/helpers/parser.ts b/src/helpers/parser.ts index 82563657c..93d249404 100644 --- a/src/helpers/parser.ts +++ b/src/helpers/parser.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { HTMLElement, parse } from "node-html-parser"; import { getPullByNumber } from "./issue"; -import { getBotContext } from "../bindings"; +import { getBotContext, getLogger } from "../bindings"; import { Payload } from "../types"; interface GitParser { @@ -29,7 +29,8 @@ export const gitIssueParser = async ({ owner, repo, issue_number }: GitParser): } }; -export const gitLinkedIssueParser = async ({ owner, repo, pull_number }: GitParser): Promise => { +export const gitLinkedIssueParser = async ({ owner, repo, pull_number }: GitParser) => { + const logger = getLogger(); try { const { data } = await axios.get(`https://github.com/${owner}/${repo}/pull/${pull_number}`); const dom = parse(data); @@ -37,17 +38,19 @@ export const gitLinkedIssueParser = async ({ owner, repo, pull_number }: GitPars const linkedIssues = devForm.querySelectorAll(".my-1"); if (linkedIssues.length === 0) { - return ""; + return null; } const issueUrl = linkedIssues[0].querySelector("a")?.attrs?.href || ""; return issueUrl; } catch (error) { - return ""; + logger.error(`${JSON.stringify(error)}`); + return null; } }; export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser) => { + const logger = getLogger(); try { const { data } = await axios.get(`https://github.com/${owner}/${repo}/issues/${issue_number}`); const context = getBotContext(); @@ -55,8 +58,8 @@ export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser const dom = parse(data); const devForm = dom.querySelector("[data-target='create-branch.developmentForm']") as HTMLElement; const linkedPRs = devForm.querySelectorAll(".my-1"); - if (linkedPRs.length === 0) return ""; - let linkedPullRequest; + if (linkedPRs.length === 0) return null; + let linkedPullRequest = null; for (const linkedPr of linkedPRs) { const prHref = linkedPr.querySelector("a")?.attrs?.href || ""; const parts = prHref.split("/"); @@ -65,7 +68,8 @@ export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser const repository = parts[parts.length - 3]; if (`${organization}/${repository}` !== payload.repository.full_name) continue; - const prNumber = prHref.substring(prHref.lastIndexOf("/") + 1); + const prNumber = parts[parts.length - 1]; + if (Number.isNaN(Number(prNumber))) return null; const pr = await getPullByNumber(context, Number(prNumber)); if (!pr || !pr.merged) continue; @@ -76,6 +80,7 @@ export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser } return linkedPullRequest; } catch (error) { - return ""; + logger.error(`${JSON.stringify(error)}`); + return null; } }; From 391453259f5d7c2ffbe359705c51df28ecf1d01c Mon Sep 17 00:00:00 2001 From: whilefoo Date: Tue, 12 Sep 2023 09:51:09 +0200 Subject: [PATCH 097/106] fix: add helpers back --- src/utils/helpers.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/utils/helpers.ts diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts new file mode 100644 index 000000000..c7bad1b2c --- /dev/null +++ b/src/utils/helpers.ts @@ -0,0 +1,3 @@ +export const ErrorDiff = (message: unknown) => { + return "```diff\n- " + message + "\n```"; +}; From 6ca50a44edd1b32a65c3b4d75899a0a289fdcd37 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Tue, 12 Sep 2023 10:36:35 +0200 Subject: [PATCH 098/106] feat: issue creator multiplier should be optional --- src/types/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/config.ts b/src/types/config.ts index ab1699c93..c162ece1a 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -141,7 +141,7 @@ export const WideConfigSchema = Type.Object( { "evm-network-id": Type.Optional(Type.Number()), "price-multiplier": Type.Optional(Type.Number()), - "issue-creator-multiplier": Type.Number(), + "issue-creator-multiplier": Type.Optional(Type.Number()), "time-labels": Type.Optional(Type.Array(LabelItemSchema)), "priority-labels": Type.Optional(Type.Array(LabelItemSchema)), "payment-permit-max-price": Type.Optional(Type.Number()), From 0414bba322f11589d895556e9c7526e6db79809d Mon Sep 17 00:00:00 2001 From: FUTURE Date: Thu, 14 Sep 2023 10:08:08 +0200 Subject: [PATCH 099/106] feat: extract time duration with ms (#756) * feat: extract time duration with ms * feat: use regex --- src/helpers/shared.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/helpers/shared.ts b/src/helpers/shared.ts index f851c5220..86e1599eb 100644 --- a/src/helpers/shared.ts +++ b/src/helpers/shared.ts @@ -1,3 +1,4 @@ +import ms from "ms"; import { getBotContext } from "../bindings"; import { LabelItem, Payload, UserType } from "../types"; @@ -35,13 +36,11 @@ export const calculateWeight = (label: LabelItem | undefined): number => { export const calculateDuration = (label: LabelItem): number => { if (!label) return 0; - const matches = label.name.match(/\d+/); if (label.name.toLowerCase().includes("priority")) return 0; - const number = matches && matches.length > 0 ? parseInt(matches[0]) || 0 : 0; - if (label.name.toLowerCase().includes("minute")) return number * 60; - if (label.name.toLowerCase().includes("hour")) return number * 3600; - if (label.name.toLowerCase().includes("day")) return number * 86400; - if (label.name.toLowerCase().includes("week")) return number * 604800; - if (label.name.toLowerCase().includes("month")) return number * 2592000; - return 0; + + const pattern = /<(\d+\s\w+)/; + const result = label.name.match(pattern); + if (!result) return 0; + + return ms(result[1]) / 1000; }; From 0dfa52024f860b6323f8d001d257461c24312ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= <4975670+pavlovcik@users.noreply.github.com> Date: Fri, 15 Sep 2023 03:58:18 +0900 Subject: [PATCH 100/106] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 86f3b5e6b..2403ad576 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: true contact_links: - - name: Telegram Group Chat - url: https://t.me/UbiquityDAO/29891 - about: "Join us on Telegram!" + - name: UbiquiBot Development Group Chat + url: https://t.me/UbiquityDAO/31132 + about: "Live chat with us on Telegram!" From cf2c9be2b3ce88cc7d55979ec57700665265532d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= <4975670+pavlovcik@users.noreply.github.com> Date: Fri, 15 Sep 2023 04:01:43 +0900 Subject: [PATCH 101/106] Update bounty-template.yml --- .github/ISSUE_TEMPLATE/bounty-template.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bounty-template.yml b/.github/ISSUE_TEMPLATE/bounty-template.yml index eb78b826f..7e81ebf0c 100644 --- a/.github/ISSUE_TEMPLATE/bounty-template.yml +++ b/.github/ISSUE_TEMPLATE/bounty-template.yml @@ -1,18 +1,20 @@ name: "Bounty Proposal" description: Have a suggestion for how to improve UbiquiBot? Let us know! -title: "Bounty Proposal:" +title: "Bounty Proposal: " body: - type: markdown attributes: value: | ## Feature Request Form - Thank you for taking the time to file a feature request! Please let us know what you're trying to do, and how UbiquiBot can help. + Thank you for taking the time to file a feature request. + If you register your wallet address, you will be eligible for compensation if this is accepted! + Please let us know how we can improve the bot. - type: textarea attributes: label: Describe the background or context - description: Please let us know what inspired you to write this proposal. Backlinking to specific comments is usually sufficient context. + description: Please let us know what inspired you to write this proposal. Backlinking to specific comments on GitHub, and leaving a remark about how the bot should have interacted with it is usually sufficient context. validations: required: false @@ -22,3 +24,10 @@ body: description: A clear description of what you want to happen. Add any considered drawbacks. validations: required: true + + - type: textarea + attributes: + label: Remarks + description: Any closing remarks? + validations: + required: false From 53fc6ab3193d89c2ef391b2faf2b3e3ee6126103 Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Mon, 18 Sep 2023 04:34:02 +0100 Subject: [PATCH 102/106] fix: use bot penalty (#749) * fix: use bot penalty uses bot penalty created at * fix: defaults set to false * fix: use reopen time vs using penalty comment * fix: future living implemented futures fix --- src/handlers/wildcard/unassign.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index f71369b47..880c29974 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -10,7 +10,8 @@ import { listAllIssuesForRepo, removeAssignees, } from "../../helpers"; -import { Comment, Issue, IssueType, Payload } from "../../types"; +import { 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 @@ -99,6 +100,11 @@ const lastActivityTime = async (issue: Issue, comments: Comment[]): Promise i.login); const activities: Date[] = [new Date(issue.created_at)]; + const lastAssignCommentOfHunter = comments + .filter((comment) => comment.user.type === UserType.Bot && comment.body.includes(assignees[0]) && comment.body.includes(deadLinePrefix)) + .sort((a: Comment, b: Comment) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); + if (lastAssignCommentOfHunter.length > 0) activities.push(new Date(lastAssignCommentOfHunter[0].created_at)); + // get last comment on the issue const lastCommentsOfHunterForIssue = comments .filter((comment) => assignees.includes(comment.user.login)) From 82564f02642da988d9fa1f6c785358669f467985 Mon Sep 17 00:00:00 2001 From: Wholesomebruh <109906215+Wholesomebruh@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:30:57 +0530 Subject: [PATCH 103/106] fix: renamed default config file (#763) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: renamed default config file * Fix: resolved changes * chore: update ubiquibot-config-default.ts Co-authored-by: アレクサンダー.eth <4975670+pavlovcik@users.noreply.github.com> * Update ubiquibot-config-default.ts as const / as read only isn't working * Fix: added default config to src --------- Co-authored-by: 0xCodercrane <108444211+0xcodercrane@users.noreply.github.com> Co-authored-by: アレクサンダー.eth <4975670+pavlovcik@users.noreply.github.com> --- README.md | 2 +- src/configs/index.ts | 1 + .../configs/ubiquibot-config-default.ts | 9 ++++++--- src/utils/private.ts | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) rename ubiquibot-config-default.json => src/configs/ubiquibot-config-default.ts (93%) diff --git a/README.md b/README.md index 4b38ed11d..e8d09c845 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ Bounty bot is built using the [probot](https://probot.github.io/) framework so i ├── utils A set of utility functions -## Default Config Notes (`ubiquibot-config-default.json`) +## Default Config Notes (`ubiquibot-config-default.ts`) We can't use a `jsonc` file due to limitations with Netlify. Here is a snippet of some values with notes next to them. diff --git a/src/configs/index.ts b/src/configs/index.ts index 6addb9b75..d6231b38f 100644 --- a/src/configs/index.ts +++ b/src/configs/index.ts @@ -1,3 +1,4 @@ export * from "./shared"; export * from "./strings"; export * from "./abis"; +export * from "./ubiquibot-config-default"; \ No newline at end of file diff --git a/ubiquibot-config-default.json b/src/configs/ubiquibot-config-default.ts similarity index 93% rename from ubiquibot-config-default.json rename to src/configs/ubiquibot-config-default.ts index eb9496538..d1d9b7ffd 100644 --- a/ubiquibot-config-default.json +++ b/src/configs/ubiquibot-config-default.ts @@ -1,4 +1,6 @@ -{ +import { MergedConfig } from "../types"; + +export const DefaultConfig : MergedConfig = { "evm-network-id": 100, "price-multiplier": 1, "issue-creator-multiplier": 2, @@ -8,7 +10,6 @@ "disable-analytics": false, "comment-incentives": false, "register-wallet-with-verification": false, - "stale-bounty-time": "0d", "promotion-comment": "\n
If you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot!
", "default-labels": [], "time-labels": [ @@ -90,5 +91,7 @@ "enable-access-control": { "label": false, "organization": true - } + }, + "stale-bounty-time":"0d" } + diff --git a/src/utils/private.ts b/src/utils/private.ts index 23f014e16..d0cb329c5 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -4,7 +4,7 @@ import { MergedConfig, Payload } from "../types"; import { Context } from "probot"; import merge from "lodash/merge"; -import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json"; +import { DefaultConfig } from "../configs"; import { validate } from "./ajv"; import { WideConfig, WideOrgConfig, WideRepoConfig, WideConfigSchema, WideOrgConfigSchema } from "../types"; @@ -126,7 +126,7 @@ export const getWideConfig = async (context: Context) => { throw new Error(`Invalid repo config: ${error}`); } } - const parsedDefault: MergedConfig = DEFAULT_CONFIG_JSON; + const parsedDefault: MergedConfig = DefaultConfig; const privateKeyDecrypted = parsedOrg && parsedOrg[KEY_NAME] ? await getPrivateKey(parsedOrg[KEY_NAME]) : undefined; const configs: MergedConfigs = { parsedDefault, parsedOrg, parsedRepo }; From 81669894775f17a09859aa188b7b188ad5d7999e Mon Sep 17 00:00:00 2001 From: Sadaf Ahmed <119438857+Sadaf-A@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:34:23 +0530 Subject: [PATCH 104/106] feat: parse Permit Url (#738) * feat(action): parsing the permitUrl to an URL object * refactor(action) : using permitBaseUrl from config * feat: use regex * fix(action): - removing unused import statement --------- Co-authored-by: 0xcodercrane <108444211+0xcodercrane@users.noreply.github.com> --- src/handlers/payout/action.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/handlers/payout/action.ts b/src/handlers/payout/action.ts index bd84daf13..49677a3cb 100644 --- a/src/handlers/payout/action.ts +++ b/src/handlers/payout/action.ts @@ -189,7 +189,12 @@ export const handleIssueClosed = async () => { logger.info(`Posting a payout url to the issue, url: ${payoutUrl}`); const comment = `#### Task Assignee Reward\n### [ **[ CLAIM ${priceInEth} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n` + "```" + shortenRecipient + "```"; - const permitComments = comments.filter((content) => content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot); + const permitComments = comments.filter((content) => { + claimUrlRegex; + const permitUrlMatches = content.body.match(claimUrlRegex); + if (!permitUrlMatches || permitUrlMatches.length < 2) return false; + else return true; + }); if (permitComments.length > 0) { logger.info(`Skip to generate a permit url because it has been already posted.`); return `Permit generation disabled because it was already posted to this issue.`; From e734904e1a3b0c8fa0f4d9b29eca93eb25f9b6ac Mon Sep 17 00:00:00 2001 From: Seprintour <51956013+seprintour@users.noreply.github.com> Date: Tue, 19 Sep 2023 18:47:28 +0100 Subject: [PATCH 105/106] fix: close pr issues and stop running handlers twice (#728) * fix: close pr issues and stop running handlers twice * chore: remove unused * chore: remove unused * chore: check org and repo name * chore: refactor gitLinkedPrParser * chore: make pr parser generic * Update src/helpers/parser.ts Co-authored-by: whilefoo <139262667+whilefoo@users.noreply.github.com> * Update src/helpers/parser.ts Co-authored-by: whilefoo <139262667+whilefoo@users.noreply.github.com> * Update src/helpers/parser.ts Co-authored-by: whilefoo <139262667+whilefoo@users.noreply.github.com> * Update src/helpers/parser.ts Co-authored-by: whilefoo <139262667+whilefoo@users.noreply.github.com> * chore: whilefoo review refactor --------- Co-authored-by: whilefoo <139262667+whilefoo@users.noreply.github.com> --- src/handlers/assign/action.ts | 15 ++++-- src/handlers/payout/post.ts | 15 ++++-- src/handlers/wildcard/unassign.ts | 2 - src/helpers/parser.ts | 89 +++++++++++++++++-------------- yarn.lock | 19 ++++--- 5 files changed, 82 insertions(+), 58 deletions(-) diff --git a/src/handlers/assign/action.ts b/src/handlers/assign/action.ts index b42312a88..e0093de07 100644 --- a/src/handlers/assign/action.ts +++ b/src/handlers/assign/action.ts @@ -1,5 +1,6 @@ import { getBotConfig, getBotContext, getLogger } from "../../bindings"; -import { addCommentToIssue, closePullRequest, getOpenedPullRequestsForAnIssue, calculateWeight, calculateDuration } from "../../helpers"; +import { addCommentToIssue, closePullRequest, calculateWeight, calculateDuration } from "../../helpers"; +import { gitLinkedPrParser } from "../../helpers/parser"; import { Payload, LabelItem } from "../../types"; import { deadLinePrefix } from "../shared"; @@ -72,13 +73,19 @@ export const closePullRequestForAnIssue = async (): Promise => { const payload = context.payload as Payload; if (!payload.issue?.number) return; - const prs = await getOpenedPullRequestsForAnIssue(payload.issue.number, ""); + const prs = await gitLinkedPrParser({ + owner: payload.repository.owner.login, + repo: payload.repository.name, + issue_number: payload.issue.number, + }); + if (!prs.length) return; + 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].number); - comment += ` #${prs[i].number} `; + await closePullRequest(prs[i].prNumber); + comment += ` #${prs[i].prNumber} `; } await addCommentToIssue(comment, payload.issue.number); }; diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index e43f9d4c5..e2a903f55 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -9,7 +9,7 @@ import { getTokenSymbol, parseComments, } from "../../helpers"; -import { gitLinkedPrParser } from "../../helpers/parser"; +import { getLatestPullRequest, gitLinkedPrParser } from "../../helpers/parser"; import { Incentives, MarkdownItem, Payload, StateReason, UserType } from "../../types"; import { commentParser } from "../comment"; import Decimal from "decimal.js"; @@ -167,9 +167,14 @@ export const incentivizePullRequestReviews = async () => { return; } - const linkedPullRequest = await gitLinkedPrParser({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issue.number }); + const linkedPullRequest = await gitLinkedPrParser({ + owner: payload.repository.owner.login, + repo: payload.repository.name, + issue_number: issue.number, + }); + const latestLinkedPullRequest = await getLatestPullRequest(linkedPullRequest); - if (!linkedPullRequest) { + if (!latestLinkedPullRequest) { logger.debug(`incentivizePullRequestReviews: No linked pull requests found`); return; } @@ -190,8 +195,8 @@ export const incentivizePullRequestReviews = async () => { return; } - const prReviews = await getAllPullRequestReviews(context, linkedPullRequest.number, "full"); - const prComments = await getAllIssueComments(linkedPullRequest.number, "full"); + const prReviews = await getAllPullRequestReviews(context, latestLinkedPullRequest.number, "full"); + const prComments = await getAllIssueComments(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/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts index 880c29974..ec057ce73 100644 --- a/src/handlers/wildcard/unassign.ts +++ b/src/handlers/wildcard/unassign.ts @@ -1,4 +1,3 @@ -import { closePullRequestForAnIssue } from "../assign"; import { getBotConfig, getBotContext, getLogger } from "../../bindings"; import { GLOBAL_STRINGS } from "../../configs/strings"; import { @@ -67,7 +66,6 @@ const checkBountyToUnassign = async (issue: Issue): Promise => { logger.info( `Unassigning... lastActivityTime: ${lastActivity.getTime()}, curTime: ${curTimestamp}, passedDuration: ${passedDuration}, followUpTime: ${followUpTime}, disqualifyTime: ${disqualifyTime}` ); - await closePullRequestForAnIssue(); // remove assignees from the issue await removeAssignees(issue.number, assignees); await addCommentToIssue(`@${assignees[0]} - ${unassignComment} \nLast activity time: ${lastActivity}`, issue.number); diff --git a/src/helpers/parser.ts b/src/helpers/parser.ts index 93d249404..42821a5b5 100644 --- a/src/helpers/parser.ts +++ b/src/helpers/parser.ts @@ -2,7 +2,6 @@ import axios from "axios"; import { HTMLElement, parse } from "node-html-parser"; import { getPullByNumber } from "./issue"; import { getBotContext, getLogger } from "../bindings"; -import { Payload } from "../types"; interface GitParser { owner: string; @@ -11,23 +10,12 @@ interface GitParser { pull_number?: number; } -export const gitIssueParser = async ({ owner, repo, issue_number }: GitParser): Promise => { - try { - const { data } = await axios.get(`https://github.com/${owner}/${repo}/issues/${issue_number}`); - const dom = parse(data); - const devForm = dom.querySelector("[data-target='create-branch.developmentForm']") as HTMLElement; - const linkedPRs = devForm.querySelectorAll(".my-1"); - if (linkedPRs.length > 0) { - //has LinkedPRs - return true; - } else { - //no LinkedPRs - return false; - } - } catch (error) { - return true; - } -}; +export interface LinkedPR { + prOrganization: string; + prRepository: string; + prNumber: number; + prHref: string; +} export const gitLinkedIssueParser = async ({ owner, repo, pull_number }: GitParser) => { const logger = getLogger(); @@ -49,38 +37,57 @@ export const gitLinkedIssueParser = async ({ owner, repo, pull_number }: GitPars } }; -export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser) => { +export const gitLinkedPrParser = async ({ owner, repo, issue_number }: GitParser): Promise => { const logger = getLogger(); try { + const prData = []; const { data } = await axios.get(`https://github.com/${owner}/${repo}/issues/${issue_number}`); - const context = getBotContext(); - const payload = context.payload as Payload; const dom = parse(data); const devForm = dom.querySelector("[data-target='create-branch.developmentForm']") as HTMLElement; const linkedPRs = devForm.querySelectorAll(".my-1"); - if (linkedPRs.length === 0) return null; - let linkedPullRequest = null; + if (linkedPRs.length === 0) return []; + for (const linkedPr of linkedPRs) { - const prHref = linkedPr.querySelector("a")?.attrs?.href || ""; - const parts = prHref.split("/"); - // extract the organization name and repo name from the link:(e.g. "https://github.com/wannacfuture/ubiquibot/pull/5";) - const organization = parts[parts.length - 4]; - const repository = parts[parts.length - 3]; - - if (`${organization}/${repository}` !== payload.repository.full_name) continue; - const prNumber = parts[parts.length - 1]; - if (Number.isNaN(Number(prNumber))) return null; - const pr = await getPullByNumber(context, Number(prNumber)); - if (!pr || !pr.merged) continue; - - if (!linkedPullRequest) linkedPullRequest = pr; - else if (linkedPullRequest.merged_at && pr.merged_at && new Date(linkedPullRequest.merged_at) < new Date(pr.merged_at)) { - linkedPullRequest = pr; - } + const prUrl = linkedPr.querySelector("a")?.attrs?.href; + + if (!prUrl) continue; + + const parts = prUrl.split("/"); + + // check if array size is at least 4 + if (parts.length < 4) continue; + + // extract the organization name and repo name from the link:(e.g. " + const prOrganization = parts[parts.length - 4]; + const prRepository = parts[parts.length - 3]; + const prNumber = Number(parts[parts.length - 1]); + const prHref = `https://github.com${prUrl}`; + + if (`${prOrganization}/${prRepository}` !== `${owner}/${repo}`) continue; + + prData.push({ prOrganization, prRepository, prNumber, prHref }); } - return linkedPullRequest; + + return prData; } catch (error) { logger.error(`${JSON.stringify(error)}`); - return null; + return []; } }; + +export const getLatestPullRequest = async (prs: LinkedPR[]) => { + const context = getBotContext(); + let linkedPullRequest = null; + for (const _pr of prs) { + if (Number.isNaN(_pr.prNumber)) return null; + const pr = await getPullByNumber(context, _pr.prNumber); + if (!pr || !pr.merged) continue; + + if (!linkedPullRequest) linkedPullRequest = pr; + else if (linkedPullRequest.merged_at && pr.merged_at && new Date(linkedPullRequest.merged_at) < new Date(pr.merged_at)) { + linkedPullRequest = pr; + } + } + + return linkedPullRequest; +}; diff --git a/yarn.lock b/yarn.lock index 89f4cdf2c..68cf237fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2235,6 +2235,13 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== +"@types/parse5@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-7.0.0.tgz#8b412a0a4461c84d6280a372bfa8c57a418a06bd" + integrity sha512-f2SeAxumolBmhuR62vNGTsSAvdz/Oj0k682xNrcKJ4dmRnTPODB74j6CPoNPzBPTHsu7Y7W7u93Mgp8Ovo8vWw== + dependencies: + parse5 "*" + "@types/phoenix@^1.5.4": version "1.6.0" resolved "https://registry.yarnpkg.com/@types/phoenix/-/phoenix-1.6.0.tgz#eb7536259ee695646e75c4c7b0c9a857ea174781" @@ -6896,18 +6903,18 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse5@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - -parse5@^7.1.2: +parse5@*, parse5@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== dependencies: entities "^4.4.0" +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" From dba8308779aa1ea74e5a98c227a5e92ae3307d9b Mon Sep 17 00:00:00 2001 From: me505 <62057938+me505@users.noreply.github.com> Date: Tue, 19 Sep 2023 23:23:23 +0530 Subject: [PATCH 106/106] feat: add repo specific wallet support (#747) * feat: add repo specific wallet support * feat: repo specific wallet support * feat: repo specific wallet support * feat: add repo specific wallet support --------- Co-authored-by: whilefoo <139262667+whilefoo@users.noreply.github.com> --- src/handlers/push/index.ts | 4 ++-- src/types/config.ts | 7 +------ src/utils/private.ts | 18 +++++++++++++----- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/handlers/push/index.ts b/src/handlers/push/index.ts index 66dac755f..340143439 100644 --- a/src/handlers/push/index.ts +++ b/src/handlers/push/index.ts @@ -1,6 +1,6 @@ import { getBotContext, getLogger } from "../../bindings"; import { createCommitComment, getFileContent } from "../../helpers"; -import { CommitsPayload, PushPayload, WideOrgConfigSchema } from "../../types"; +import { CommitsPayload, PushPayload, WideConfigSchema } from "../../types"; import { parseYAML } from "../../utils/private"; import { updateBaseRate } from "./update-base"; import { validate } from "../../utils/ajv"; @@ -87,7 +87,7 @@ export const validateConfigChange = async () => { if (configFileContent) { const decodedConfig = Buffer.from(configFileContent, "base64").toString(); const config = parseYAML(decodedConfig); - const { valid, error } = validate(WideOrgConfigSchema, config); + const { valid, error } = validate(WideConfigSchema, config); if (!valid) { await createCommitComment(`@${payload.sender.login} Config validation failed! ${error}`, commitSha, BASE_RATE_FILE); } diff --git a/src/types/config.ts b/src/types/config.ts index c162ece1a..b4dedf3d0 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -156,6 +156,7 @@ export const WideConfigSchema = Type.Object( "register-wallet-with-verification": Type.Optional(Type.Boolean()), "enable-access-control": Type.Optional(AccessControlSchema), "stale-bounty-time": Type.Optional(Type.String()), + "private-key-encrypted": Type.Optional(Type.String()), }, { additionalProperties: false, @@ -166,12 +167,6 @@ export type WideConfig = Static; export type WideRepoConfig = WideConfig; -export const WideOrgConfigSchema = Type.Composite([Type.Object({ "private-key-encrypted": Type.Optional(Type.String()) }), WideConfigSchema], { - additionalProperties: false, -}); - -export type WideOrgConfig = Static; - export const MergedConfigSchema = Type.Object({ "evm-network-id": Type.Number(), "price-multiplier": Type.Number(), diff --git a/src/utils/private.ts b/src/utils/private.ts index d0cb329c5..683a9014c 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -6,7 +6,7 @@ import merge from "lodash/merge"; import { DefaultConfig } from "../configs"; import { validate } from "./ajv"; -import { WideConfig, WideOrgConfig, WideRepoConfig, WideConfigSchema, WideOrgConfigSchema } from "../types"; +import { WideConfig, WideRepoConfig, WideConfigSchema } from "../types"; const CONFIG_REPO = "ubiquibot-config"; const CONFIG_PATH = ".github/ubiquibot-config.yml"; @@ -35,7 +35,7 @@ export const getConfigSuperset = async (context: Context, type: "org" | "repo", export interface MergedConfigs { parsedRepo: WideRepoConfig | undefined; - parsedOrg: WideOrgConfig | undefined; + parsedOrg: WideRepoConfig | undefined; parsedDefault: MergedConfig; } @@ -111,10 +111,10 @@ export const getWideConfig = async (context: Context) => { const orgConfig = await getConfigSuperset(context, "org", CONFIG_PATH); const repoConfig = await getConfigSuperset(context, "repo", CONFIG_PATH); - const parsedOrg: WideOrgConfig | undefined = parseYAML(orgConfig); + const parsedOrg: WideRepoConfig | undefined = parseYAML(orgConfig); if (parsedOrg) { - const { valid, error } = validate(WideOrgConfigSchema, parsedOrg); + const { valid, error } = validate(WideConfigSchema, parsedOrg); if (!valid) { throw new Error(`Invalid org config: ${error}`); } @@ -127,7 +127,15 @@ export const getWideConfig = async (context: Context) => { } } const parsedDefault: MergedConfig = DefaultConfig; - const privateKeyDecrypted = parsedOrg && parsedOrg[KEY_NAME] ? await getPrivateKey(parsedOrg[KEY_NAME]) : undefined; + + let privateKeyDecrypted; + if (parsedRepo && parsedRepo[KEY_NAME]) { + privateKeyDecrypted = await getPrivateKey(parsedRepo[KEY_NAME]); + } else if (parsedOrg && parsedOrg[KEY_NAME]) { + privateKeyDecrypted = await getPrivateKey(parsedOrg[KEY_NAME]); + } else { + privateKeyDecrypted = undefined; + } const configs: MergedConfigs = { parsedDefault, parsedOrg, parsedRepo }; const mergedConfigData: MergedConfig = mergeConfigs(configs);