Skip to content
This repository has been archived by the owner on Sep 19, 2024. It is now read-only.

feat: selectivly enable incentives #781

Closed
wants to merge 12 commits into from
8 changes: 8 additions & 0 deletions src/configs/ubiquibot-config-default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export const DefaultConfig: MergedConfig = {
},
],
commandSettings: [
{
name: "start",
enabled: false,
},
{
name: "start",
enabled: false,
Expand All @@ -72,6 +76,10 @@ export const DefaultConfig: MergedConfig = {
name: "query",
enabled: false,
},
{
name: "comment-incentive",
enabled: false,
},
{
name: "ask",
enabled: false,
Expand Down
1 change: 1 addition & 0 deletions src/handlers/comment/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum IssueCommentCommands {
PAYOUT = "/payout", // request permit payout
MULTIPLIER = "/multiplier", // set bounty multiplier (for treasury)
QUERY = "/query",
COMMENTINCENTIVE = "/comment-incentive",
ASK = "/ask", // ask GPT a question
// Access Controls

Expand Down
23 changes: 23 additions & 0 deletions src/handlers/comment/handlers/comment-incentives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getBotContext, getLogger } from "../../../bindings";
import { Payload } from "../../../types";

export const commentIncentive = async (body: string) => {
const context = getBotContext();
const logger = getLogger();
const payload = context.payload as Payload;
const sender = payload.sender.login;

logger.info(`Received '/comment-incentive' command from user: ${sender}`);

if (!payload.issue) {
logger.info(`Skipping '/comment-incentive' because of no issue instance`);
return `Skipping '/comment-incentive' because of no issue instance`;
}
const parts = body.split(" ");
parts.shift();
if (parts.pop() !== "true" && parts.pop() !== "false") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can create a helper function that parses the command so you can use it here and in getIncentivizedUsers to avoid repeating code

return `invalid syntax for /comment-incentive \n usage /comment-incentive @user @user1... true|false \n ex /comment-incentive @user true`;
} else {
return;
}
};
8 changes: 8 additions & 0 deletions src/handlers/comment/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { autoPay } from "./payout";
import { getTargetPriceLabel } from "../../shared";
import Decimal from "decimal.js";
import { ErrorDiff } from "../../../utils/helpers";
import { commentIncentive } from "./comment-incentives";

export * from "./assign";
export * from "./wallet";
Expand All @@ -46,6 +47,7 @@ export * from "./payout";
export * from "./help";
export * from "./multiplier";
export * from "./query";
export * from "./comment-incentives";
export * from "./ask";
export * from "./authorize";

Expand Down Expand Up @@ -282,6 +284,12 @@ export const userCommands = (): UserCommands[] => {
handler: autoPay,
callback: commandCallback,
},
{
id: IssueCommentCommands.COMMENTINCENTIVE,
description: `Enabled or disables comment incentives for a user`,
handler: commentIncentive,
callback: commandCallback,
},
{
id: IssueCommentCommands.QUERY,
description: `Comments the users multiplier and address`,
Expand Down
58 changes: 37 additions & 21 deletions src/handlers/payout/post.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { getWalletAddress } from "../../adapters/supabase";
import { getBotContext, getLogger } from "../../bindings";
import { getAllIssueComments, getAllPullRequestReviews, getIssueDescription, parseComments } from "../../helpers";
import {
getAllIssueComments,
getAllPullRequestReviews,
getIncentivizedUsers,
getIssueDescription,
parseComments,
} from "../../helpers";
import { getLatestPullRequest, gitLinkedPrParser } from "../../helpers/parser";
import { Incentives, MarkdownItem, Payload, UserType } from "../../types";
import { RewardsResponse, commentParser } from "../comment";
Expand Down Expand Up @@ -46,13 +52,16 @@
logger.info("incentivizeComments: skipping payment permit generation because `assignee` is `undefined`.");
return { error: "incentivizeComments: skipping payment permit generation because `assignee` is `undefined`." };
}

const issueComments = await getAllIssueComments(calculateIncentives.issue.number, "full");

logger.info(`Getting the issue comments done. comments: ${JSON.stringify(issueComments)}`);
const issueCommentsByUser: Record<string, { id: string; comments: string[] }> = {};
for (const issueComment of issueComments) {
const user = issueComment.user;
if (user.type == UserType.Bot || user.login == assignee.login) continue;

if (user.type == UserType.Bot) continue;

const commands = commentParser(issueComment.body);
if (commands.length > 0) {
logger.info(`Skipping to parse the comment because it contains commands. comment: ${JSON.stringify(issueComment)}`);
Expand All @@ -71,35 +80,42 @@
}
logger.info(`Filtering by the user type done. commentsByUser: ${JSON.stringify(issueCommentsByUser)}`);

// The mapping between gh handle and comment with a permit url
const reward: Record<string, string> = {};

Check failure on line 84 in src/handlers/payout/post.ts

View workflow job for this annotation

GitHub Actions / build

Cannot redeclare block-scoped variable 'reward'.
const users = await getIncentivizedUsers(calculateIncentives.issue.number);
if (!users) return;

Check failure on line 86 in src/handlers/payout/post.ts

View workflow job for this annotation

GitHub Actions / build

Type 'undefined' is not assignable to type 'RewardsResponse'.

// The mapping between gh handle and amount in ETH
const fallbackReward: Record<string, Decimal> = {};

// array of awaiting permits to generate
const reward: { account: string; priceInEth: Decimal; userId: string; user: string; penaltyAmount: BigNumber }[] = [];

Check failure on line 92 in src/handlers/payout/post.ts

View workflow job for this annotation

GitHub Actions / build

Cannot redeclare block-scoped variable 'reward'.

for (const user of Object.keys(issueCommentsByUser)) {
const commentsByUser = issueCommentsByUser[user];
const commentsByNode = await parseComments(commentsByUser.comments, ItemsToExclude);
const rewardValue = calculateRewardValue(commentsByNode, calculateIncentives.incentives);
if (rewardValue.equals(0)) {
logger.info(`Skipping to generate a permit url because the reward value is 0. user: ${user}`);
continue;
}
logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`);
const account = await getWalletAddress(user);
const priceInEth = rewardValue.mul(calculateIncentives.baseMultiplier);
if (priceInEth.gt(calculateIncentives.paymentPermitMaxPrice)) {
logger.info(`Skipping comment reward for user ${user} because reward is higher than payment permit max price`);
continue;
}
if (account) {
reward.push({ account, priceInEth, userId: commentsByUser.id, user, penaltyAmount: BigNumber.from(0) });
} else {
fallbackReward[user] = priceInEth;
if (users[user] || users[user] == true) {
const commentsByUser = issueCommentsByUser[user];
const commentsByNode = await parseComments(commentsByUser.comments, ItemsToExclude);
const rewardValue = calculateRewardValue(commentsByNode, calculateIncentives.incentives);
if (rewardValue.equals(0)) {
logger.info(`Skipping to generate a permit url because the reward value is 0. user: ${user}`);
continue;
}
logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`);
const account = await getWalletAddress(user);
const priceInEth = rewardValue.mul(calculateIncentives.baseMultiplier);
if (priceInEth.gt(calculateIncentives.paymentPermitMaxPrice)) {
logger.info(`Skipping comment reward for user ${user} because reward is higher than payment permit max price`);
continue;
}
if (account) {
reward.push({ account, priceInEth, userId: commentsByUser.id, user, penaltyAmount: BigNumber.from(0) });

Check failure on line 111 in src/handlers/payout/post.ts

View workflow job for this annotation

GitHub Actions / build

This expression is not callable.
} else {
fallbackReward[user] = priceInEth;
}
}
}

return { error: "", title, reward, fallbackReward };

Check failure on line 118 in src/handlers/payout/post.ts

View workflow job for this annotation

GitHub Actions / build

Type 'Record<string, string>' is missing the following properties from type '{ account: string; priceInEth: Decimal; penaltyAmount: BigNumber; user: string; userId: string; }[]': length, pop, push, concat, and 26 more.
};

