From c86267762306b9b9a984175c6ade050d631c1b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Brauer?= <zaubernerd@zaubernerd.de> Date: Tue, 12 May 2020 23:21:58 +0200 Subject: [PATCH] feat(platform): remove comments by content (#6181) --- lib/platform/azure/index.spec.ts | 66 ++++++---- lib/platform/azure/index.ts | 49 ++++--- .../__snapshots__/index.spec.ts.snap | 36 +++++- lib/platform/bitbucket-server/index.spec.ts | 15 ++- lib/platform/bitbucket-server/index.ts | 25 +++- .../__snapshots__/comments.spec.ts.snap | 11 +- lib/platform/bitbucket/comments.spec.ts | 11 +- lib/platform/bitbucket/comments.ts | 27 ++-- lib/platform/bitbucket/index.ts | 3 +- lib/platform/common.ts | 15 ++- lib/platform/gitea/index.spec.ts | 16 ++- lib/platform/gitea/index.ts | 23 +++- .../github/__snapshots__/index.spec.ts.snap | 122 +++++++++++++++++- lib/platform/github/index.spec.ts | 20 ++- lib/platform/github/index.ts | 24 +++- lib/platform/gitlab/index.spec.ts | 12 +- lib/platform/gitlab/index.ts | 32 +++-- 17 files changed, 409 insertions(+), 98 deletions(-) diff --git a/lib/platform/azure/index.spec.ts b/lib/platform/azure/index.spec.ts index 0762a6bbd9..e01cc79edb 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 9bb3700045..52414ad774 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 918f2a460b..35d7d34f24 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 2c90f2b2b1..9dc79ed5a5 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 d5af777596..ca495e9c05 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 0e7a5aeef0..bb1301f380 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 bd42fd8c41..6488be7873 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 1c86bd1040..86a1c40086 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 39f1fa85a0..bba4830baf 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 6690e178b4..38bc903153 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 49ddde3a4e..1ab1a02d09 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 ff00f8d0aa..bc47b23f13 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 7dea1c622b..9b6a588d17 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 c651112481..bf32b2283d 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 636d2fe811..108fb64602 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 87b0787e72..40f748ca9f 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 0e88681bb7..2e0a380576 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); } -- GitLab