Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add pull review parsing and reward parsing #218

Open
wants to merge 68 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
5c9fa6f
feat: add pull review parsing and reward parsing
ishowvel Dec 14, 2024
14e6964
chore: remove testing leftovers
ishowvel Dec 14, 2024
7d5193e
chore: remove testing leftovers
ishowvel Dec 14, 2024
63ff526
feat: add mod to processor, fix the mod, add metadata to comment
ishowvel Dec 15, 2024
fba1b27
fix: cspell and one spelling mistake
ishowvel Dec 15, 2024
c34ee34
chore: remove review id
ishowvel Dec 15, 2024
fc5a63b
feat: add support for priority label
ishowvel Dec 18, 2024
e4d52d9
chore: updated manifest.json and dist build
github-actions[bot] Dec 18, 2024
81b1595
feat: mul the reward by priority
ishowvel Dec 18, 2024
b131a80
chore: retain parsing comments
ishowvel Dec 18, 2024
383d67d
chore: updated manifest.json and dist build
github-actions[bot] Dec 18, 2024
74953a7
chore: updated manifest.json and dist build
github-actions[bot] Dec 18, 2024
e0cd2f4
feat: refactor existing test results and add tests
ishowvel Dec 20, 2024
3d285f5
chore: updated manifest.json and dist build
github-actions[bot] Dec 20, 2024
95a187f
Merge remote-tracking branch 'upstream/development' into review-incen…
ishowvel Dec 20, 2024
8f17b06
chore: refactor and fix tests
ishowvel Dec 20, 2024
cb06d40
chore: updated manifest.json and dist build
github-actions[bot] Dec 20, 2024
f2e522b
fix: refactor failing tests
ishowvel Dec 20, 2024
a2093ed
fix: add a description to the new config
ishowvel Dec 28, 2024
4f2eb73
chore: updated manifest.json and dist build
github-actions[bot] Dec 28, 2024
6d437b7
fix: add example configs
ishowvel Dec 31, 2024
31848ae
chore: add {} block
ishowvel Dec 31, 2024
7093922
chore: updated manifest.json and dist build
github-actions[bot] Dec 31, 2024
1a6b2f2
chore: fix commit parser and refactor code
ishowvel Jan 2, 2025
8067d73
Merge branch 'review-incentive' of https://github.com/ishowvel/text-c…
ishowvel Jan 2, 2025
1f0171d
chore: add 1 more review reward to each test
ishowvel Jan 2, 2025
3d38c0f
chore: updated manifest.json and dist build
github-actions[bot] Jan 2, 2025
880c720
chore: only add changes when ALL patterns dont match
ishowvel Jan 2, 2025
7644062
fix: review parsing and make code more efficient
ishowvel Jan 5, 2025
7625773
Merge branch 'review-incentive' of https://github.com/ishowvel/text-c…
ishowvel Jan 5, 2025
9be1324
chore: updated manifest.json and dist build
github-actions[bot] Jan 5, 2025
43a9d26
update comment
ishowvel Jan 5, 2025
03823ab
fix: file exclusion, refcator pull fetching and add a test
ishowvel Jan 7, 2025
9194551
Merge branch 'review-incentive' of https://github.com/ishowvel/text-c…
ishowvel Jan 7, 2025
bb6074d
chore: updated manifest.json and dist build
github-actions[bot] Jan 7, 2025
367064b
feat: add support for multiple pull and assignees
ishowvel Jan 8, 2025
4987882
chore: updated manifest.json and dist build
github-actions[bot] Jan 8, 2025
3ac6bcc
chore: refactor test checkpoints
ishowvel Jan 8, 2025
be1cdca
fix: only fetch merged pulls and use cached reviews
ishowvel Jan 9, 2025
487b9d8
chore: updated manifest.json and dist build
github-actions[bot] Jan 9, 2025
87456c1
fix: stop review rewards from reinitializing
ishowvel Jan 11, 2025
e76d914
chore: updated manifest.json and dist build
github-actions[bot] Jan 11, 2025
a3c7f97
Merge remote-tracking branch 'upstream/development' into review-incen…
ishowvel Jan 11, 2025
fbf72c8
Merge branch 'review-incentive' of https://github.com/ishowvel/text-c…
ishowvel Jan 11, 2025
12c327f
chore: updated manifest.json and dist build
github-actions[bot] Jan 11, 2025
cd48565
chore: remove testing leftover and reduce api usage
ishowvel Jan 12, 2025
20d0c5e
chore: updated manifest.json and dist build
github-actions[bot] Jan 12, 2025
e370a3b
fix: testing leftover
ishowvel Jan 14, 2025
3523e59
Merge remote-tracking branch 'upstream/development' into review-incen…
ishowvel Jan 14, 2025
809b325
chore: updated manifest.json and dist build
github-actions[bot] Jan 14, 2025
d2ee5d5
chore: use html url instead of api url
ishowvel Jan 14, 2025
f50b037
chore: updated manifest.json and dist build
github-actions[bot] Jan 14, 2025
6aaf5fd
feat: seperate diff pull reviews to diff tables
ishowvel Jan 15, 2025
e2bb265
chore: updated manifest.json and dist build
github-actions[bot] Jan 15, 2025
21fe3c6
chore: update tests
ishowvel Jan 15, 2025
3afb0a1
chore: updaten contribution overview table
ishowvel Jan 16, 2025
070af86
chore: updated manifest.json and dist build
github-actions[bot] Jan 16, 2025
ba8db72
chore: update table and tests
ishowvel Jan 16, 2025
db70c57
chore: updated manifest.json and dist build
github-actions[bot] Jan 16, 2025
7061e76
Update src/web/.ubiquity-os.config.yml
ishowvel Jan 20, 2025
9ae7f9d
chore: cap each type of reward instead of capping sum of reward, cap …
ishowvel Jan 20, 2025
941223e
chore: fix config and sync with upstream branch
ishowvel Jan 21, 2025
6e85e1d
chore: updated manifest.json and dist build
github-actions[bot] Jan 21, 2025
404b939
fix: enable cross fork diffs, update tests and bump plugin sdk version
ishowvel Jan 23, 2025
6b3241a
Merge remote-tracking branch 'upstream/development' into review-incen…
ishowvel Jan 23, 2025
fdcf1bf
chore: sync to upstream
ishowvel Jan 23, 2025
bf1e87e
fix: base branch can be from outside repo and skip review reward for …
ishowvel Jan 23, 2025
827e9f1
chore: update tests
ishowvel Jan 23, 2025
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 .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"language": "en",
"words": [
"dataurl",
"Incentivizer",
"devpool",
"outdir",
"servedir",
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ with:
redeemTask: true
dataPurge:
skipCommentsWhileAssigned: all
reviewIncentivizer:
baseRate: 100
conclusiveReviewCredit: 25
formattingEvaluator:
wordCountExponent: 0.85
multipliers:
Expand Down
Binary file modified bun.lockb
Binary file not shown.
20 changes: 10 additions & 10 deletions dist/index.js

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,30 @@
}
]
},
"reviewIncentivizer": {
"default": null,
"anyOf": [
{
"default": {},
"type": "object",
"properties": {
"baseRate": {
"default": 100,
"description": "Number of lines of code that equals $1 in review credit",
"type": "number"
},
"conclusiveReviewCredit": {
"default": 25,
"description": "Flat rate bonus in dollars for completing a conclusive review i.e (Approved or Changes Requested)",
"type": "number"
}
}
},
{
"type": "null"
}
]
},
"formattingEvaluator": {
"default": null,
"anyOf": [
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"js-tiktoken": "1.0.15",
"jsdom": "24.0.0",
"markdown-it": "14.1.0",
"minimatch": "^10.0.1",
"openai": "4.56.0",
"yaml": "^2.6.1"
},
Expand Down
23 changes: 23 additions & 0 deletions src/configuration/review-incentivizer-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Static, Type } from "@sinclair/typebox";

