Skip to content

Commit

Permalink
Implement basic summarize logic (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
Holi0317 authored Feb 18, 2024
1 parent 0ff061c commit 5ae061a
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 7 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240208.0",
"@octokit/webhooks-types": "^7.3.2",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-unicorn": "^51.0.1",
Expand All @@ -36,6 +37,8 @@
"dependencies": {
"@cfworker/sentry": "^2.0.0",
"@octokit/auth-app": "^6.0.3",
"octokit": "^3.1.2"
"octokit": "^3.1.2",
"parse-git-diff": "^0.0.15",
"zod": "^3.22.4"
}
}
18 changes: 16 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/diff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import parseGitDiff from "parse-git-diff";

export function genDiffSummary(diffFile: string) {
const diff = parseGitDiff(diffFile);

// TODO: Implement diff summary and return label as well
return "```json\n" + JSON.stringify(diff) + "\n```";
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ router.post("/github/webhook", async (req, env) => {
const octoApp = useOctoApp(env);

await octoApp.webhooks.verifyAndReceive({
id: unwrap(req.headers.get("x-request-id")),
id: unwrap(req.headers.get("x-github-hook-id")),
name: unwrap(req.headers.get("x-github-event")) as any,
signature: unwrap(req.headers.get("x-hub-signature")),
payload: await req.text(),
Expand Down
146 changes: 145 additions & 1 deletion src/octo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,107 @@
import { App } from "octokit";
import { App, type Octokit } from "octokit";
import type { PullRequest } from "@octokit/webhooks-types";
import { genDiffSummary } from "./diff";
import { unwrap } from "./utils";
import { z } from "zod";

const ViewerSchema = z.object({
viewer: z.object({
databaseId: z.number(),
}),
});

/**
* Find PR comment number to edit, if we have already left a comment there.
*
* If this is a new PR we haven't touched before, this will return `null`,
* suggesting we should create a new comment.
*/
async function findPRComment(octo: Octokit, pr: PullRequest) {
// IDK how to get the app's user ID with rest API. But this is trivial with
// graphQL
const viewerQuery = await octo.graphql(`query { viewer { databaseId } }`);
const viewer = ViewerSchema.parse(viewerQuery);

// Only listing the first page for pr comments. This endpoint sorts comments
// in ascending order of comment number (which means commenting order/creation
// time). Assuming we are fast and be the first few bots leaving comments in
// the PR.
const comments = await octo.request(
"GET /repos/{owner}/{repo}/issues/{issue_number}/comments",
{
owner: pr.base.repo.owner.login,
repo: pr.base.repo.name,
issue_number: pr.number,
},
);

for (const comment of comments.data) {
if (comment.user?.id === viewer.viewer.databaseId) {
return comment.id;
}
}

return null;
}

async function handle(app: App, installation_id: number, pr: PullRequest) {
console.log(
`Handling PR ${pr.base.repo.full_name}#${pr.number}. Installation ID = ${installation_id}`,
);

const octo = await app.getInstallationOctokit(installation_id);

if (pr.state === "closed") {
console.info("PR is closed. Ignoring the webhook");
return;
}

console.log("Fetching diff for the PR");
const response = await octo.request(
"GET /repos/{owner}/{repo}/pulls/{pull_number}",
{
owner: pr.base.repo.owner.login,
repo: pr.base.repo.name,
pull_number: pr.number,
mediaType: {
format: "diff",
},
},
);
// The cast is suggested here. Cast is necessary coz octokit does not support
// mediaType in typing.
// https://github.com/octokit/request.js/issues/463#issuecomment-1164800888
const diffFile = response.data as unknown as string;

const summary = genDiffSummary(diffFile);

console.log("Finding existing PR comment");
const commentID = await findPRComment(octo, pr);

if (commentID == null) {
console.info("Cannot find existing PR. Leaving new comment");
await octo.request(
"POST /repos/{owner}/{repo}/issues/{issue_number}/comments",
{
owner: pr.base.repo.owner.login,
repo: pr.base.repo.name,
issue_number: pr.number,
body: summary,
},
);
} else {
console.info(`Updating PR comment ${commentID}`);
await octo.request(
"PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}",
{
owner: pr.base.repo.owner.login,
repo: pr.base.repo.name,
comment_id: commentID,
body: summary,
},
);
}
}

export function useOctoApp(env: Env) {
const app = new App({
Expand All @@ -7,5 +110,46 @@ export function useOctoApp(env: Env) {
webhooks: { secret: env.GH_WEBHOOK_SECRET },
});

app.webhooks.on("pull_request.edited", (event) =>
handle(
app,
unwrap(
event.payload.installation?.id,
"Missing installation.id in webhook payload",
),
event.payload.pull_request,
),
);
app.webhooks.on("pull_request.synchronize", (event) =>
handle(
app,
unwrap(
event.payload.installation?.id,
"Missing installation.id in webhook payload",
),
event.payload.pull_request,
),
);
app.webhooks.on("pull_request.opened", (event) =>
handle(
app,
unwrap(
event.payload.installation?.id,
"Missing installation.id in webhook payload",
),
event.payload.pull_request,
),
);
app.webhooks.on("pull_request.reopened", (event) =>
handle(
app,
unwrap(
event.payload.installation?.id,
"Missing installation.id in webhook payload",
),
event.payload.pull_request,
),
);

return app;
}
7 changes: 5 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export function unwrap<T>(value: T): NonNullable<T> {
export function unwrap<T>(
value: T,
message: string = "Tried to unwrap a nullish value",
): NonNullable<T> {
if (value == null) {
throw new Error("Tried to unwrap a nullish value");
throw new Error(message);
}

return value;
Expand Down

0 comments on commit 5ae061a

Please sign in to comment.