export const calculateIssueCreatorReward = async (incentivesCalculation: IncentivesCalculationResult): Promise<RewardsResponse> => {
Expand Down
26 changes: 26 additions & 0 deletions src/helpers/issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,32 @@ export const listAllIssuesForRepo = async (state: "open" | "closed" | "all" = "o
return issuesArr;
};

export const getIncentivizedUsers = async (issue_number: number) => {
const comments = await getAllIssueComments(issue_number);
const incentiveComments = comments.filter((comment) => comment.body.startsWith("/comment-incentives"));
let users: { [key: string]: boolean } = {};
for (const incentiveComment of incentiveComments) {
const parts = incentiveComment.body.split(" ");
parts.shift();
if (parts.pop() == "true") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this relies on command order. just use a for loop and check for true, false, or starts with @ (there shouldn't more than one true or false). you can look at this example

for (const part of parts) {
if (part.startsWith("@")) {
users[part.substring(1)] = true;
}
}
} else if (parts.pop() == "false") {
for (const part of parts) {
if (part.startsWith("@")) {
users[part.substring(1)] = true;
}
}
} else {
return undefined;
}
}
return users;
};

export const addCommentToIssue = async (msg: string, issue_number: number) => {
const context = getBotContext();
const logger = getLogger();
Expand Down
Loading