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: github comment generation and posting #12

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e7b555d
chore: comment html generation (WIP)
gentlementlegen Apr 12, 2024
7e71e30
chore: added missing types for comments
gentlementlegen Apr 15, 2024
8dea21d
chore: debug mode and review comments for github comment module
gentlementlegen Apr 15, 2024
df2c34f
chore: comment incentive rows (WIP)
gentlementlegen Apr 15, 2024
c9897e2
chore: incentive rows generation
gentlementlegen Apr 15, 2024
29ce3c3
chore: comment posting to github
gentlementlegen Apr 15, 2024
d29427a
chore: fix pre div formatting
gentlementlegen Apr 15, 2024
ad141fd
Merge branch 'feat/permit-generation' into feat/github-comment
gentlementlegen Apr 16, 2024
96f4104
chore: update package version
gentlementlegen Apr 16, 2024
43b1912
chore: updated .gitignore
gentlementlegen Apr 16, 2024
1f9b050
chore: updated permit generation package
gentlementlegen Apr 17, 2024
3bab31a
chore: env check for permit generation module
gentlementlegen Apr 17, 2024
03fac6c
chore: added ignore words to cspell
gentlementlegen Apr 17, 2024
efe17cb
Merge branch 'refs/heads/development' into feat/github-comment
gentlementlegen Apr 17, 2024
119c633
chore: simplified code github comment module
gentlementlegen Apr 17, 2024
24c748d
chore: always return content on empty string
gentlementlegen Apr 18, 2024
5eaa303
chore: changed rpc for DAI
gentlementlegen Apr 18, 2024
6567feb
chore: testing suite for modules (WIP)
gentlementlegen Apr 18, 2024
559c13c
chore: fix knip and change COMMENT to COMMENTED, added jsdocs on enum
gentlementlegen Apr 18, 2024
abbcf33
chore: changed content to be an array of strings instead of a single …
gentlementlegen Apr 18, 2024
02bf994
chore: tests for data purge module
gentlementlegen Apr 18, 2024
5bf432f
chore: removed invalid type check
gentlementlegen Apr 19, 2024
3bab2b8
chore: set comment type to contributor if author_association is none
gentlementlegen Apr 20, 2024
e61436b
chore: changed body type to an array of string
gentlementlegen Apr 21, 2024
73c650f
chore: changed RPC for mainnet
gentlementlegen Apr 22, 2024
a22cec2
chore: removed spurious Promise resolves
gentlementlegen Apr 22, 2024
a12318e
chore: un-nested GitHub comment module functions to reduce reading co…
gentlementlegen Apr 22, 2024
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
2 changes: 1 addition & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log"],
"useGitignore": true,
"language": "en",
"words": ["dataurl", "devpool", "outdir", "servedir", "ubiquibot", "tiktoken", "typebox"],
"words": ["dataurl", "devpool", "outdir", "servedir", "ubiquibot", "tiktoken", "typebox", "supabase", "wxdai", "noopener"],
"dictionaries": ["typescript", "node", "software-terms"],
"import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"],
"ignoreRegExpList": ["[0-9a-fA-F]{6}"]
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/cspell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: Spell Check

on:
push:
pull_request:

jobs:
spellcheck:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ node_modules
static/dist
.env
junit.xml
coverage
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@octokit/rest": "20.1.0",
"@sinclair/typebox": "0.32.20",
"@supabase/supabase-js": "2.42.0",
"@ubiquibot/permit-generation": "1.0.3",
"@ubiquibot/permit-generation": "1.2.1",
"commander": "12.0.0",
"decimal.js": "10.4.3",
"dotenv": "16.4.5",
Expand All @@ -56,7 +56,7 @@
"@types/node": "20.11.28",
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
"babel-jest": "^29.7.0",
"babel-jest": "29.7.0",
"cspell": "8.3.2",
"eslint": "8.56.0",
"eslint-plugin-sonarjs": "0.23.0",
Expand Down
26 changes: 18 additions & 8 deletions rewards-configuration.default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,39 @@ formattingEvaluator:
td: 1
hr: 0
multipliers:
- type: [ISSUE, ISSUER]:
- type: [ISSUE, ISSUER, TASK]:
formattingMultiplier: 1
wordValue: 0.1
- type: [ISSUE, ISSUER, COMMENT]:
formattingMultiplier: 1
wordValue: 0.2
- type: [ISSUE, ASSIGNEE]:
- type: [ISSUE, ASSIGNEE, COMMENT]:
gentlementlegen marked this conversation as resolved.
Show resolved Hide resolved
formattingMultiplier: 0
wordValue: 0
- type: [ISSUE, COLLABORATOR]:
- type: [ISSUE, COLLABORATOR, COMMENT]:
formattingMultiplier: 1
wordValue: 0.1
- type: [ISSUE, CONTRIBUTOR]:
- type: [ISSUE, CONTRIBUTOR, COMMENT]:
formattingMultiplier: 0.25
wordValue: 0.1
- type: [REVIEW, ISSUER]:
- type: [REVIEW, ISSUER, SPECIFICATION]:
formattingMultiplier: 0
wordValue: 0
- type: [REVIEW, ISSUER, COMMENT]:
formattingMultiplier: 2
wordValue: 0.2
- type: [REVIEW, ASSIGNEE]:
- type: [REVIEW, ASSIGNEE, COMMENT]:
formattingMultiplier: 1
wordValue: 0.1
- type: [REVIEW, COLLABORATOR]:
- type: [REVIEW, COLLABORATOR, COMMENT]:
formattingMultiplier: 1
wordValue: 0.1
- type: [REVIEW, CONTRIBUTOR]:
- type: [REVIEW, CONTRIBUTOR, COMMENT]:
formattingMultiplier: 0.25
wordValue: 0.1
permitGeneration:
enabled: true
githubComment:
enabled: true
post: true
debug: true
11 changes: 11 additions & 0 deletions src/configuration/github-comment-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Static, Type } from "@sinclair/typebox";

