diff --git a/.tool-versions b/.tool-versions index 4b62baf..f62fc07 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 14.15.4 +nodejs 14.17.0 diff --git a/__tests__/comment.test.ts b/__tests__/comment.test.ts index 3d7146c..46993c2 100644 --- a/__tests__/comment.test.ts +++ b/__tests__/comment.test.ts @@ -25,41 +25,63 @@ it("findPreviousComment", async () => { login: "some-user" } const comment = { - user: authenticatedUser, + id: "1", + author: authenticatedUser, + isMinimized: false, body: "previous message\n" } const commentWithCustomHeader = { - user: authenticatedUser, + id: "2", + author: authenticatedUser, + isMinimized: false, body: "previous message\n" } const headerFirstComment = { - user: authenticatedUser, + id: "3", + author: authenticatedUser, + isMinimized: false, body: "\nheader first message" } const otherUserComment = { - user: otherUser, + id: "4", + author: otherUser, + isMinimized: false, body: "Fake previous message\n" } const otherComments = [ { - user: otherUser, + id: "5", + author: otherUser, + isMinimized: false, body: "lgtm" }, { - user: authenticatedUser, + id: "6", + author: authenticatedUser, + isMinimized: false, body: "previous message\n" } ] const octokit = getOctokit("github-token") - jest.spyOn(octokit, "graphql").mockResolvedValue({viewer: authenticatedUser}) - jest.spyOn(octokit.rest.issues, "listComments").mockResolvedValue({ - data: [ - commentWithCustomHeader, - otherUserComment, - comment, - headerFirstComment, - ...otherComments - ] + jest.spyOn(octokit, "graphql").mockResolvedValue({ + viewer: authenticatedUser, + repository: { + pullRequest: { + comments: { + nodes: [ + commentWithCustomHeader, + otherUserComment, + comment, + headerFirstComment, + ...otherComments + ], + pageInfo: { + hasNextPage: false, + endCursor: "6" + } + } + } + } } as any) expect(await findPreviousComment(octokit, repo, 123, "")).toBe(comment) @@ -69,10 +91,11 @@ it("findPreviousComment", async () => { expect(await findPreviousComment(octokit, repo, 123, "LegacyComment")).toBe( headerFirstComment ) - expect(octokit.rest.issues.listComments).toBeCalledWith({ + expect(octokit.graphql).toBeCalledWith(expect.any(String), { + after: null, + number: 123, owner: "marocchino", - repo: "sticky-pull-request-comment", - issue_number: 123 + repo: "sticky-pull-request-comment" }) }) @@ -80,51 +103,48 @@ describe("updateComment", () => { const octokit = getOctokit("github-token") beforeEach(() => { - jest - .spyOn(octokit.rest.issues, "updateComment") - .mockResolvedValue("") + jest.spyOn(octokit, "graphql").mockResolvedValue("") }) it("with comment body", async () => { expect( - await updateComment(octokit, repo, 456, "hello there", "") + await updateComment(octokit, "456", "hello there", "") ).toBeUndefined() - expect(octokit.rest.issues.updateComment).toBeCalledWith({ - comment_id: 456, - owner: "marocchino", - repo: "sticky-pull-request-comment", - body: "hello there\n" + expect(octokit.graphql).toBeCalledWith(expect.any(String), { + input: { + id: "456", + body: "hello there\n" + } }) expect( - await updateComment(octokit, repo, 456, "hello there", "TypeA") + await updateComment(octokit, "456", "hello there", "TypeA") ).toBeUndefined() - expect(octokit.rest.issues.updateComment).toBeCalledWith({ - comment_id: 456, - owner: "marocchino", - repo: "sticky-pull-request-comment", - body: "hello there\n" + expect(octokit.graphql).toBeCalledWith(expect.any(String), { + input: { + id: "456", + body: "hello there\n" + } }) expect( await updateComment( octokit, - repo, - 456, + "456", "hello there", "TypeA", "hello there\n" ) ).toBeUndefined() - expect(octokit.rest.issues.updateComment).toBeCalledWith({ - comment_id: 456, - owner: "marocchino", - repo: "sticky-pull-request-comment", - body: "hello there\n\nhello there" + expect(octokit.graphql).toBeCalledWith(expect.any(String), { + input: { + id: "456", + body: "hello there\n\nhello there" + } }) }) it("without comment body and previous body", async () => { - expect(await updateComment(octokit, repo, 456, "", "")).toBeUndefined() - expect(octokit.rest.issues.updateComment).not.toBeCalled() + expect(await updateComment(octokit, "456", "", "")).toBeUndefined() + expect(octokit.graphql).not.toBeCalled() expect(core.warning).toBeCalledWith("Comment body cannot be blank") }) }) @@ -168,14 +188,10 @@ describe("createComment", () => { it("deleteComment", async () => { const octokit = getOctokit("github-token") - jest - .spyOn(octokit.rest.issues, "deleteComment") - .mockReturnValue(undefined as any) - expect(await deleteComment(octokit, repo, 456)).toBeUndefined() - expect(octokit.rest.issues.deleteComment).toBeCalledWith({ - comment_id: 456, - owner: "marocchino", - repo: "sticky-pull-request-comment" + jest.spyOn(octokit, "graphql").mockReturnValue(undefined as any) + expect(await deleteComment(octokit, "456")).toBeUndefined() + expect(octokit.graphql).toBeCalledWith(expect.any(String), { + id: "456" }) }) diff --git a/lib/comment.js b/lib/comment.js index e893b42..2ac4850 100644 --- a/lib/comment.js +++ b/lib/comment.js @@ -33,22 +33,76 @@ const core = __importStar(require("@actions/core")); function headerComment(header) { return ``; } -function findPreviousComment(octokit, repo, issue_number, header) { +function findPreviousComment(octokit, repo, number, header) { + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; return __awaiter(this, void 0, void 0, function* () { - const { viewer } = yield octokit.graphql("query { viewer { login } }"); - const { data: comments } = yield octokit.rest.issues.listComments(Object.assign(Object.assign({}, repo), { issue_number })); + let after = null; + let hasNextPage = true; const h = headerComment(header); - return comments.find(comment => { var _a, _b; return ((_a = comment.user) === null || _a === void 0 ? void 0 : _a.login) === viewer.login && ((_b = comment.body) === null || _b === void 0 ? void 0 : _b.includes(h)); }); + while (hasNextPage) { + const data = yield octokit.graphql(` + query($repo: String! $owner: String! $number: Int! $after: String) { + viewer { login } + repository(name: $repo owner: $owner) { + pullRequest(number: $number) { + comments(first: 100 after: $after) { + nodes { + id + author { + login + } + isMinimized + body + } + pageInfo { + endCursor + hasNextPage + } + } + } + } + } + `, Object.assign(Object.assign({}, repo), { after, number })); + const viewer = data.viewer; + const repository = data.repository; + const target = (_c = (_b = (_a = repository.pullRequest) === null || _a === void 0 ? void 0 : _a.comments) === null || _b === void 0 ? void 0 : _b.nodes) === null || _c === void 0 ? void 0 : _c.find((node) => { + var _a, _b; + return ((_a = node === null || node === void 0 ? void 0 : node.author) === null || _a === void 0 ? void 0 : _a.login) === viewer.login && + !(node === null || node === void 0 ? void 0 : node.isMinimized) && + ((_b = node === null || node === void 0 ? void 0 : node.body) === null || _b === void 0 ? void 0 : _b.includes(h)); + }); + if (target) { + return target; + } + after = (_f = (_e = (_d = repository.pullRequest) === null || _d === void 0 ? void 0 : _d.comments) === null || _e === void 0 ? void 0 : _e.pageInfo) === null || _f === void 0 ? void 0 : _f.endCursor; + hasNextPage = + (_k = (_j = (_h = (_g = repository.pullRequest) === null || _g === void 0 ? void 0 : _g.comments) === null || _h === void 0 ? void 0 : _h.pageInfo) === null || _j === void 0 ? void 0 : _j.hasNextPage) !== null && _k !== void 0 ? _k : false; + } + return undefined; }); } exports.findPreviousComment = findPreviousComment; -function updateComment(octokit, repo, comment_id, body, header, previousBody) { +function updateComment(octokit, id, body, header, previousBody) { return __awaiter(this, void 0, void 0, function* () { if (!body && !previousBody) return core.warning("Comment body cannot be blank"); - yield octokit.rest.issues.updateComment(Object.assign(Object.assign({}, repo), { comment_id, body: previousBody - ? `${previousBody}\n${body}` - : `${body}\n${headerComment(header)}` })); + yield octokit.graphql(` + mutation($input: UpdateIssueCommentInput!) { + updateIssueComment(input: $input) { + issueComment { + id + body + } + } + } + `, { + input: { + id, + body: previousBody + ? `${previousBody}\n${body}` + : `${body}\n${headerComment(header)}` + } + }); }); } exports.updateComment = updateComment; @@ -62,9 +116,15 @@ function createComment(octokit, repo, issue_number, body, header, previousBody) }); } exports.createComment = createComment; -function deleteComment(octokit, repo, comment_id) { +function deleteComment(octokit, id) { return __awaiter(this, void 0, void 0, function* () { - yield octokit.rest.issues.deleteComment(Object.assign(Object.assign({}, repo), { comment_id })); + yield octokit.graphql(` + mutation($id: ID!) { + deleteIssueComment(input: { id: $id }) { + clientMutationId + } + } + `, { id }); }); } exports.deleteComment = deleteComment; diff --git a/lib/main.js b/lib/main.js index af9672f..6d3634b 100644 --- a/lib/main.js +++ b/lib/main.js @@ -52,16 +52,16 @@ function run() { return; } if (config_1.deleteOldComment) { - yield (0, comment_1.deleteComment)(octokit, config_1.repo, previous.id); + yield (0, comment_1.deleteComment)(octokit, previous.id); return; } const previousBody = (0, comment_1.getBodyOf)(previous, config_1.append, config_1.hideDetails); if (config_1.recreate) { - yield (0, comment_1.deleteComment)(octokit, config_1.repo, previous.id); + yield (0, comment_1.deleteComment)(octokit, previous.id); yield (0, comment_1.createComment)(octokit, config_1.repo, config_1.pullRequestNumber, config_1.body, config_1.header, previousBody); return; } - yield (0, comment_1.updateComment)(octokit, config_1.repo, previous.id, config_1.body, config_1.header, previousBody); + yield (0, comment_1.updateComment)(octokit, previous.id, config_1.body, config_1.header, previousBody); } catch (error) { if (error instanceof Error) { diff --git a/package.json b/package.json index 355e909..9f072c5 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "license": "MIT", "dependencies": { "@actions/core": "^1.6.0", - "@actions/github": "^5.0.0" + "@actions/github": "^5.0.0", + "@octokit/graphql-schema": "^10.73.0" }, "devDependencies": { "@types/jest": "^27.0.2", diff --git a/src/comment.ts b/src/comment.ts index 862da94..dec78b2 100644 --- a/src/comment.ts +++ b/src/comment.ts @@ -1,4 +1,5 @@ import * as core from "@actions/core" +import {IssueComment, Repository, User} from "@octokit/graphql-schema" import {GitHub} from "@actions/github/lib/utils" function headerComment(header: String): string { @@ -11,26 +12,60 @@ export async function findPreviousComment( owner: string repo: string }, - issue_number: number, + number: number, header: string -): Promise<{body?: string; id: number} | undefined> { - const {viewer} = await octokit.graphql("query { viewer { login } }") - const {data: comments} = await octokit.rest.issues.listComments({ - ...repo, - issue_number - }) +): Promise<{body: string; id: string} | undefined> { + let after = null + let hasNextPage = true const h = headerComment(header) - return comments.find( - comment => comment.user?.login === viewer.login && comment.body?.includes(h) - ) + while (hasNextPage) { + const data = await octokit.graphql<{repository: Repository; viewer: User}>( + ` + query($repo: String! $owner: String! $number: Int! $after: String) { + viewer { login } + repository(name: $repo owner: $owner) { + pullRequest(number: $number) { + comments(first: 100 after: $after) { + nodes { + id + author { + login + } + isMinimized + body + } + pageInfo { + endCursor + hasNextPage + } + } + } + } + } + `, + {...repo, after, number} + ) + const viewer = data.viewer as User + const repository = data.repository as Repository + const target = repository.pullRequest?.comments?.nodes?.find( + (node: IssueComment | null | undefined) => + node?.author?.login === viewer.login && + !node?.isMinimized && + node?.body?.includes(h) + ) + if (target) { + return target + } + after = repository.pullRequest?.comments?.pageInfo?.endCursor + hasNextPage = + repository.pullRequest?.comments?.pageInfo?.hasNextPage ?? false + } + return undefined } + export async function updateComment( octokit: InstanceType, - repo: { - owner: string - repo: string - }, - comment_id: number, + id: string, body: string, header: string, previousBody?: string @@ -38,13 +73,26 @@ export async function updateComment( if (!body && !previousBody) return core.warning("Comment body cannot be blank") - await octokit.rest.issues.updateComment({ - ...repo, - comment_id, - body: previousBody - ? `${previousBody}\n${body}` - : `${body}\n${headerComment(header)}` - }) + await octokit.graphql( + ` + mutation($input: UpdateIssueCommentInput!) { + updateIssueComment(input: $input) { + issueComment { + id + body + } + } + } + `, + { + input: { + id, + body: previousBody + ? `${previousBody}\n${body}` + : `${body}\n${headerComment(header)}` + } + } + ) } export async function createComment( octokit: InstanceType, @@ -70,16 +118,18 @@ export async function createComment( } export async function deleteComment( octokit: InstanceType, - repo: { - owner: string - repo: string - }, - comment_id: number + id: string ): Promise { - await octokit.rest.issues.deleteComment({ - ...repo, - comment_id - }) + await octokit.graphql( + ` + mutation($id: ID!) { + deleteIssueComment(input: { id: $id }) { + clientMutationId + } + } + `, + {id} + ) } export function getBodyOf( diff --git a/src/main.ts b/src/main.ts index ac8fa3f..ebc642b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -48,13 +48,13 @@ async function run(): Promise { } if (deleteOldComment) { - await deleteComment(octokit, repo, previous.id) + await deleteComment(octokit, previous.id) return } const previousBody = getBodyOf(previous, append, hideDetails) if (recreate) { - await deleteComment(octokit, repo, previous.id) + await deleteComment(octokit, previous.id) await createComment( octokit, repo, @@ -66,7 +66,7 @@ async function run(): Promise { return } - await updateComment(octokit, repo, previous.id, body, header, previousBody) + await updateComment(octokit, previous.id, body, header, previousBody) } catch (error) { if (error instanceof Error) { core.setFailed(error.message) diff --git a/yarn.lock b/yarn.lock index 5613e66..e532267 100644 --- a/yarn.lock +++ b/yarn.lock @@ -597,6 +597,14 @@ is-plain-object "^5.0.0" universal-user-agent "^6.0.0" +"@octokit/graphql-schema@^10.73.0": + version "10.73.0" + resolved "https://registry.yarnpkg.com/@octokit/graphql-schema/-/graphql-schema-10.73.0.tgz#d1dadc7ea854f01b16ee9a5bdc4715b9ab5a8f9c" + integrity sha512-0pHfbnl3n+b5mtTwP6nmMYlMwLgUvGZjndKVqPS2uyR1Vh8WGMldVW4sFXxDxOPY8RyznLGg44m8sDQT+vAbnw== + dependencies: + graphql "^15.0.0" + graphql-tag "^2.10.3" + "@octokit/graphql@^4.5.8": version "4.8.0" resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.8.0.tgz#664d9b11c0e12112cbf78e10f49a05959aa22cc3" @@ -1992,6 +2000,18 @@ graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graphql-tag@^2.10.3: + version "2.12.5" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.5.tgz#5cff974a67b417747d05c8d9f5f3cb4495d0db8f" + integrity sha512-5xNhP4063d16Pz3HBtKprutsPrmHZi5IdUGOWRxA2B6VF7BIRGOHZ5WQvDmJXZuPcBg7rYwaFxvQYjqkSdR3TQ== + dependencies: + tslib "^2.1.0" + +graphql@^15.0.0: + version "15.6.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.6.1.tgz#9125bdf057553525da251e19e96dab3d3855ddfc" + integrity sha512-3i5lu0z6dRvJ48QP9kFxBkJ7h4Kso7PS8eahyTFz5Jm6CvQfLtNIE8LX9N6JLnXTuwR+sIYnXzaWp6anOg0QQw== + has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" @@ -3639,6 +3659,11 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"