export const reviewIncentivizerConfigurationType = Type.Object(
{
/**
* Number of lines of code that equals $1 in review credit
*/
baseRate: Type.Number({
default: 100,
description: "Number of lines of code that equals $1 in review credit",
}),
/**
* Flat rate bonus in dollars for completing a conclusive review i.e (Approved or Changes Requested)
*/
conclusiveReviewCredit: Type.Number({
default: 25,
description: "Flat rate bonus in dollars for completing a conclusive review i.e (Approved or Changes Requested)",
}),
},
{ default: {} }
);

export type ReviewIncentivizerConfiguration = Static<typeof reviewIncentivizerConfigurationType>;
4 changes: 3 additions & 1 deletion src/data-collection/collect-linked-pulls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ export async function collectLinkedMergedPulls(context: ContextPlugin, issue: Is
issue_number,
});

return result.repository.issue.closedByPullRequestsReferences.edges.map((edge) => edge.node);
return result.repository.issue.closedByPullRequestsReferences.edges
.map((edge) => edge.node)
.filter((pull) => pull.state === "MERGED");
}
70 changes: 70 additions & 0 deletions src/helpers/excluded-files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ContextPlugin } from "../types/plugin-input";

interface GitAttributes {
pattern: string;
attributes: { [key: string]: string | boolean };
}