const githubCommentConfigurationType = Type.Object({
enabled: Type.Boolean({ default: true }),
post: Type.Boolean({ default: true }),
debug: Type.Boolean({ default: false }),
});

export type GithubCommentConfiguration = Static<typeof githubCommentConfigurationType>;

export default githubCommentConfigurationType;
10 changes: 9 additions & 1 deletion src/issue-activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export enum CommentType {
ISSUER = 0b1000,
COLLABORATOR = 0b10000,
CONTRIBUTOR = 0b100000,
COMMENT = 0b1000000,
TASK = 0b10000000,
whilefoo marked this conversation as resolved.
Show resolved Hide resolved
SPECIFICATION = 0b100000000,
}

export class IssueActivity {
Expand Down Expand Up @@ -73,7 +76,12 @@ export class IssueActivity {
self: GitHubPullRequest | GitHubIssue | null
) {
let ret = 0;
ret |= "pull_request_review_id" in comment ? CommentType.REVIEW : CommentType.ISSUE;
ret |= "pull_request_review_id" in comment || "draft" in comment ? CommentType.REVIEW : CommentType.ISSUE;
gentlementlegen marked this conversation as resolved.
Show resolved Hide resolved
if (comment.id === self?.id) {
ret |= ret & CommentType.ISSUE ? CommentType.TASK : CommentType.SPECIFICATION;
} else {
ret |= CommentType.COMMENT;
}
if (comment.user?.id === self?.user?.id) {
ret |= CommentType.ISSUER;
} else if (comment.user?.id === self?.assignee?.id) {
Expand Down
212 changes: 212 additions & 0 deletions src/parser/github-comment-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import { Value } from "@sinclair/typebox/value";
import Decimal from "decimal.js";
import * as fs from "fs";
import { stringify } from "yaml";
import configuration from "../configuration/config-reader";
import githubCommentConfig, { GithubCommentConfiguration } from "../configuration/github-comment-config";
import { getOctokitInstance } from "../get-authentication-token";
import { CommentType, IssueActivity } from "../issue-activity";
import { parseGitHubUrl } from "../start";
import { getPayoutConfigByNetworkId } from "../types/payout";
import program from "./command-line";
import { GithubCommentScore, Module, Result } from "./processor";

/**
* Posts a GitHub comment according to the given results.
*/
export class GithubCommentModule implements Module {
private readonly _configuration: GithubCommentConfiguration = configuration.githubComment;
private readonly _debugFilePath = "./output.html";

async transform(data: Readonly<IssueActivity>, result: Result): Promise<Result> {
let body = "";
gentlementlegen marked this conversation as resolved.
Show resolved Hide resolved

for (const [key, value] of Object.entries(result)) {
result[key].evaluationCommentHtml = this._generateHtml(key, value);
body += result[key].evaluationCommentHtml;
}
if (this._configuration.debug) {
fs.writeFileSync(this._debugFilePath, body);
}
if (this._configuration.post) {
try {
const octokit = getOctokitInstance();
const { owner, repo, issue_number } = parseGitHubUrl(program.opts().issue);

await octokit.issues.createComment({
body,
repo,
owner,
issue_number,
});
} catch (e) {
console.error(`Could not post GitHub comment: ${e}`);
}
}
return Promise.resolve(result);
gentlementlegen marked this conversation as resolved.
Show resolved Hide resolved
}

get enabled(): boolean {
if (!Value.Check(githubCommentConfig, this._configuration)) {
console.warn("Invalid configuration detected for GithubContentModule, disabling.");
return false;
}
return true;
}

_generateHtml(username: string, result: Result[0]) {
const sorted = result.comments?.reduce<{
issues: { task: GithubCommentScore | null; comments: GithubCommentScore[] };
reviews: GithubCommentScore[];
}>(
(acc, curr) => {
if (curr.type & CommentType.ISSUE) {
if (curr.type & CommentType.TASK) {
acc.issues.task = curr;
} else {
acc.issues.comments.push(curr);
}
} else if (curr.type & CommentType.REVIEW) {
acc.reviews.push(curr);
}
return acc;
},
{ issues: { task: null, comments: [] }, reviews: [] }
);

function createContributionRows() {
let content = "";

if (!sorted) {
return "";
}

function generateContributionRow(
gentlementlegen marked this conversation as resolved.
Show resolved Hide resolved
view: string,
contribution: string,
count: number,
reward: number | Decimal | undefined
) {
return `
<tr>
<td>${view}</td>
<td>${contribution}</td>
<td>${count}</td>
<td>${reward || "-"}</td>
</tr>`;
}

if (result.task?.reward) {
content += generateContributionRow("Issue", "Task", 1, result.task.reward);
}
if (sorted.issues.task) {
content += generateContributionRow("Issue", "Specification", 1, sorted.issues.task.score?.reward);
}
if (sorted.issues.comments.length) {
content += generateContributionRow(
"Issue",
"Comment",
sorted.issues.comments.length,
sorted.issues.comments.reduce((acc, curr) => acc.add(curr.score?.reward ?? 0), new Decimal(0))
);
}
if (sorted.reviews.length) {
content += generateContributionRow(
"Review",
"Comment",
sorted.reviews.length,
sorted.reviews.reduce((acc, curr) => acc.add(curr.score?.reward ?? 0), new Decimal(0))
);
}
return content;
}

function createIncentiveRows() {
let content = "";

if (!sorted) {
return "";
}

function buildIncentiveRow(commentScore: GithubCommentScore) {
// Properly escape carriage returns for HTML rendering
const formatting = stringify(commentScore.score?.formatting?.content).replace(/[\n\r]/g, "&#13;");
return `
<tr>
<td>
<h6>
<a href="${commentScore.url}" target="_blank" rel="noopener">${commentScore.content.replace(/(.{64})..+/, "$1…")}</a>
</h6>
</td>
<td>
<details>
<summary>
${Object.values(commentScore.score?.formatting?.content || {}).reduce((acc, curr) => {
return acc.add(curr.score * curr.count);
}, new Decimal(0))}
</summary>
<pre>${formatting}</pre>
</details>
</td>
<td>${commentScore.score?.relevance || "-"}</td>
<td>${commentScore.score?.reward || "-"}</td>
</tr>`;
}

for (const issueComment of sorted.issues.comments) {
content += buildIncentiveRow(issueComment);
}
for (const reviewComment of sorted.reviews) {
content += buildIncentiveRow(reviewComment);
}
return content;
}

return `
<details>
<summary>
<b>
<h3>
<a href="${result.permitUrl}" target="_blank" rel="noopener">
whilefoo marked this conversation as resolved.
Show resolved Hide resolved
[ ${result.total} ${getPayoutConfigByNetworkId(program.opts().evmNetworkId).symbol} ]
</a>
</h3>
<h6>
@${username}
</h6>
</b>
</summary>
<h6>Contributions Overview</h6>
<table>
<thead>
<tr>
<th>View</th>
<th>Contribution</th>
<th>Count</th>
<th>Reward</th>
</tr>
</thead>
<tbody>
${createContributionRows()}
</tbody>
</table>
<h6>Conversation Incentives</h6>
<table>
<thead>
<tr>
<th>Comment</th>
<th>Formatting</th>
<th>Relevance</th>
<th>Reward</th>
</tr>
</thead>
<tbody>
${createIncentiveRows()}
</tbody>
</table>
</details>
`
.replace(/\s+/g, " ")
gentlementlegen marked this conversation as resolved.
Show resolved Hide resolved
.trim();
}
}
15 changes: 13 additions & 2 deletions src/parser/permit-generation-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import {
generatePayoutPermit,
SupportedEvents,
TokenType,
} from "@ubiquibot/permit-generation";
} from "@ubiquibot/permit-generation/core";
import configuration from "../configuration/config-reader";
import permitGenerationConfigurationType, {
PermitGenerationConfiguration,
} from "../configuration/permit-generation-configuration";
import { getOctokitInstance } from "../get-authentication-token";
import { IssueActivity } from "../issue-activity";
import envConfigSchema from "../types/env-type";
import program from "./command-line";
import { Module, Result } from "./processor";

interface Payload {
Expand All @@ -30,12 +32,21 @@ export class PermitGenerationModule implements Module {
readonly _supabase = createClient<Database>(process.env.SUPABASE_URL, process.env.SUPABASE_KEY);

async transform(data: Readonly<IssueActivity>, result: Result): Promise<Result> {
const payload: Context["payload"] & Payload = context.payload.inputs;
const payload: Context["payload"] & Payload = {
...context.payload.inputs,
issueUrl: program.opts().issue,
evmPrivateEncrypted: program.opts().evmPrivateEncrypted,
evmNetworkId: program.opts().evmNetworkId,
};
const issueId = Number(payload.issueUrl.match(/[0-9]+$/)?.[1]);
payload.issue = {
id: issueId,
};
const env = process.env;
if (!Value.Check(envConfigSchema, env)) {
console.warn("[PermitGenerationModule] Invalid env detected, skipping.");
return Promise.resolve(result);
}
const eventName = context.eventName as SupportedEvents;
const octokit = getOctokitInstance();
const logger = {
Expand Down
Loading
Loading