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

Debug comment incentive #848

Merged
merged 2 commits into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/handlers/comment/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface RewardsResponse {
penaltyAmount: BigNumber;
user: string;
userId: number;
debug: Record<string, { count: number; reward: Decimal }>;
}[];
fallbackReward?: Record<string, Decimal>;
}
Expand Down
8 changes: 7 additions & 1 deletion src/handlers/payout/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface RewardByUser {
type: (string | undefined)[];
user: string | undefined;
priceArray: string[];
debug: Record<string, { count: number; reward: Decimal }>;
}

/**
Expand Down Expand Up @@ -324,6 +325,7 @@ export const calculateIssueAssigneeReward = async (incentivesCalculation: Incent
account: account || "0x",
user: "",
userId: incentivesCalculation.assignee.id,
debug: {},
},
],
};
Expand Down Expand Up @@ -369,6 +371,7 @@ export const handleIssueClosed = async (
type: [conversationRewards.title],
user: permit.user,
priceArray: [permit.priceInEth.toString()],
debug: permit.debug,
});
}
});
Expand All @@ -387,6 +390,7 @@ export const handleIssueClosed = async (
type: [pullRequestReviewersReward.title],
user: permit.user,
priceArray: [permit.priceInEth.toString()],
debug: permit.debug,
});
}
});
Expand All @@ -403,6 +407,7 @@ export const handleIssueClosed = async (
type: [creatorReward.title],
user: creatorReward.username,
priceArray: [creatorReward.reward[0].priceInEth.toString()],
debug: creatorReward.reward[0].debug,
});
} else if (creatorReward && creatorReward.reward && creatorReward.reward[0].account === "0x") {
logger.info(`Skipping to generate a permit url for missing account. fallback: ${creatorReward.fallbackReward}`);
Expand All @@ -424,6 +429,7 @@ export const handleIssueClosed = async (
type: title,
user: assigneeReward.username,
priceArray: [assigneeReward.reward[0].priceInEth.toString()],
debug: assigneeReward.reward[0].debug,
});

if (permitComments.length > 0) {
Expand Down Expand Up @@ -497,7 +503,7 @@ export const handleIssueClosed = async (

const price = `${reward.priceInEth} ${incentivesCalculation.tokenSymbol.toUpperCase()}`;

const comment = createDetailsTable(price, payoutUrl, reward.user, detailsValue);
const comment = createDetailsTable(price, payoutUrl, reward.user, detailsValue, reward.debug);

await savePermitToDB(Number(reward.userId), txData);
permitComment += comment;
Expand Down
67 changes: 51 additions & 16 deletions src/handlers/payout/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,25 +67,32 @@ export const calculateIssueConversationReward = async (calculateIncentives: Ince
const fallbackReward: Record<string, Decimal> = {};

// array of awaiting permits to generate
const reward: { account: string; priceInEth: Decimal; userId: number; user: string; penaltyAmount: BigNumber }[] = [];
const reward: {
account: string;
priceInEth: Decimal;
userId: number;
user: string;
penaltyAmount: BigNumber;
debug: Record<string, { count: number; reward: Decimal }>;
}[] = [];

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)) {
if (rewardValue.sum.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}`);
logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue.sum}`);
const account = await getWalletAddress(user);
const priceInEth = rewardValue.mul(calculateIncentives.baseMultiplier);
const priceInEth = rewardValue.sum.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) });
reward.push({ account, priceInEth, userId: commentsByUser.id, user, penaltyAmount: BigNumber.from(0), debug: rewardValue.sumByType });
} else {
fallbackReward[user] = priceInEth;
}
Expand Down Expand Up @@ -147,6 +154,7 @@ export const calculateIssueCreatorReward = async (incentivesCalculation: Incenti
userId: creator.id,
user: "",
penaltyAmount: BigNumber.from(0),
debug: {},
},
],
};
Expand Down Expand Up @@ -212,7 +220,14 @@ export const calculatePullRequestReviewsReward = async (incentivesCalculation: I
logger.info(`calculatePullRequestReviewsReward: Filtering by the user type done. commentsByUser: ${JSON.stringify(prReviewsByUser)}`);

// array of awaiting permits to generate
const reward: { account: string; priceInEth: Decimal; userId: number; user: string; penaltyAmount: BigNumber }[] = [];
const reward: {
account: string;
priceInEth: Decimal;
userId: number;
user: string;
penaltyAmount: BigNumber;
debug: Record<string, { count: number; reward: Decimal }>;
}[] = [];

// The mapping between gh handle and amount in ETH
const fallbackReward: Record<string, Decimal> = {};
Expand All @@ -221,20 +236,22 @@ export const calculatePullRequestReviewsReward = async (incentivesCalculation: I
const commentByUser = prReviewsByUser[user];
const commentsByNode = await parseComments(commentByUser.comments, ItemsToExclude);
const rewardValue = calculateRewardValue(commentsByNode, incentivesCalculation.incentives);
if (rewardValue.equals(0)) {
if (rewardValue.sum.equals(0)) {
logger.info(`calculatePullRequestReviewsReward: Skipping to generate a permit url because the reward value is 0. user: ${user}`);
continue;
}
logger.info(`calculatePullRequestReviewsReward: Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`);
logger.info(
`calculatePullRequestReviewsReward: Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue.sum}`
);
const account = await getWalletAddress(user);
const priceInEth = rewardValue.mul(incentivesCalculation.baseMultiplier);
const priceInEth = rewardValue.sum.mul(incentivesCalculation.baseMultiplier);
if (priceInEth.gt(incentivesCalculation.paymentPermitMaxPrice)) {
logger.info(`calculatePullRequestReviewsReward: Skipping comment reward for user ${user} because reward is higher than payment permit max price`);
continue;
}

if (account) {
reward.push({ account, priceInEth, userId: commentByUser.id, user, penaltyAmount: BigNumber.from(0) });
reward.push({ account, priceInEth, userId: commentByUser.id, user, penaltyAmount: BigNumber.from(0), debug: rewardValue.sumByType });
} else {
fallbackReward[user] = priceInEth;
}
Expand All @@ -256,13 +273,13 @@ const generatePermitForComments = async (
const logger = getLogger();
const commentsByNode = await parseComments(comments, ItemsToExclude);
const rewardValue = calculateRewardValue(commentsByNode, incentives);
if (rewardValue.equals(0)) {
if (rewardValue.sum.equals(0)) {
logger.info(`No reward for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`);
return;
}
logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`);
logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue.sum}`);
const account = await getWalletAddress(user);
const amountInETH = rewardValue.mul(multiplier);
const amountInETH = rewardValue.sum.mul(multiplier);
if (amountInETH.gt(paymentPermitMaxPrice)) {
logger.info(`Skipping issue creator reward for user ${user} because reward is higher than payment permit max price`);
return;
Expand All @@ -280,28 +297,46 @@ const generatePermitForComments = async (
* @param incentives - The basic price table for reward calculation
* @returns - The reward value
*/
const calculateRewardValue = (comments: Record<string, string[]>, incentives: Incentives): Decimal => {
const calculateRewardValue = (
comments: Record<string, string[]>,
incentives: Incentives
): { sum: Decimal; sumByType: Record<string, { count: number; reward: Decimal }> } => {
let sum = new Decimal(0);
const sumByType: Record<string, { count: number; reward: Decimal }> = {};

for (const key of Object.keys(comments)) {
const value = comments[key];

// Initialize the sum for this key if it doesn't exist
if (!sumByType[key]) {
sumByType[key] = {
count: 0,
reward: new Decimal(0),
};
}

// if it's a text node calculate word count and multiply with the reward value
if (key == "#text") {
if (!incentives.comment.totals.word) {
continue;
}
const wordReward = new Decimal(incentives.comment.totals.word);
const reward = wordReward.mul(value.map((str) => str.trim().split(" ").length).reduce((totalWords, wordCount) => totalWords + wordCount, 0));
const wordCount = value.map((str) => str.trim().split(" ").length).reduce((totalWords, wordCount) => totalWords + wordCount, 0);
const reward = wordReward.mul(wordCount);
sumByType[key].count += wordCount;
sumByType[key].reward = wordReward;
sum = sum.add(reward);
} else {
if (!incentives.comment.elements[key]) {
continue;
}
const rewardValue = new Decimal(incentives.comment.elements[key]);
const reward = rewardValue.mul(value.length);
sumByType[key].count += value.length;
sumByType[key].reward = rewardValue;
sum = sum.add(reward);
}
}

return sum;
return { sum, sumByType };
};
57 changes: 55 additions & 2 deletions src/helpers/comment.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Decimal from "decimal.js";
import { isEmpty } from "lodash";
import * as parse5 from "parse5";

type Node = {
Expand Down Expand Up @@ -41,12 +43,46 @@ export const parseComments = (comments: string[], itemsToExclude: string[]): Rec
return result;
};

export const generateCollapsibleTable = (data: { element: string; units: number; reward: Decimal }[]) => {
// Check if the data array is empty
if (data.length === 0) {
return "No data to display.";
}

// Create the table header row
const headerRow = "| element | units | reward |\n| --- | --- | --- |";

// Create the table rows from the data array
const tableRows = data.map((item) => `| ${item.element} | ${item.units} | ${item.reward} |`).join("\n");

// Create the complete Markdown table
const tableMarkdown = `
<details>
<summary>Details</summary>
${headerRow}
${tableRows}
</details>
`;

return tableMarkdown;
};

export const createDetailsTable = (
amount: string,
paymentURL: string,
username: string,
values: { title: string; subtitle: string; value: string }[]
values: { title: string; subtitle: string; value: string }[],
debug: Record<
string,
{
count: number;
reward: Decimal;
}
>
): string => {
let collapsibleTable = null;
// Generate the table rows based on the values array
const tableRows = values
.map(({ title, value, subtitle }) => {
Expand All @@ -61,6 +97,19 @@ export const createDetailsTable = (
})
.join("");

if (!isEmpty(debug)) {
const data = Object.entries(debug)
.filter(([_, value]) => value.count > 0)
.map(([key, value]) => {
const element = key === "#text" ? "words" : key;
const units = value.count;
const reward = value.reward;
return { element, units, reward };
});

collapsibleTable = generateCollapsibleTable(data);
}

// Construct the complete HTML structure
const html = `
<details>
Expand All @@ -77,11 +126,15 @@ export const createDetailsTable = (
${tableRows}
</tbody>
</table>
${collapsibleTable ? "COLLAPSIBLE_TABLE_PLACEHOLDER" : ""}
</details>
`;

// Remove spaces and line breaks from the HTML, ignoring the attributes like <a href="..."> and [ ... ]
const cleanedHtml = html.replace(/>\s+</g, "><").replace(/[\r\n]+/g, "");

return cleanedHtml;
// Add collapsible table here to avoid compression
const finalHtml = cleanedHtml.replace("COLLAPSIBLE_TABLE_PLACEHOLDER", collapsibleTable || "");

return finalHtml;
};