async function parseGitAttributes(content: string): Promise<GitAttributes[]> {
const lines = content.split("\n");
return lines
.map((line) => {
line = line.trim();
if (!line || line.startsWith("#")) return null;

const parts = line.split(/\s+/);
if (parts.length < 2) return null;

const pattern = parts[0];
const attributes: { [key: string]: string | boolean } = {};

for (let i = 1; i < parts.length; i++) {
const attr = parts[i];
if (attr.includes("=")) {
const [key, value] = attr.split("=");
attributes[key.trim()] = value.trim();
} else {
attributes[attr.trim()] = true;
}
}

return { pattern, attributes };
})
.filter((item): item is GitAttributes => item !== null);
}

export async function getExcludedFiles(context: ContextPlugin) {
const gitAttributesContent = await getFileContent(context, ".gitattributes");
if (!gitAttributesContent) {
return null;
}
const gitAttributesLinguistGenerated = (await parseGitAttributes(gitAttributesContent))
.filter((v) => v.attributes["linguist-generated"])
.map((v) => v.pattern);

return gitAttributesLinguistGenerated;
}

async function getFileContent(context: ContextPlugin, path: string): Promise<string | null> {
try {
const response = await context.octokit.rest.repos.getContent({
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
path,
});

// GitHub API returns content as base64
if ("content" in response.data && !Array.isArray(response.data)) {
return Buffer.from(response.data.content, "base64").toString("utf-8");
}
return null;
} catch (err) {
if (err instanceof Error && "status" in err && err.status === 404) {
context.logger.error(
`.gitattributes was not found for ${context.payload.repository.owner.login}/${context.payload.repository.name}`
);
return null;
}
throw context.logger.error(`Error fetching files to be excluded ${err}`);
}
}
17 changes: 17 additions & 0 deletions src/helpers/github.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import * as github from "@actions/github";
import { GitHubIssue } from "../github-types";

export function getGithubWorkflowRunUrl() {
return `${github.context.payload.repository?.html_url}/actions/runs/${github.context.runId}`;
}

