diff --git a/.github/workflows/compute.yml b/.github/workflows/compute.yml index c0a83fe4..c9aff5fe 100644 --- a/.github/workflows/compute.yml +++ b/.github/workflows/compute.yml @@ -30,6 +30,30 @@ jobs: NFT_CONTRACT_ADDRESS: ${{ secrets.NFT_CONTRACT_ADDRESS }} steps: + - name: Post starting comment to issue + uses: actions/github-script@v7 + id: post-comment + with: + github-token: ${{ inputs.authToken }} + script: | + const comment_body = '\`\`\`diff\n+ Evaluating results. Please wait...'; + const obj = ${{ inputs.eventPayload }} + if (obj.issue && "${{ inputs.eventName }}" === "issues.closed") { + const response = await github.rest.issues.createComment({ + owner: obj.repository.owner.login, + repo: obj.repository.name, + issue_number: obj.issue.number, + body: comment_body, + }); + core.setOutput('comment_id', response.data.id); + } + + - name: Set environment variable + run: echo "COMMENT_ID=${{ steps.post-comment.outputs.comment_id }}" >> $GITHUB_ENV + + - run: ${{ toJSON(inputs) }} + shell: cat {0} + - name: Checkout code uses: actions/checkout@v4 @@ -38,8 +62,5 @@ jobs: with: node-version: "20.10.0" - - run: ${{ toJSON(inputs) }} - shell: cat {0} - - name: Generate Rewards uses: ./ diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 3c432a4d..61f619e9 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -17,3 +17,4 @@ jobs: - uses: googleapis/release-please-action@v4 with: release-type: simple + target-branch: main diff --git a/package.json b/package.json index e7be7d63..23df28f9 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@supabase/supabase-js": "2.42.0", "@ubiquibot/permit-generation": "1.3.1", "@ubiquity-dao/rpc-handler": "1.1.0", + "@ubiquity-dao/ubiquibot-logger": "1.2.0", "decimal.js": "10.4.3", "dotenv": "16.4.5", "ethers": "^6.13.0", diff --git a/src/helpers/github-comment-module-instance.ts b/src/helpers/github-comment-module-instance.ts new file mode 100644 index 00000000..c409aedc --- /dev/null +++ b/src/helpers/github-comment-module-instance.ts @@ -0,0 +1,5 @@ +import { GithubCommentModule } from "../parser/github-comment-module"; + +const githubCommentModule = new GithubCommentModule(); + +export default githubCommentModule; diff --git a/src/helpers/label-price-extractor.ts b/src/helpers/label-price-extractor.ts index ad264825..28c1ee65 100644 --- a/src/helpers/label-price-extractor.ts +++ b/src/helpers/label-price-extractor.ts @@ -1,4 +1,4 @@ -import { GitHubIssue } from "../github-types.ts"; +import { GitHubIssue } from "../github-types"; export function getSortedPrices(labels: GitHubIssue["labels"] | undefined) { if (!labels) return []; diff --git a/src/helpers/logger.ts b/src/helpers/logger.ts new file mode 100644 index 00000000..c37bb718 --- /dev/null +++ b/src/helpers/logger.ts @@ -0,0 +1,5 @@ +import { Logs } from "@ubiquity-dao/ubiquibot-logger"; + +const logger = new Logs("debug"); + +export default logger; diff --git a/src/parser/github-comment-module.ts b/src/parser/github-comment-module.ts index 2a51427e..1e41a8ed 100644 --- a/src/parser/github-comment-module.ts +++ b/src/parser/github-comment-module.ts @@ -6,11 +6,11 @@ import { CommentType } from "../configuration/comment-types"; import configuration from "../configuration/config-reader"; import { GithubCommentConfiguration, githubCommentConfigurationType } from "../configuration/github-comment-config"; import { getOctokitInstance } from "../get-authentication-token"; +import logger from "../helpers/logger"; +import { getERC20TokenSymbol } from "../helpers/web3"; import { IssueActivity } from "../issue-activity"; -import { parseGitHubUrl } from "../start"; import program from "./command-line"; import { GithubCommentScore, Module, Result } from "./processor"; -import { getERC20TokenSymbol } from "../helpers/web3"; interface SortedTasks { issues: { specification: GithubCommentScore | null; comments: GithubCommentScore[] }; @@ -23,6 +23,11 @@ interface SortedTasks { export class GithubCommentModule implements Module { private readonly _configuration: GithubCommentConfiguration = configuration.incentives.githubComment; private readonly _debugFilePath = "./output.html"; + /** + * COMMENT_ID can be set in the environment to reference the id of the last comment created during this workflow. + * See also compute.yml to understand how it is set. + */ + private _lastCommentId: number | null = process.env.COMMENT_ID ? Number(process.env.COMMENT_ID) : null; async transform(data: Readonly, result: Result): Promise { const bodyArray: (string | undefined)[] = []; @@ -37,17 +42,9 @@ export class GithubCommentModule implements Module { } if (this._configuration.post) { try { - const octokit = getOctokitInstance(); - const { owner, repo, issue_number } = parseGitHubUrl(program.eventPayload.issue.html_url); - - await octokit.issues.createComment({ - body, - repo, - owner, - issue_number, - }); + await this.postComment(body); } catch (e) { - console.error(`Could not post GitHub comment: ${e}`); + logger.error(`Could not post GitHub comment: ${e}`); } } return result; @@ -55,12 +52,33 @@ export class GithubCommentModule implements Module { get enabled(): boolean { if (!Value.Check(githubCommentConfigurationType, this._configuration)) { - console.warn("Invalid configuration detected for GithubContentModule, disabling."); + logger.error("Invalid configuration detected for GithubContentModule, disabling."); return false; } return true; } + async postComment(body: string, updateLastComment = true) { + const { eventPayload } = program; + if (updateLastComment && this._lastCommentId !== null) { + await getOctokitInstance().issues.updateComment({ + body, + repo: eventPayload.repository.name, + owner: eventPayload.repository.owner.login, + issue_number: eventPayload.issue.number, + comment_id: this._lastCommentId, + }); + } else { + const comment = await getOctokitInstance().issues.createComment({ + body, + repo: eventPayload.repository.name, + owner: eventPayload.repository.owner.login, + issue_number: eventPayload.issue.number, + }); + this._lastCommentId = comment.data.id; + } + } + _createContributionRows(result: Result[0], sortedTasks: SortedTasks | undefined) { const content: string[] = []; diff --git a/src/parser/processor.ts b/src/parser/processor.ts index d2344142..eb2fd464 100644 --- a/src/parser/processor.ts +++ b/src/parser/processor.ts @@ -2,11 +2,12 @@ import Decimal from "decimal.js"; import * as fs from "fs"; import { CommentType } from "../configuration/comment-types"; import configuration from "../configuration/config-reader"; +import githubCommentModuleInstance from "../helpers/github-comment-module-instance"; +import logger from "../helpers/logger"; import { IssueActivity } from "../issue-activity"; import { ContentEvaluatorModule } from "./content-evaluator-module"; import { DataPurgeModule } from "./data-purge-module"; import { FormattingEvaluatorModule } from "./formatting-evaluator-module"; -import { GithubCommentModule } from "./github-comment-module"; import { PermitGenerationModule } from "./permit-generation-module"; import { UserExtractorModule } from "./user-extractor-module"; @@ -21,7 +22,7 @@ export class Processor { .add(new FormattingEvaluatorModule()) .add(new ContentEvaluatorModule()) .add(new PermitGenerationModule()) - .add(new GithubCommentModule()); + .add(githubCommentModuleInstance); } add(transformer: Module) { @@ -31,7 +32,7 @@ export class Processor { async run(data: Readonly) { if (!this._configuration.enabled) { - console.log("Module is disabled. Skipping..."); + logger.debug("Module is disabled. Skipping..."); return; } for (const transformer of this._transformers) { @@ -67,7 +68,7 @@ export class Processor { 2 ); if (!file) { - console.log(result); + logger.debug(result); } else { fs.writeFileSync(file, result); } diff --git a/src/parser/user-extractor-module.ts b/src/parser/user-extractor-module.ts index c13c7618..282c4e9c 100644 --- a/src/parser/user-extractor-module.ts +++ b/src/parser/user-extractor-module.ts @@ -3,7 +3,7 @@ import Decimal from "decimal.js"; import configuration from "../configuration/config-reader"; import { UserExtractorConfiguration, userExtractorConfigurationType } from "../configuration/user-extractor-config"; import { GitHubIssue } from "../github-types"; -import { getSortedPrices } from "../helpers/label-price-extractor.ts"; +import { getSortedPrices } from "../helpers/label-price-extractor"; import { IssueActivity } from "../issue-activity"; import { Module, Result } from "./processor"; diff --git a/src/run.ts b/src/run.ts index d98587e1..2e6de60b 100644 --- a/src/run.ts +++ b/src/run.ts @@ -1,43 +1,32 @@ -import { getSortedPrices } from "./helpers/label-price-extractor.ts"; +import configuration from "./configuration/config-reader"; +import githubCommentModuleInstance from "./helpers/github-comment-module-instance"; +import { getSortedPrices } from "./helpers/label-price-extractor"; +import logger from "./helpers/logger"; import { IssueActivity } from "./issue-activity"; import program from "./parser/command-line"; import { Processor } from "./parser/processor"; import { parseGitHubUrl } from "./start"; -import { getOctokitInstance } from "./get-authentication-token.ts"; -import configuration from "./configuration/config-reader.ts"; export async function run() { const { eventPayload, eventName } = program; if (eventName === "issues.closed") { if (eventPayload.issue.state_reason !== "completed") { - const result = "# Issue was not closed as completed. Skipping."; - await getOctokitInstance().issues.createComment({ - body: `\`\`\`text\n${result}\n\`\`\``, - repo: eventPayload.repository.name, - owner: eventPayload.repository.owner.login, - issue_number: eventPayload.issue.number, - }); - return result; + const result = logger.info("Issue was not closed as completed. Skipping."); + await githubCommentModuleInstance.postComment(result?.logMessage.diff || ""); + return result?.logMessage.raw; } const issue = parseGitHubUrl(eventPayload.issue.html_url); const activity = new IssueActivity(issue); await activity.init(); if (configuration.incentives.requirePriceLabel && !getSortedPrices(activity.self?.labels).length) { - const result = "! No price label has been set. Skipping permit generation."; - await getOctokitInstance().issues.createComment({ - body: `\`\`\`text\n${result}\n\`\`\``, - repo: eventPayload.repository.name, - owner: eventPayload.repository.owner.login, - issue_number: eventPayload.issue.number, - }); - return result; + const result = logger.error("No price label has been set. Skipping permit generation."); + await githubCommentModuleInstance.postComment(result?.logMessage.diff || ""); + return result?.logMessage.raw; } const processor = new Processor(); await processor.run(activity); return processor.dump(); } else { - const result = `${eventName} is not supported, skipping.`; - console.warn(result); - return result; + return logger.error(`${eventName} is not supported, skipping.`)?.logMessage.raw; } } diff --git a/src/types/env.d.ts b/src/types/env.d.ts index cbe80985..989499a2 100644 --- a/src/types/env.d.ts +++ b/src/types/env.d.ts @@ -8,6 +8,7 @@ declare global { SUPABASE_URL: string; NFT_CONTRACT_ADDRESS: string; NFT_MINTER_PRIVATE_KEY: string; + COMMENT_ID: string | undefined; } } } diff --git a/tests/action.test.ts b/tests/action.test.ts index 4f772e06..ad72383f 100644 --- a/tests/action.test.ts +++ b/tests/action.test.ts @@ -1,7 +1,7 @@ /* eslint @typescript-eslint/no-var-requires: 0 */ import "../src/parser/command-line"; import { run } from "../src/run"; -import { server } from "./__mocks__/node.ts"; +import { server } from "./__mocks__/node"; beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); @@ -36,6 +36,6 @@ jest.mock("../src/parser/command-line", () => { describe("Action tests", () => { it("Should skip when the issue is closed without the completed status", async () => { const result = await run(); - expect(result).toEqual("# Issue was not closed as completed. Skipping."); + expect(result).toEqual("Issue was not closed as completed. Skipping."); }); }); diff --git a/tests/price-label.test.ts b/tests/price-label.test.ts index e99f7915..596df015 100644 --- a/tests/price-label.test.ts +++ b/tests/price-label.test.ts @@ -1,7 +1,7 @@ /* eslint @typescript-eslint/no-var-requires: 0 */ import "../src/parser/command-line"; import { run } from "../src/run"; -import { server } from "./__mocks__/node.ts"; +import { server } from "./__mocks__/node"; beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); @@ -42,6 +42,6 @@ jest.mock("../src/parser/command-line", () => { describe("Price tests", () => { it("Should skip when no price label is set", async () => { const result = await run(); - expect(result).toEqual("! No price label has been set. Skipping permit generation."); + expect(result).toEqual("No price label has been set. Skipping permit generation."); }); }); diff --git a/tests/process.issue.test.ts b/tests/process.issue.test.ts index 5cfe266e..0f6e54ca 100644 --- a/tests/process.issue.test.ts +++ b/tests/process.issue.test.ts @@ -249,7 +249,7 @@ describe("Modules tests", () => { }); it("Should do a full run", async () => { - const module = (await import("../src/index.ts")) as unknown as { default: Promise }; + const module = (await import("../src/index")) as unknown as { default: Promise }; const result = await module.default; expect(result).toBeTruthy(); }); diff --git a/tests/rewards.test.ts b/tests/rewards.test.ts index f79b3b5c..2c5a1441 100644 --- a/tests/rewards.test.ts +++ b/tests/rewards.test.ts @@ -2,19 +2,19 @@ import { drop } from "@mswjs/data"; import Decimal from "decimal.js"; import fs from "fs"; import { http, HttpResponse } from "msw"; -import { IssueActivity } from "../src/issue-activity.ts"; -import { ContentEvaluatorModule } from "../src/parser/content-evaluator-module.ts"; -import { DataPurgeModule } from "../src/parser/data-purge-module.ts"; -import { FormattingEvaluatorModule } from "../src/parser/formatting-evaluator-module.ts"; -import { GithubCommentModule } from "../src/parser/github-comment-module.ts"; -import { PermitGenerationModule } from "../src/parser/permit-generation-module.ts"; -import { Processor } from "../src/parser/processor.ts"; -import { UserExtractorModule } from "../src/parser/user-extractor-module.ts"; -import { parseGitHubUrl } from "../src/start.ts"; +import { IssueActivity } from "../src/issue-activity"; +import { ContentEvaluatorModule } from "../src/parser/content-evaluator-module"; +import { DataPurgeModule } from "../src/parser/data-purge-module"; +import { FormattingEvaluatorModule } from "../src/parser/formatting-evaluator-module"; +import { GithubCommentModule } from "../src/parser/github-comment-module"; +import { PermitGenerationModule } from "../src/parser/permit-generation-module"; +import { Processor } from "../src/parser/processor"; +import { UserExtractorModule } from "../src/parser/user-extractor-module"; +import { parseGitHubUrl } from "../src/start"; import "../src/parser/command-line"; -import { db, db as mockDb } from "./__mocks__/db.ts"; +import { db, db as mockDb } from "./__mocks__/db"; import dbSeed from "./__mocks__/db-seed.json"; -import { server } from "./__mocks__/node.ts"; +import { server } from "./__mocks__/node"; import rewardSplitResult from "./__mocks__/results/reward-split.json"; const issueUrl = "https://github.com/ubiquity/work.ubq.fi/issues/69"; diff --git a/tsconfig.json b/tsconfig.json index 7b4680a9..54f91018 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,7 +35,7 @@ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ diff --git a/yarn.lock b/yarn.lock index fa8a19f8..556bbed1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3324,6 +3324,11 @@ axios "^1.7.1" node-fetch "^3.3.2" +"@ubiquity-dao/ubiquibot-logger@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ubiquity-dao/ubiquibot-logger/-/ubiquibot-logger-1.2.0.tgz#c636edb299e22dc4f55e4cab64cc8d976d89fbf4" + integrity sha512-CgnfbiIZNc7CVrGfHKksIdbeehc6pG7qEfUMTS4vyNlPnIa0OnQeigGbn6zKvkUvIRvJ9oj0gqFATs7vGDoL4w== + "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" @@ -4493,9 +4498,9 @@ easy-table@1.2.0: wcwidth "^1.0.1" electron-to-chromium@^1.4.796: - version "1.4.818" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.818.tgz#7762c8bfd15a07c3833b7f5deed990e9e5a4c24f" - integrity sha512-eGvIk2V0dGImV9gWLq8fDfTTsCAeMDwZqEPMr+jMInxZdnp9Us8UpovYpRCf9NQ7VOFgrN2doNSgvISbsbNpxA== + version "1.4.819" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.819.tgz#b1bf73d71748a44c3b719cfe7b351d75268c9044" + integrity sha512-8RwI6gKUokbHWcN3iRij/qpvf/wCbIVY5slODi85werwqUQwpFXM+dvUBND93Qh7SB0pW3Hlq3/wZsqQ3M9Jaw== elliptic@6.5.4: version "6.5.4" @@ -4774,9 +4779,9 @@ esprima@^4.0.0, esprima@^4.0.1: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== dependencies: estraverse "^5.1.0" @@ -5233,9 +5238,9 @@ glob-parent@^6.0.2: is-glob "^4.0.3" glob@^10.2.2: - version "10.4.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.3.tgz#e0ba2253dd21b3d0acdfb5d507c59a29f513fc7a" - integrity sha512-Q38SGlYRpVtDBPSWEylRyctn7uDeTp4NQERTLiCT1FqA9JXPYWqAVmQU6qh4r/zMM5ehxTcbaO8EjhWnvEhmyg== + version "10.4.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.4.tgz#d60943feb6f8140522117e6576a923b715718380" + integrity sha512-XsOKvHsu38Xe19ZQupE6N/HENeHQBA05o3hV8labZZT2zYDg1+emxWHnc/Bm9AcCMPXfD6jt+QC7zC5JSFyumw== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" @@ -5867,9 +5872,9 @@ iterable-lookahead@^1.0.0: integrity sha512-hJnEP2Xk4+44DDwJqUQGdXal5VbyeWLaPyDl2AQc242Zr7iqz4DgpQOrEzglWVMGHMDCkguLHEKxd1+rOsmgSQ== jackspeak@^3.1.2: - version "3.4.1" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.1.tgz#145422416740568e9fc357bf60c844b3c1585f09" - integrity sha512-U23pQPDnmYybVkYjObcuYMk43VRlMLLqLI+RdZy8s8WV8WsxO9SnqSroKaluuvcNOdCAlauKszDwd+umbot5Mg== + version "3.4.2" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.2.tgz#c3d1e00071d52dba8b0dac17cd2a12d0187d2989" + integrity sha512-qH3nOSj8q/8+Eg8LUPOq3C+6HWkpUioIjDsq1+D4zY91oZvpPttw8GwtF1nReRYKXl+1AORyFqtm2f5Q1SB6/Q== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: @@ -6608,9 +6613,9 @@ log-update@^6.0.0: wrap-ansi "^9.0.0" lru-cache@^10.0.1, lru-cache@^10.0.2, lru-cache@^10.2.0: - version "10.3.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.3.1.tgz#a37050586f84ccfdb570148a253bf1632a29ef44" - integrity sha512-9/8QXrtbGeMB6LxwQd4x1tIMnsmUxMvIH/qWGsccz6bt9Uln3S+sgAaqfQNhbGA8ufzs2fHuP/yqapGgP9Hh2g== + version "10.4.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.1.tgz#da9a9cb51aec89fda9b485f5a12b2fdb8f6dbe88" + integrity sha512-8h/JsUc/2+Dm9RPJnBAmObGnUqTMmsIKThxixMLOkrebSihRhTV0wLD/8BSk6OU6Pbj8hiDTbsI3fLjBJSlhDg== lru-cache@^5.1.1: version "5.1.1"