diff --git a/.env.action.example b/.env.action.example index 3b7cc0a..04a86dd 100644 --- a/.env.action.example +++ b/.env.action.example @@ -14,6 +14,10 @@ LANGUAGE= # The path(s) to ignore, split path with `,`. Support glob patterns. # e.g. package.json PATH_TO_EXCLUDE= +# The reply to ignore, the bot will not add comment to the pull request if given string is fully matched with the comment. +# WARN: You may not get any review comment if all of the review(s) are ignored. +# e.g. "No issue found." +REPLY_TO_IGNORE= # The max patch for a single file MAX_PATCH_PER_FILE= # The max file amount for a single pull request diff --git a/action/core/code-review.d.ts b/action/core/code-review.d.ts index f007702..bde36ce 100644 --- a/action/core/code-review.d.ts +++ b/action/core/code-review.d.ts @@ -2,6 +2,7 @@ import { Context } from "probot"; import { OpenAI } from './openai-helper'; export declare class CodeReview { PATH_TO_EXCLUDE: string; + REPLY_TO_IGNORE: string | null; MAX_FILE_PER_PR: number; MAX_PATCH_PER_FILE: number; LANGUAGE: string; diff --git a/action/index.js b/action/index.js index 2c2ac04..3748c04 100644 --- a/action/index.js +++ b/action/index.js @@ -144195,6 +144195,7 @@ var CodeReviewType; var CodeReview = /** @class */ (function () { function CodeReview() { this.PATH_TO_EXCLUDE = process.env.PATH_TO_EXCLUDE || ''; + this.REPLY_TO_IGNORE = process.env.REPLY_TO_IGNORE || null; this.MAX_FILE_PER_PR = Number(process.env.MAX_FILE_PER_PR) || 20; this.MAX_PATCH_PER_FILE = Number(process.env.MAX_PATCH_PER_FILE) || Number.MAX_VALUE; this.LANGUAGE = process.env.LANGUAGE || 'English'; @@ -144400,21 +144401,20 @@ var CodeReview = /** @class */ (function () { value = response['value']; if (!(value.type == CodeReviewType.CodeReview)) return [3 /*break*/, 3]; message = value.message, file = value.file, position = value.position; + if (message === this.REPLY_TO_IGNORE) { + return [2 /*return*/, Promise.resolve('This reply is ignored')]; + } return [4 /*yield*/, pullRequest.reviewComment(message, file, position)]; - case 2: - _a.sent(); - return [3 /*break*/, 5]; + case 2: return [2 /*return*/, _a.sent()]; case 3: if (!(value.type == CodeReviewType.Message)) return [3 /*break*/, 5]; return [4 /*yield*/, pullRequest.comment(value.message)]; - case 4: - _a.sent(); - _a.label = 5; + case 4: return [2 /*return*/, _a.sent()]; case 5: return [3 /*break*/, 7]; case 6: if (response['status'] == 'rejected') { - console.log(response['reason']); allSettled = false; + return [2 /*return*/, Promise.reject(response['reason'])]; } _a.label = 7; case 7: diff --git a/src/core/code-review.ts b/src/core/code-review.ts index cf31e29..88dde02 100644 --- a/src/core/code-review.ts +++ b/src/core/code-review.ts @@ -18,6 +18,7 @@ type CodeReviewResponse = { export class CodeReview { PATH_TO_EXCLUDE: string = process.env.PATH_TO_EXCLUDE || ''; + REPLY_TO_IGNORE: string | null = process.env.REPLY_TO_IGNORE || null; MAX_FILE_PER_PR: number = Number(process.env.MAX_FILE_PER_PR) || 20; MAX_PATCH_PER_FILE: number = Number(process.env.MAX_PATCH_PER_FILE) || Number.MAX_VALUE; LANGUAGE: string = process.env.LANGUAGE || 'English'; @@ -227,13 +228,17 @@ export class CodeReview { if (value.type == CodeReviewType.CodeReview) { const { message, file, position } = value; - await pullRequest.reviewComment(message, file, position); + if (message === this.REPLY_TO_IGNORE) { + return Promise.resolve('This reply is ignored'); + } + + return await pullRequest.reviewComment(message, file, position); } else if (value.type == CodeReviewType.Message) { - await pullRequest.comment(value.message); + return await pullRequest.comment(value.message); } } else if (response['status'] == 'rejected') { - console.log(response['reason']); allSettled = false; + return Promise.reject(response['reason']); } } }) diff --git a/test/index.test.ts b/test/index.test.ts index 5c77302..dd1c075 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -31,6 +31,8 @@ describe("My Probot app", () => { }); // Load our app into probot probot.load(myProbotApp); + + process.env.REPLY_TO_IGNORE = 'No issue found.' }); test("creates a comment when an pull request is opened", async () => { @@ -140,6 +142,91 @@ describe("My Probot app", () => { }); + test("ignore reply",async () => { + const openAiMock = nock("https://api.openai.com/v1/") + .get("/models") + .reply(200, { + "data": [ + { + "id": "model-id-0", + "object": "model", + "owned_by": "organization-owner", + "permission": [] + }, + ], + "object": "list" + }) + + // as for code review + .post("/chat/completions", (body: any) => { + console.log(body); + return true; + }) + .reply(200, { + "id": "chatcmpl-123", + "object": "chat.completion", + "created": 1677652288, + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": "No issue found.", + }, + "finish_reason": "stop" + }], + "usage": { + "prompt_tokens": 9, + "completion_tokens": 12, + "total_tokens": 21 + } + }) + + const ghMock = nock("https://api.github.com") + // Test that we correctly return a test token + .post("/app/installations/2/access_tokens") + .reply(200, { + token: "test", + permissions: { + issues: "write", + }, + }) + + // Test that a comment is posted + .post("/repos/cr/testing-things/issues/1/comments", (body: any) => { + expect(body).toMatchObject({ + body: `🤖 Thanks for your pull request! AI reviewers will be checking it soon. Please make sure it follows our contribution guidelines and has passed our automated tests. 🤖💻` + }); + return true; + }) + .reply(200) + + // Compare 2 commits + .get("/repos/cr/testing-things/compare/main...development") + .reply(200, { + files: [ + { + "sha": "bbcd538c8e72b8c175046e27cc8f907076331401", + "filename": "file1.txt", + "status": "added", + "additions": 103, + "deletions": 21, + "changes": 124, + "blob_url": "https://github.com/octocat/Hello-World/blob/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt", + "raw_url": "https://github.com/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt", + "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/file1.txt?ref=6dcb09b5b57875f334f61aebed695e2e4193db5e", + "patch": "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test" + } + ] + }) + + // Receive a webhook event + await probot.receive({ name: "pull_request.opened", payload }); + + expect(ghMock.pendingMocks()).toStrictEqual([]); + expect(openAiMock.pendingMocks()).toStrictEqual([]); + + }) + afterEach(() => { nock.cleanAll(); nock.enableNetConnect();