export function parsePriorityLabel(labels: GitHubIssue["labels"] | undefined) {
if (!labels) return 1;

for (const label of labels) {
const priorityLabel = typeof label === "string" ? label : (label.name ?? "");
const matched = priorityLabel.match(/^Priority:\s*(\d+)/i);

if (matched) {
const urgency = Number(matched[1]);
if (urgency) return urgency;
}
}

return 1;
}
5 changes: 3 additions & 2 deletions src/issue-activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ export class IssueActivity {

private async _getLinkedReviews(): Promise<Review[]> {
this._context.logger.debug("Trying to fetch linked pull-requests for", this._issueParams);
const pulls = (await collectLinkedMergedPulls(this._context, this._issueParams)).slice(-1);
this._context.logger.debug("Collected linked pull-requests", { pulls });
const pulls = await collectLinkedMergedPulls(this._context, this._issueParams);
this._context.logger.debug(`Collected linked pull-requests ${pulls.map((v) => v.number)}`);

const promises = pulls
.map(async (pull) => {
const repository = pull.repository;
Expand Down
20 changes: 2 additions & 18 deletions src/parser/formatting-evaluator-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { BaseModule } from "../types/module";
import { GithubCommentScore, Result, WordResult } from "../types/results";
import { typeReplacer } from "../helpers/result-replacer";
import { ContextPlugin } from "../types/plugin-input";
import { GitHubIssue } from "../github-types";
import { parsePriorityLabel } from "../helpers/github";

interface Multiplier {
multiplier: number;
Expand Down Expand Up @@ -69,7 +69,7 @@ export class FormattingEvaluatorModule extends BaseModule {
const { formatting, words } = this._getFormattingScore(comment);
const multiplierFactor = this._multipliers?.[comment.type] ?? { multiplier: 0 };
const formattingTotal = this._calculateFormattingTotal(formatting, words, multiplierFactor).toDecimalPlaces(2);
const priority = this._parsePriorityLabel(data.self?.labels);
const priority = parsePriorityLabel(data.self?.labels);
const reward = (comment.score?.reward ? formattingTotal.add(comment.score.reward) : formattingTotal).toNumber();
comment.score = {
...comment.score,
Expand Down Expand Up @@ -192,20 +192,4 @@ export class FormattingEvaluatorModule extends BaseModule {

return { formatting, words };
}

_parsePriorityLabel(labels: GitHubIssue["labels"] | undefined) {
if (!labels) return 1;

for (const label of labels) {
const priorityLabel = typeof label === "string" ? label : (label.name ?? "");
const matched = priorityLabel.match(/^Priority:\s*(\d+)/i);

if (matched) {
const urgency = Number(matched[1]);
if (urgency) return urgency;
}
}

return 1;
}
}
92 changes: 81 additions & 11 deletions src/parser/github-comment-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { removeKeyFromObject, typeReplacer } from "../helpers/result-replacer";
import { getErc20TokenSymbol } from "../helpers/web3";
import { IssueActivity } from "../issue-activity";
import { BaseModule } from "../types/module";
import { GithubCommentScore, Result } from "../types/results";
import { GithubCommentScore, Result, ReviewScore } from "../types/results";

interface SortedTasks {
issues: { specification: GithubCommentScore | null; comments: GithubCommentScore[] };
Expand Down Expand Up @@ -134,15 +134,6 @@ export class GithubCommentModule extends BaseModule {

_createContributionRows(result: Result[0], sortedTasks: SortedTasks | undefined) {
const content: string[] = [];

if (result.task?.reward) {
content.push(buildContributionRow("Issue", "Task", result.task.multiplier, result.task.reward));
}

if (!sortedTasks) {
return content.join("");
}

function buildContributionRow(
view: string,
contribution: string,
Expand All @@ -158,6 +149,48 @@ export class GithubCommentModule extends BaseModule {
</tr>`;
}

if (result.task?.reward) {
content.push(buildContributionRow("Issue", "Task", result.task.multiplier, result.task.reward));
}

if (result.reviewRewards) {
result.reviewRewards.forEach((reviewReward) => {
const reviewRewardPullNumber = reviewReward.url.split("/").slice(-1)[0];
if (reviewReward.reviewBaseReward?.reward) {
content.push(
buildContributionRow(
"Review",
`Base Review for&nbsp;<a href="${reviewReward.url}" target="_blank" rel="noopener">#${reviewRewardPullNumber}</a>`,
1,
reviewReward.reviewBaseReward?.reward
)
);
}
});

const reviewCount = result.reviewRewards.reduce(
(total, reviewReward) => total + (reviewReward.reviews?.length ?? 0),
0
);

const totalReviewReward = result.reviewRewards.reduce(
(sum, reviewReward) =>
sum.add(
reviewReward.reviews?.reduce((subSum, review) => subSum.add(review.reward), new Decimal(0)) ??
new Decimal(0)
),
new Decimal(0)
);

if (reviewCount > 0) {
content.push(buildContributionRow("Review", "Code Review", reviewCount, totalReviewReward.toNumber()));
}
}

if (!sortedTasks) {
return content.join("");
}

if (sortedTasks.issues.specification) {
content.push(buildContributionRow("Issue", "Specification", 1, sortedTasks.issues.specification.score?.reward));
}
Expand Down Expand Up @@ -236,6 +269,43 @@ export class GithubCommentModule extends BaseModule {
return content.join("");
}

_createReviewRows(result: Result[0]) {
if (result.reviewRewards?.every((reviewReward) => reviewReward.reviews?.length === 0) || !result.reviewRewards) {
return "";
}

function buildReviewRow(review: ReviewScore) {
return `
<tr>
<td>+${review.effect.addition} -${review.effect.deletion}</td>
<td>${review.priority ?? "-"}</td>
<td>${review.reward}</td>
</tr>`;
}

const reviewTables = result.reviewRewards
.filter((reviewReward) => reviewReward.reviews && reviewReward.reviews.length > 0)
.map((reviewReward) => {
const rows = reviewReward.reviews?.map(buildReviewRow).join("") ?? "";
return `
<h6>Review Details for&nbsp;<a href="${reviewReward.url}" target="_blank" rel="noopener">#${reviewReward.url.split("/").slice(-1)[0]}</a></h6>
<table>
<thead>
<tr>
<th>Changes</th>
<th>Priority</th>
<th>Reward</th>
</tr>
</thead>
<tbody>
${rows}
</tbody>
</table>`;
})
.join("");

return reviewTables;
}
async _generateHtml(username: string, result: Result[0], taskReward: number, stripComments = false) {
const sortedTasks = result.comments?.reduce<SortedTasks>(
(acc, curr) => {
Expand All @@ -261,7 +331,6 @@ export class GithubCommentModule extends BaseModule {
const rewardsSum =
result.comments?.reduce<Decimal>((acc, curr) => acc.add(curr.score?.reward ?? 0), new Decimal(0)) ??
new Decimal(0);
// The task reward can be 0 if either there is no pricing tag or if there is no assignee
const isCapped = taskReward > 0 && rewardsSum.gt(taskReward);

return `
Expand Down Expand Up @@ -296,6 +365,7 @@ export class GithubCommentModule extends BaseModule {
${this._createContributionRows(result, sortedTasks)}
</tbody>
</table>
${!stripComments ? this._createReviewRows(result) : ""}
${
!stripComments
? `<h6>Conversation Incentives</h6>
Expand Down
Loading
Loading