diff --git a/lib/platform/azure/index.spec.ts b/lib/platform/azure/index.spec.ts index 0762a6bbd95c29b7161337590c6aa69091467b70..e01cc79edbd3db9207ae3189b3e97e48e0c92d2b 100644 --- a/lib/platform/azure/index.spec.ts +++ b/lib/platform/azure/index.spec.ts @@ -777,40 +777,50 @@ describe('platform/azure', () => { }); describe('ensureCommentRemoval', () => { - it('deletes comment if found', async () => { + let gitApiMock; + beforeEach(() => { + gitApiMock = { + getThreads: jest.fn(() => [ + { + comments: [{ content: '### some-subject\n\nblabla' }], + id: 123, + }, + { + comments: [{ content: 'some-content\n' }], + id: 124, + }, + ]), + updateThread: jest.fn(), + }; + azureApi.gitApi.mockImplementation(() => gitApiMock); + }); + it('deletes comment by topic if found', async () => { await initRepo({ repository: 'some/repo' }); - azureApi.gitApi.mockImplementation( - () => - ({ - getThreads: jest.fn(() => [ - { - comments: [{ content: '### some-subject\n\nblabla' }], - id: 123, - }, - ]), - updateThread: jest.fn(), - } as any) - ); await azure.ensureCommentRemoval({ number: 42, topic: 'some-subject' }); - expect(azureApi.gitApi).toHaveBeenCalledTimes(3); + expect(gitApiMock.getThreads).toHaveBeenCalledWith('1', 42); + expect(gitApiMock.updateThread).toHaveBeenCalledWith( + { status: 4 }, + '1', + 42, + 123 + ); }); - it('nothing should happen, no number', async () => { - await azure.ensureCommentRemoval({ number: 0, topic: 'test' }); - expect(azureApi.gitApi).toHaveBeenCalledTimes(0); + it('deletes comment by content if found', async () => { + await initRepo({ repository: 'some/repo' }); + await azure.ensureCommentRemoval({ number: 42, content: 'some-content' }); + expect(gitApiMock.getThreads).toHaveBeenCalledWith('1', 42); + expect(gitApiMock.updateThread).toHaveBeenCalledWith( + { status: 4 }, + '1', + 42, + 124 + ); }); it('comment not found', async () => { await initRepo({ repository: 'some/repo' }); - azureApi.gitApi.mockImplementation( - () => - ({ - getThreads: jest.fn(() => [ - { comments: [{ content: 'stupid comment' }], id: 123 }, - ]), - updateThread: jest.fn(), - } as any) - ); - await azure.ensureCommentRemoval({ number: 42, topic: 'some-subject' }); - expect(azureApi.gitApi).toHaveBeenCalledTimes(3); + await azure.ensureCommentRemoval({ number: 42, topic: 'does-not-exist' }); + expect(gitApiMock.getThreads).toHaveBeenCalledWith('1', 42); + expect(gitApiMock.updateThread).not.toHaveBeenCalled(); }); }); diff --git a/lib/platform/azure/index.ts b/lib/platform/azure/index.ts index 9bb3700045da3632e2d8f108fa07ec4c5dff0b3d..52414ad774f868d49e0339e5b99ceb8ea9fb2e7a 100644 --- a/lib/platform/azure/index.ts +++ b/lib/platform/azure/index.ts @@ -1,5 +1,6 @@ import { GitPullRequest, + GitPullRequestCommentThread, GitPullRequestMergeStrategy, } from 'azure-devops-node-api/interfaces/GitInterfaces'; import { RenovateConfig } from '../../config/common'; @@ -572,29 +573,37 @@ export async function ensureComment({ export async function ensureCommentRemoval({ number: issueNo, topic, + content, }: EnsureCommentRemovalConfig): Promise<void> { - logger.debug(`ensureCommentRemoval(issueNo, topic)(${issueNo}, ${topic})`); - if (issueNo) { - const azureApiGit = await azureApi.gitApi(); - const threads = await azureApiGit.getThreads(config.repoId, issueNo); - let threadIdFound = null; + logger.debug( + `Ensuring comment "${topic || content}" in #${issueNo} is removed` + ); - threads.forEach((thread) => { - if (thread.comments[0].content.startsWith(`### ${topic}\n\n`)) { - threadIdFound = thread.id; - } - }); + const azureApiGit = await azureApi.gitApi(); + const threads = await azureApiGit.getThreads(config.repoId, issueNo); - if (threadIdFound) { - await azureApiGit.updateThread( - { - status: 4, // close - }, - config.repoId, - issueNo, - threadIdFound - ); - } + const byTopic = (thread: GitPullRequestCommentThread): boolean => + thread.comments[0].content.startsWith(`### ${topic}\n\n`); + const byContent = (thread: GitPullRequestCommentThread): boolean => + thread.comments[0].content.trim() === content; + + let threadIdFound: number | null = null; + + if (topic) { + threadIdFound = threads.find(byTopic)?.id; + } else if (content) { + threadIdFound = threads.find(byContent)?.id; + } + + if (threadIdFound) { + await azureApiGit.updateThread( + { + status: 4, // close + }, + config.repoId, + issueNo, + threadIdFound + ); } } diff --git a/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap b/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap index 918f2a460b402719402223a5cd6108fd9fd096aa..35d7d34f24a908e890c32a8e2e62b3fd4a660e1e 100644 --- a/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap +++ b/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap @@ -439,7 +439,23 @@ Array [ ] `; -exports[`platform/bitbucket-server endpoint with no path ensureCommentRemoval() deletes comment if found 1`] = ` +exports[`platform/bitbucket-server endpoint with no path ensureCommentRemoval() deletes comment by content if found 1`] = ` +Array [ + Array [ + "./rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100", + undefined, + ], + Array [ + "./rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100&start=1", + undefined, + ], + Array [ + "./rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/comments/22", + ], +] +`; + +exports[`platform/bitbucket-server endpoint with no path ensureCommentRemoval() deletes comment by topic if found 1`] = ` Array [ Array [ "./rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100", @@ -2002,7 +2018,23 @@ Array [ ] `; -exports[`platform/bitbucket-server endpoint with path ensureCommentRemoval() deletes comment if found 1`] = ` +exports[`platform/bitbucket-server endpoint with path ensureCommentRemoval() deletes comment by content if found 1`] = ` +Array [ + Array [ + "./rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100", + undefined, + ], + Array [ + "./rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100&start=1", + undefined, + ], + Array [ + "./rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/comments/22", + ], +] +`; + +exports[`platform/bitbucket-server endpoint with path ensureCommentRemoval() deletes comment by topic if found 1`] = ` Array [ Array [ "./rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100", diff --git a/lib/platform/bitbucket-server/index.spec.ts b/lib/platform/bitbucket-server/index.spec.ts index 2c90f2b2b127609e927d00b6aef8aa4213c1a72a..9dc79ed5a50f0c7e927e8a7304eb4f716d802d0e 100644 --- a/lib/platform/bitbucket-server/index.spec.ts +++ b/lib/platform/bitbucket-server/index.spec.ts @@ -491,7 +491,7 @@ describe('platform/bitbucket-server', () => { expect(api.get.mock.calls).toMatchSnapshot(); }); - it('deletes comment if found', async () => { + it('deletes comment by topic if found', async () => { expect.assertions(2); await initRepo(); api.get.mockClear(); @@ -504,6 +504,19 @@ describe('platform/bitbucket-server', () => { expect(api.delete).toHaveBeenCalledTimes(1); }); + it('deletes comment by content if found', async () => { + expect.assertions(2); + await initRepo(); + api.get.mockClear(); + + await bitbucket.ensureCommentRemoval({ + number: 5, + content: '!merge', + }); + expect(api.get.mock.calls).toMatchSnapshot(); + expect(api.delete).toHaveBeenCalledTimes(1); + }); + it('deletes nothing', async () => { expect.assertions(2); await initRepo(); diff --git a/lib/platform/bitbucket-server/index.ts b/lib/platform/bitbucket-server/index.ts index d5af7775967becffc93a1b4d13dada0110bbb713..ca495e9c0520529117f044412959713ed4b71c19 100644 --- a/lib/platform/bitbucket-server/index.ts +++ b/lib/platform/bitbucket-server/index.ts @@ -867,16 +867,27 @@ export async function ensureComment({ export async function ensureCommentRemoval({ number: prNo, topic, + content, }: EnsureCommentRemovalConfig): Promise<void> { try { - logger.debug(`Ensuring comment "${topic}" in #${prNo} is removed`); + logger.debug( + `Ensuring comment "${topic || content}" in #${prNo} is removed` + ); const comments = await getComments(prNo); - let commentId: number; - comments.forEach((comment) => { - if (comment.text.startsWith(`### ${topic}\n\n`)) { - commentId = comment.id; - } - }); + + const byTopic = (comment: Comment): boolean => + comment.text.startsWith(`### ${topic}\n\n`); + const byContent = (comment: Comment): boolean => + comment.text.trim() === content; + + let commentId: number | null = null; + + if (topic) { + commentId = comments.find(byTopic)?.id; + } else if (content) { + commentId = comments.find(byContent)?.id; + } + if (commentId) { await deleteComment(prNo, commentId); } diff --git a/lib/platform/bitbucket/__snapshots__/comments.spec.ts.snap b/lib/platform/bitbucket/__snapshots__/comments.spec.ts.snap index 0e7a5aeef0302416b4175f4a5c9e131d5fe21a00..bb1301f380dddfa2b266ea98f227fa8a28479fc1 100644 --- a/lib/platform/bitbucket/__snapshots__/comments.spec.ts.snap +++ b/lib/platform/bitbucket/__snapshots__/comments.spec.ts.snap @@ -63,7 +63,16 @@ Array [ ] `; -exports[`platform/comments ensureCommentRemoval() deletes comment if found 1`] = ` +exports[`platform/comments ensureCommentRemoval() deletes comment by content if found 1`] = ` +Array [ + Array [ + "/2.0/repositories/some/repo/pullrequests/5/comments?pagelen=100", + undefined, + ], +] +`; + +exports[`platform/comments ensureCommentRemoval() deletes comment by topic if found 1`] = ` Array [ Array [ "/2.0/repositories/some/repo/pullrequests/5/comments?pagelen=100", diff --git a/lib/platform/bitbucket/comments.spec.ts b/lib/platform/bitbucket/comments.spec.ts index bd42fd8c41bc502e0816313673afdd485c4a3ae2..6488be7873ce0a43bc27bccdb8ba0e6c4817a283 100644 --- a/lib/platform/bitbucket/comments.spec.ts +++ b/lib/platform/bitbucket/comments.spec.ts @@ -147,7 +147,7 @@ describe('platform/comments', () => { expect(api.get.mock.calls).toMatchSnapshot(); }); - it('deletes comment if found', async () => { + it('deletes comment by topic if found', async () => { expect.assertions(2); api.get.mockClear(); @@ -156,6 +156,15 @@ describe('platform/comments', () => { expect(api.delete).toHaveBeenCalledTimes(1); }); + it('deletes comment by content if found', async () => { + expect.assertions(2); + api.get.mockClear(); + + await comments.ensureCommentRemoval(config, 5, undefined, '!merge'); + expect(api.get.mock.calls).toMatchSnapshot(); + expect(api.delete).toHaveBeenCalledTimes(1); + }); + it('deletes nothing', async () => { expect.assertions(2); api.get.mockClear(); diff --git a/lib/platform/bitbucket/comments.ts b/lib/platform/bitbucket/comments.ts index 1c86bd10403b6e457153d9e2e80f0fcaf6c7568c..86a1c40086d6effa52796a84b7830e8fb433b603 100644 --- a/lib/platform/bitbucket/comments.ts +++ b/lib/platform/bitbucket/comments.ts @@ -115,17 +115,28 @@ export async function ensureComment({ export async function ensureCommentRemoval( config: CommentsConfig, prNo: number, - topic: string + topic?: string, + content?: string ): Promise<void> { try { - logger.debug(`Ensuring comment "${topic}" in #${prNo} is removed`); + logger.debug( + `Ensuring comment "${topic || content}" in #${prNo} is removed` + ); const comments = await getComments(config, prNo); - let commentId: number; - comments.forEach((comment) => { - if (comment.content.raw.startsWith(`### ${topic}\n\n`)) { - commentId = comment.id; - } - }); + + const byTopic = (comment: Comment): boolean => + comment.content.raw.startsWith(`### ${topic}\n\n`); + const byContent = (comment: Comment): boolean => + comment.content.raw.trim() === content; + + let commentId: number | null = null; + + if (topic) { + commentId = comments.find(byTopic)?.id; + } else if (content) { + commentId = comments.find(byContent)?.id; + } + if (commentId) { await deleteComment(config, prNo, commentId); } diff --git a/lib/platform/bitbucket/index.ts b/lib/platform/bitbucket/index.ts index 39f1fa85a0803041a1edd2caefb322dcbb0f5425..bba4830bafa9d15f03d6c592482ffe0a097c9a16 100644 --- a/lib/platform/bitbucket/index.ts +++ b/lib/platform/bitbucket/index.ts @@ -710,8 +710,9 @@ export function ensureComment({ export function ensureCommentRemoval({ number: prNo, topic, + content, }: EnsureCommentRemovalConfig): Promise<void> { - return comments.ensureCommentRemoval(config, prNo, topic); + return comments.ensureCommentRemoval(config, prNo, topic, content); } // Creates PR and returns PR number diff --git a/lib/platform/common.ts b/lib/platform/common.ts index 6690e178b4b16c901b92714640aea818921ddf37..38bc9031532c9c9826f87ea6af0bdf609396d891 100644 --- a/lib/platform/common.ts +++ b/lib/platform/common.ts @@ -166,10 +166,19 @@ export interface EnsureCommentConfig { topic: string; content: string; } + +export interface EnsureCommentRemovalConfigByTopic { + number: number; + topic: string; +} +export interface EnsureCommentRemovalConfigByContent { + number: number; + content: string; +} export interface EnsureCommentRemovalConfig { number: number; - topic?: string; content?: string; + topic?: string; } export type EnsureIssueResult = 'updated' | 'created'; @@ -207,7 +216,9 @@ export interface Platform { context: string ): Promise<BranchStatus | null>; ensureCommentRemoval( - ensureCommentRemoval: EnsureCommentRemovalConfig + ensureCommentRemoval: + | EnsureCommentRemovalConfigByTopic + | EnsureCommentRemovalConfigByContent ): Promise<void>; deleteBranch(branchName: string, closePr?: boolean): Promise<void>; ensureComment(ensureComment: EnsureCommentConfig): Promise<boolean>; diff --git a/lib/platform/gitea/index.spec.ts b/lib/platform/gitea/index.spec.ts index 49ddde3a4e297740714627fda9154543b38d29cb..1ab1a02d09f6bbbb8d724a7289f2c124ef9dfd85 100644 --- a/lib/platform/gitea/index.spec.ts +++ b/lib/platform/gitea/index.spec.ts @@ -1322,16 +1322,22 @@ index 0000000..2173594 }); describe('ensureCommentRemoval', () => { - it('should remove existing comment', async () => { + it('should remove existing comment by topic', async () => { helper.getComments.mockResolvedValueOnce(mockComments); await initFakeRepo(); await gitea.ensureCommentRemoval({ number: 1, topic: 'some-topic' }); expect(helper.deleteComment).toHaveBeenCalledTimes(1); - expect(helper.deleteComment).toHaveBeenCalledWith( - mockRepo.full_name, - expect.any(Number) - ); + expect(helper.deleteComment).toHaveBeenCalledWith(mockRepo.full_name, 3); + }); + + it('should remove existing comment by content', async () => { + helper.getComments.mockResolvedValueOnce(mockComments); + await initFakeRepo(); + await gitea.ensureCommentRemoval({ number: 1, content: 'some-body' }); + + expect(helper.deleteComment).toHaveBeenCalledTimes(1); + expect(helper.deleteComment).toHaveBeenCalledWith(mockRepo.full_name, 1); }); it('should gracefully fail with warning', async () => { diff --git a/lib/platform/gitea/index.ts b/lib/platform/gitea/index.ts index ff00f8d0aae0f9d204d04eea7fa04a22a2632740..bc47b23f13438afe434eab7d30c2d301e5b02c0c 100644 --- a/lib/platform/gitea/index.ts +++ b/lib/platform/gitea/index.ts @@ -128,6 +128,13 @@ function findCommentByTopic( return comments.find((c) => c.body.startsWith(`### ${topic}\n\n`)); } +function findCommentByContent( + comments: helper.Comment[], + content: string +): helper.Comment | null { + return comments.find((c) => c.body.trim() === content); +} + async function isPRModified( repoPath: string, branchName: string @@ -820,13 +827,23 @@ const platform: Platform = { async ensureCommentRemoval({ number: issue, topic, + content, }: EnsureCommentRemovalConfig): Promise<void> { + logger.debug( + `Ensuring comment "${topic || content}" in #${issue} is removed` + ); const commentList = await helper.getComments(config.repository, issue); - const comment = findCommentByTopic(commentList, topic); + let comment: helper.Comment | null = null; + + if (topic) { + comment = findCommentByTopic(commentList, topic); + } else if (content) { + comment = findCommentByContent(commentList, content); + } // Abort and do nothing if no matching comment was found if (!comment) { - return null; + return; } // Attempt to delete comment @@ -835,8 +852,6 @@ const platform: Platform = { } catch (err) { logger.warn({ err, issue, subject: topic }, 'Error deleting comment'); } - - return null; }, async getBranchPr(branchName: string): Promise<Pr | null> { diff --git a/lib/platform/github/__snapshots__/index.spec.ts.snap b/lib/platform/github/__snapshots__/index.spec.ts.snap index 7dea1c622b0e2e2de22b9729f7656448436a502d..9b6a588d17c82ee6076f641595ca6354a7ee648f 100644 --- a/lib/platform/github/__snapshots__/index.spec.ts.snap +++ b/lib/platform/github/__snapshots__/index.spec.ts.snap @@ -802,7 +802,127 @@ Array [ ] `; -exports[`platform/github ensureCommentRemoval deletes comment if found 1`] = ` +exports[`platform/github ensureCommentRemoval deletes comment by content if found 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc123", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/repo", + }, + Object { + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "repo", + "owner": "some", + }, + "pullRequests": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "comments": Object { + "__args": Object { + "last": "100", + }, + "nodes": Object { + "body": null, + "databaseId": null, + }, + }, + "headRefName": null, + "number": null, + "state": null, + "title": null, + }, + }, + }, + }, + }, + "headers": Object { + "accept-encoding": "gzip, deflate", + "authorization": "token abc123", + "content-length": 513, + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "POST", + "url": "https://api.github.com/graphql", + }, + Object { + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "repo", + "owner": "some", + }, + "pullRequests": Object { + "__args": Object { + "first": "25", + }, + "nodes": Object { + "comments": Object { + "__args": Object { + "last": "100", + }, + "nodes": Object { + "body": null, + "databaseId": null, + }, + }, + "headRefName": null, + "number": null, + "state": null, + "title": null, + }, + }, + }, + }, + }, + "headers": Object { + "accept-encoding": "gzip, deflate", + "authorization": "token abc123", + "content-length": 512, + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "POST", + "url": "https://api.github.com/graphql", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc123", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/repo/issues/42/comments?per_page=100", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc123", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "DELETE", + "url": "https://api.github.com/repos/some/repo/issues/comments/1234", + }, +] +`; + +exports[`platform/github ensureCommentRemoval deletes comment by topic if found 1`] = ` Array [ Object { "headers": Object { diff --git a/lib/platform/github/index.spec.ts b/lib/platform/github/index.spec.ts index c65111248127757024d16661752a39bc3d0f7ce7..bf32b2283d6a37544079a31f37d005d6251aa53c 100644 --- a/lib/platform/github/index.spec.ts +++ b/lib/platform/github/index.spec.ts @@ -1426,7 +1426,7 @@ describe('platform/github', () => { }); }); describe('ensureCommentRemoval', () => { - it('deletes comment if found', async () => { + it('deletes comment by topic if found', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); scope @@ -1441,6 +1441,24 @@ describe('platform/github', () => { await github.ensureCommentRemoval({ number: 42, topic: 'some-subject' }); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('deletes comment by content if found', async () => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope + .post('/graphql') + .twice() + .reply(200, {}) + .get('/repos/some/repo/issues/42/comments?per_page=100') + .reply(200, [{ id: 1234, body: 'some-content' }]) + .delete('/repos/some/repo/issues/comments/1234') + .reply(200); + await github.initRepo({ repository: 'some/repo', token: 'token' } as any); + await github.ensureCommentRemoval({ + number: 42, + content: 'some-content', + }); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); }); describe('findPr(branchName, prTitle, state)', () => { it('returns true if no title and all state', async () => { diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts index 636d2fe811ae4fb7f4a0e62bbb9cbc30c2d054a8..108fb6460242de327873678328876ea6d46e3dcd 100644 --- a/lib/platform/github/index.ts +++ b/lib/platform/github/index.ts @@ -1568,15 +1568,25 @@ export async function ensureComment({ export async function ensureCommentRemoval({ number: issueNo, topic, + content, }: EnsureCommentRemovalConfig): Promise<void> { - logger.debug(`Ensuring comment "${topic}" in #${issueNo} is removed`); + logger.debug( + `Ensuring comment "${topic || content}" in #${issueNo} is removed` + ); const comments = await getComments(issueNo); - let commentId: number; - comments.forEach((comment) => { - if (comment.body.startsWith(`### ${topic}\n\n`)) { - commentId = comment.id; - } - }); + let commentId: number | null = null; + + const byTopic = (comment: Comment): boolean => + comment.body.startsWith(`### ${topic}\n\n`); + const byContent = (comment: Comment): boolean => + comment.body.trim() === content; + + if (topic) { + commentId = comments.find(byTopic)?.id; + } else if (content) { + commentId = comments.find(byContent)?.id; + } + try { if (commentId) { await deleteComment(commentId); diff --git a/lib/platform/gitlab/index.spec.ts b/lib/platform/gitlab/index.spec.ts index 87b0787e724addaca9694b8ae8beb65412f1b270..40f748ca9f8cde3ca93b086c68ea2787ac2a9d23 100644 --- a/lib/platform/gitlab/index.spec.ts +++ b/lib/platform/gitlab/index.spec.ts @@ -850,7 +850,7 @@ describe('platform/gitlab', () => { }); }); describe('ensureCommentRemoval', () => { - it('deletes comment if found', async () => { + it('deletes comment by topic if found', async () => { await initRepo({ repository: 'some/repo', token: 'token' }); api.get.mockResolvedValueOnce( partial<GotResponse>({ @@ -860,6 +860,16 @@ describe('platform/gitlab', () => { await gitlab.ensureCommentRemoval({ number: 42, topic: 'some-subject' }); expect(api.delete).toHaveBeenCalledTimes(1); }); + it('deletes comment by content if found', async () => { + await initRepo({ repository: 'some/repo', token: 'token' }); + api.get.mockResolvedValueOnce( + partial<GotResponse>({ + body: [{ id: 1234, body: 'some-body\n' }], + }) + ); + await gitlab.ensureCommentRemoval({ number: 42, content: 'some-body' }); + expect(api.delete).toHaveBeenCalledTimes(1); + }); }); describe('findPr(branchName, prTitle, state)', () => { it('returns true if no title and all state', async () => { diff --git a/lib/platform/gitlab/index.ts b/lib/platform/gitlab/index.ts index 0e88681bb775d5e165e2f3dff72e123ef0c5cca1..2e0a380576aad4a6ecc37a915fa68da3af4ead8a 100644 --- a/lib/platform/gitlab/index.ts +++ b/lib/platform/gitlab/index.ts @@ -848,7 +848,7 @@ export async function deleteLabel( } } -async function getComments(issueNo: number): Promise<any[]> { +async function getComments(issueNo: number): Promise<GitlabComment[]> { // GET projects/:owner/:repo/merge_requests/:number/notes logger.debug(`Getting comments for #${issueNo}`); const url = `projects/${config.repository}/merge_requests/${issueNo}/notes`; @@ -942,18 +942,34 @@ export async function ensureComment({ return true; } +type GitlabComment = { + body: string; + id: number; +}; + export async function ensureCommentRemoval({ number: issueNo, topic, + content, }: EnsureCommentRemovalConfig): Promise<void> { - logger.debug(`Ensuring comment "${topic}" in #${issueNo} is removed`); + logger.debug( + `Ensuring comment "${topic || content}" in #${issueNo} is removed` + ); + const comments = await getComments(issueNo); - let commentId: number; - comments.forEach((comment: { body: string; id: number }) => { - if (comment.body.startsWith(`### ${topic}\n\n`)) { - commentId = comment.id; - } - }); + let commentId: number | null = null; + + const byTopic = (comment: GitlabComment): boolean => + comment.body.startsWith(`### ${topic}\n\n`); + const byContent = (comment: GitlabComment): boolean => + comment.body.trim() === content; + + if (topic) { + commentId = comments.find(byTopic)?.id; + } else if (content) { + commentId = comments.find(byContent)?.id; + } + if (commentId) { await deleteComment(issueNo, commentId); }