diff --git a/lib/platform/gitlab/index.js b/lib/platform/gitlab/index.js index 952c31b08c4bca1f103b967c3c8d1956097f622e..11d036d6fe0d5163fa6dcd25f73662a4ffa9fca0 100644 --- a/lib/platform/gitlab/index.js +++ b/lib/platform/gitlab/index.js @@ -381,12 +381,97 @@ function addReviewers(iid, reviewers) { logger.warn('Unimplemented in GitLab: approvals'); } -async function ensureComment() { - // Todo: implement. See GitHub API for example +async function getComments(issueNo) { + // GET https://gitlab.com/api/v4/projects/:owner/:repo/merge_requests/:number/notes + logger.debug(`Getting comments for #${issueNo}`); + const url = `https://gitlab.com/api/v4/projects/${ + config.repository + }/merge_requests/${issueNo}/notes`; + const comments = (await get(url, { paginate: true })).body; + logger.debug(`Found ${comments.length} comments`); + return comments; +} + +async function addComment(issueNo, body) { + // POST https://gitlab.com/api/v4/projects/:owner/:repo/merge_requests/:number/notes + await get.post( + `https://gitlab.com/api/v4/projects/${ + config.repository + }/merge_requests/${issueNo}/notes`, + { + body: { body }, + } + ); +} + +async function editComment(issueNo, commentId, body) { + // PATCH https://gitlab.com/api/v4/projects/:owner/:repo/merge_requests/:number/notes/:id + await get.patch( + `https://gitlab.com/api/v4/projects/${ + config.repository + }/merge_requests/${issueNo}/notes/${commentId}`, + { + body: { body }, + } + ); +} + +async function deleteComment(issueNo, commentId) { + // DELETE https://gitlab.com/api/v4/projects/:owner/:repo/merge_requests/:number/notes/:id + await get.delete( + `https://gitlab.com/api/v4/projects/${ + config.repository + }/merge_requests/${issueNo}/notes/${commentId}` + ); } -async function ensureCommentRemoval() { - // Todo: implement. See GitHub API for example +async function ensureComment(issueNo, topic, content) { + const comments = await getComments(issueNo); + let body; + let commentId; + let commentNeedsUpdating; + if (topic) { + logger.debug(`Ensuring comment "${topic}" in #${issueNo}`); + body = `### ${topic}\n\n${content}`; + comments.forEach(comment => { + if (comment.body.startsWith(`### ${topic}\n\n`)) { + commentId = comment.id; + commentNeedsUpdating = comment.body !== body; + } + }); + } else { + logger.debug(`Ensuring content-only comment in #${issueNo}`); + body = `${content}`; + comments.forEach(comment => { + if (comment.body === body) { + commentId = comment.id; + commentNeedsUpdating = false; + } + }); + } + if (!commentId) { + await addComment(issueNo, body); + logger.info({ repository: config.repository, issueNo }, 'Added comment'); + } else if (commentNeedsUpdating) { + await editComment(issueNo, commentId, body); + logger.info({ repository: config.repository, issueNo }, 'Updated comment'); + } else { + logger.debug('Comment is already update-to-date'); + } +} + +async function ensureCommentRemoval(issueNo, topic) { + logger.debug(`Ensuring comment "${topic}" in #${issueNo} is removed`); + const comments = await getComments(issueNo); + let commentId; + comments.forEach(comment => { + if (comment.body.startsWith(`### ${topic}\n\n`)) { + commentId = comment.id; + } + }); + if (commentId) { + await deleteComment(issueNo, commentId); + } } async function getPrList() { diff --git a/test/platform/gitlab/__snapshots__/index.spec.js.snap b/test/platform/gitlab/__snapshots__/index.spec.js.snap index e19ed8345f1bef098ebc0f422b9f357a25638005..231763b4939dc2598c692e5fd6ccd4ab41dbf79e 100644 --- a/test/platform/gitlab/__snapshots__/index.spec.js.snap +++ b/test/platform/gitlab/__snapshots__/index.spec.js.snap @@ -124,6 +124,38 @@ Array [ ] `; +exports[`platform/gitlab ensureComment add comment if not found 1`] = ` +Array [ + Array [ + "https://gitlab.com/api/v4/projects/some%2Frepo/merge_requests/42/notes", + Object { + "body": Object { + "body": "### some-subject + +some +content", + }, + }, + ], +] +`; + +exports[`platform/gitlab ensureComment add updates comment if necessary 1`] = ` +Array [ + Array [ + "https://gitlab.com/api/v4/projects/some%2Frepo/merge_requests/42/notes/1234", + Object { + "body": Object { + "body": "### some-subject + +some +content", + }, + }, + ], +] +`; + exports[`platform/gitlab getAllRenovateBranches() should return all renovate branches 1`] = ` Array [ "renovate/a", diff --git a/test/platform/gitlab/index.spec.js b/test/platform/gitlab/index.spec.js index 758131f3a32de9b66a5d7dbac1f34fb4fb09811f..3dbe45b825e63ff02940ea30d9a92fbefb6abc22 100644 --- a/test/platform/gitlab/index.spec.js +++ b/test/platform/gitlab/index.spec.js @@ -78,6 +78,16 @@ describe('platform/gitlab', () => { email: 'a@b.com', }, })); + get.mockReturnValue({ + body: [ + { + number: 1, + source_branch: 'branch-a', + title: 'branch a pr', + state: 'opened', + }, + ], + }); return gitlab.initRepo(...args); } @@ -529,13 +539,48 @@ describe('platform/gitlab', () => { }); }); describe('ensureComment', () => { - it('exists', async () => { + it('add comment if not found', async () => { + await initRepo({ repository: 'some/repo', token: 'token' }); + get.mockReturnValueOnce({ body: [] }); + await gitlab.ensureComment(42, 'some-subject', 'some\ncontent'); + expect(get.post.mock.calls).toHaveLength(1); + expect(get.post.mock.calls).toMatchSnapshot(); + }); + it('add updates comment if necessary', async () => { + await initRepo({ repository: 'some/repo', token: 'token' }); + get.mockReturnValueOnce({ + body: [{ id: 1234, body: '### some-subject\n\nblablabla' }], + }); + await gitlab.ensureComment(42, 'some-subject', 'some\ncontent'); + expect(get.post.mock.calls).toHaveLength(0); + expect(get.patch.mock.calls).toHaveLength(1); + expect(get.patch.mock.calls).toMatchSnapshot(); + }); + it('skips comment', async () => { + await initRepo({ repository: 'some/repo', token: 'token' }); + get.mockReturnValueOnce({ + body: [{ id: 1234, body: '### some-subject\n\nsome\ncontent' }], + }); await gitlab.ensureComment(42, 'some-subject', 'some\ncontent'); + expect(get.post.mock.calls).toHaveLength(0); + expect(get.patch.mock.calls).toHaveLength(0); + }); + it('handles comment with no description', async () => { + await initRepo({ repository: 'some/repo', token: 'token' }); + get.mockReturnValueOnce({ body: [{ id: 1234, body: '!merge' }] }); + await gitlab.ensureComment(42, null, '!merge'); + expect(get.post.mock.calls).toHaveLength(0); + expect(get.patch.mock.calls).toHaveLength(0); }); }); describe('ensureCommentRemoval', () => { - it('exists', async () => { + it('deletes comment if found', async () => { + await initRepo({ repository: 'some/repo', token: 'token' }); + get.mockReturnValueOnce({ + body: [{ id: 1234, body: '### some-subject\n\nblablabla' }], + }); await gitlab.ensureCommentRemoval(42, 'some-subject'); + expect(get.delete.mock.calls).toHaveLength(1); }); }); describe('findPr(branchName, prTitle, state)', () => {