From afc7ccfad396b63e3865228f986185f79b3975e5 Mon Sep 17 00:00:00 2001 From: Jonas Schubert Date: Sun, 2 Jun 2024 16:12:07 +0200 Subject: [PATCH] feat(success-comment): allow to skip based on provided template #480 #636 --- README.md | 28 ++++--- lib/success.js | 40 +++++---- test/success.test.js | 191 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 235 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 129df9f8..227cea70 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ If you need to bypass the proxy for some hosts, configure the `NO_PROXY` environ | `gitlabApiPathPrefix` | The GitLab API prefix. | `GL_PREFIX` or `GITLAB_PREFIX` environment variable or CI provided environment variables if running on [GitLab CI/CD](https://docs.gitlab.com/ee/ci) or `/api/v4`. | | `assets` | An array of files to upload to the release. See [assets](#assets). | - | | `milestones` | An array of milestone titles to associate to the release. See [GitLab Release API](https://docs.gitlab.com/ee/api/releases/#create-a-release). | - | -| `successComment` | The comment to add to each Issue and Merge Request resolved by the release. Set to false to disable commenting. See [successComment](#successComment). | :tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nThe release is available on [GitLab release](gitlab_release_url) | +| `successComment` | The comment to add to each Issue and Merge Request resolved by the release. Set to false to disable commenting or use filter to select when to comment. See [successComment](#successComment). | :tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nThe release is available on [GitLab release](gitlab_release_url) | | `failComment` | The content of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. See [failComment](#failcomment). | Friendly message with links to **semantic-release** documentation and support, with the list of errors that caused the release to fail. | | `failTitle` | The title of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. | `The automated release is failing 🚨` | | `labels` | The [labels](https://docs.gitlab.com/ee/user/project/labels.html#labels) to add to the issue created when a release fails. Set to `false` to not add any label. Labels should be comma-separated as described in the [official docs](https://docs.gitlab.com/ee/api/issues.html#new-issue), e.g. `"semantic-release,bot"`. | `semantic-release` | @@ -136,15 +136,23 @@ distribution` and `MyLibrary CSS distribution` in the GitLab release. The message for the issue comments is generated with [Lodash template](https://lodash.com/docs#template). The following variables are available: -| Parameter | Description | -| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `branch` | `Object` with `name`, `type`, `channel`, `range` and `prerelease` properties of the branch from which the release is done. | -| `lastRelease` | `Object` with `version`, `channel`, `gitTag` and `gitHead` of the last release. | -| `nextRelease` | `Object` with `version`, `channel`, `gitTag`, `gitHead` and `notes` of the release being done. | -| `commits` | `Array` of commit `Object`s with `hash`, `subject`, `body` `message` and `author`. | -| `releases` | `Array` with a release `Object`s for each release published, with optional release data such as `name` and `url`. | -| `issue` | A [GitLab API Issue object](https://docs.gitlab.com/ee/api/issues.html#single-issue) the comment will be posted to, or `false` when commenting Merge Requests. | -| `mergeRequest` | A [GitLab API Merge Request object](https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr) the comment will be posted to, or `false` when commenting Issues. | +| Parameter | Description | +| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `branch` | `Object` with `name`, `type`, `channel`, `range` and `prerelease` properties of the branch from which the release is done. | +| `lastRelease` | `Object` with `version`, `channel`, `gitTag` and `gitHead` of the last release. | +| `nextRelease` | `Object` with `version`, `channel`, `gitTag`, `gitHead` and `notes` of the release being done. | +| `commits` | `Array` of commit `Object`s with `hash`, `subject`, `body` `message` and `author`. | +| `releases` | `Array` with a release `Object`s for each release published, with optional release data such as `name` and `url`. | +| `issue` | A [GitLab API Issue object](https://docs.gitlab.com/ee/api/issues.html#single-issue) the comment will be posted to, or `false` when commenting Merge Requests. | +| `mergeRequest` | A [GitLab API Merge Request object](https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr) the comment will be posted to, or `false` when commenting Issues. | +| `defaultComment` | Introduced in `v13.2.0`. You can customize the `successComment` to only comment on defined issues or merge requests. For convenience reasons, the default comment is made available to reduce boilerplate. I.e. if you want to only comment on issues, your `successComment` can be written like `"<% if (issue) { %><%= defaultComment %><% } %>"`. | + +From `v13.2.0` on you can define when to comment on success. This utilizes the [Lodash template](https://lodash.com/docs#template). + +- to only comment on issues with the otherwise default message: `"<% if (issue) { %><%= defaultComment %><% } %>"` +- to only comment on merge requests with the otherwise default message: `"<% if (mergeRequest) { %><%= defaultComment %><% } %>"` +- you can use labels and following `successMessage` to filter issues: `"<% if (issue.labels?.includes('semantic-release-relevant')) { %><%= defaultComment %><% } %>"` +- check the [GitLab API Merge Request object](https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr) or the [GitLab API Issue object](https://docs.gitlab.com/ee/api/issues.html#single-issue) for properties which can be used for the filter #### failComment diff --git a/lib/success.js b/lib/success.js index bc697b90..f29c6c97 100644 --- a/lib/success.js +++ b/lib/success.js @@ -28,14 +28,20 @@ export default async (pluginConfig, context) => { const postCommentToIssue = (issue) => { const issueNotesEndpoint = urlJoin(gitlabApiUrl, `/projects/${issue.project_id}/issues/${issue.iid}/notes`); debug("Posting issue note to %s", issueNotesEndpoint); + const defaultComment = getSuccessComment(issue, releaseInfos, nextRelease); const body = successComment - ? template(successComment)({ ...context, issue, mergeRequest: false }) - : getSuccessComment(issue, releaseInfos, nextRelease); - return got.post(issueNotesEndpoint, { - ...apiOptions, - ...proxy, - json: { body }, - }); + ? template(successComment)({ ...context, defaultComment, issue, mergeRequest: false }) + : defaultComment; + + if (body) { + return got.post(issueNotesEndpoint, { + ...apiOptions, + ...proxy, + json: { body }, + }); + } else { + logger.log("Skip commenting on issue #%d.", issue.id); + } }; const postCommentToMergeRequest = (mergeRequest) => { @@ -44,14 +50,20 @@ export default async (pluginConfig, context) => { `/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/notes` ); debug("Posting MR note to %s", mergeRequestNotesEndpoint); + const defaultComment = getSuccessComment({ isMergeRequest: true, ...mergeRequest }, releaseInfos, nextRelease); const body = successComment - ? template(successComment)({ ...context, issue: false, mergeRequest }) - : getSuccessComment({ isMergeRequest: true, ...mergeRequest }, releaseInfos, nextRelease); - return got.post(mergeRequestNotesEndpoint, { - ...apiOptions, - ...proxy, - json: { body }, - }); + ? template(successComment)({ ...context, defaultComment, issue: false, mergeRequest }) + : defaultComment; + + if (body) { + return got.post(mergeRequestNotesEndpoint, { + ...apiOptions, + ...proxy, + json: { body }, + }); + } else { + logger.log("Skip commenting on merge request #%d.", mergeRequest.iid); + } }; const getRelatedMergeRequests = async (commitHash) => { diff --git a/test/success.test.js b/test/success.test.js index 47d0c392..367af114 100644 --- a/test/success.test.js +++ b/test/success.test.js @@ -138,3 +138,194 @@ test.serial("Does not post comments when successComment is set to false", async t.true(gitlab.isDone()); }); + +test.serial("Post comments to related issues but not MRs if comments should only be for issues", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { successComment: "<% if (issue) { %><%= defaultComment %><% } %>" }; + const nextRelease = { version: "1.0.0" }; + const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }]; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const commits = [{ hash: "abcdef" }, { hash: "fedcba" }]; + const gitlab = authenticate(env) + .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`) + .reply(200, [ + { project_id: 100, iid: 1, state: "merged" }, + { project_id: 200, iid: 2, state: "closed" }, + { project_id: 300, iid: 3, state: "merged" }, + ]) + .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`) + .reply(200, [{ project_id: 100, iid: 1, state: "merged" }]) + .get(`/projects/100/merge_requests/1/closes_issues`) + .reply(200, [ + { project_id: 100, iid: 11, state: "closed" }, + { project_id: 100, iid: 12, state: "open" }, + { project_id: 100, iid: 13, state: "closed" }, + ]) + .get(`/projects/300/merge_requests/3/closes_issues`) + .reply(200, []) + .post(`/projects/100/issues/11/notes`, { + body: ":tada: This issue has been resolved in version 1.0.0 :tada:\n\nThe release is available on [GitLab release](https://gitlab.com/test_user/test_repo/-/releases/v1.0.0).\n\nYour **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package: :rocket:", + }) + .reply(200) + .post(`/projects/100/issues/13/notes`) + .reply(200); + + await success(pluginConfig, { env, options, nextRelease, logger: t.context.logger, commits, releases }); + + t.true(gitlab.isDone()); +}); + +test.serial("Post comments to related MRs but not issues if comments should only be for MRs", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { successComment: "<% if (mergeRequest) { %><%= defaultComment %><% } %>" }; + const nextRelease = { version: "1.0.0" }; + const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }]; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const commits = [{ hash: "abcdef" }, { hash: "fedcba" }]; + const gitlab = authenticate(env) + .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`) + .reply(200, [ + { project_id: 100, iid: 1, state: "merged" }, + { project_id: 200, iid: 2, state: "closed" }, + { project_id: 300, iid: 3, state: "merged" }, + ]) + .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`) + .reply(200, [{ project_id: 100, iid: 1, state: "merged" }]) + .get(`/projects/100/merge_requests/1/closes_issues`) + .reply(200, [ + { project_id: 100, iid: 11, state: "closed" }, + { project_id: 100, iid: 12, state: "open" }, + { project_id: 100, iid: 13, state: "closed" }, + ]) + .get(`/projects/300/merge_requests/3/closes_issues`) + .reply(200, []) + .post(`/projects/100/merge_requests/1/notes`, { + body: ":tada: This MR is included in version 1.0.0 :tada:\n\nThe release is available on [GitLab release](https://gitlab.com/test_user/test_repo/-/releases/v1.0.0).\n\nYour **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package: :rocket:", + }) + .reply(200) + .post(`/projects/300/merge_requests/3/notes`) + .reply(200); + + await success(pluginConfig, { env, options, nextRelease, logger: t.context.logger, commits, releases }); + + t.true(gitlab.isDone()); +}); + +test.serial("Post comments to custom defined succes relevant issues and MRs", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { + successComment: "<% if (mergeRequest.iid === 1 || issue.iid < 13) { %><%= defaultComment %><% } %>", + }; + const nextRelease = { version: "1.0.0" }; + const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }]; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const commits = [{ hash: "abcdef" }, { hash: "fedcba" }]; + const gitlab = authenticate(env) + .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`) + .reply(200, [ + { project_id: 100, iid: 1, state: "merged" }, + { project_id: 200, iid: 2, state: "closed" }, + { project_id: 300, iid: 3, state: "merged" }, + ]) + .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`) + .reply(200, [{ project_id: 100, iid: 1, state: "merged" }]) + .get(`/projects/100/merge_requests/1/closes_issues`) + .reply(200, [ + { project_id: 100, iid: 11, state: "closed" }, + { project_id: 100, iid: 12, state: "open" }, + { project_id: 100, iid: 13, state: "closed" }, + ]) + .get(`/projects/300/merge_requests/3/closes_issues`) + .reply(200, []) + .post(`/projects/100/merge_requests/1/notes`, { + body: ":tada: This MR is included in version 1.0.0 :tada:\n\nThe release is available on [GitLab release](https://gitlab.com/test_user/test_repo/-/releases/v1.0.0).\n\nYour **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package: :rocket:", + }) + .reply(200) + .post(`/projects/100/issues/11/notes`, { + body: ":tada: This issue has been resolved in version 1.0.0 :tada:\n\nThe release is available on [GitLab release](https://gitlab.com/test_user/test_repo/-/releases/v1.0.0).\n\nYour **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package: :rocket:", + }) + .reply(200); + + await success(pluginConfig, { env, options, nextRelease, logger: t.context.logger, commits, releases }); + + t.true(gitlab.isDone()); +}); + +test.serial("Post comments to issues with label 'semantic-release-relevant'", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { + successComment: "<% if (issue.labels?.includes('semantic-release-relevant')) { %><%= defaultComment %><% } %>", + }; + const nextRelease = { version: "1.0.0" }; + const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }]; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const commits = [{ hash: "abcdef" }, { hash: "fedcba" }]; + const gitlab = authenticate(env) + .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`) + .reply(200, [ + { project_id: 100, iid: 1, state: "merged" }, + { project_id: 200, iid: 2, state: "closed" }, + { project_id: 300, iid: 3, state: "merged" }, + ]) + .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`) + .reply(200, [{ project_id: 100, iid: 1, state: "merged" }]) + .get(`/projects/100/merge_requests/1/closes_issues`) + .reply(200, [ + { project_id: 100, iid: 11, labels: "doing,bug", state: "closed" }, + { project_id: 100, iid: 12, labels: "todo,feature", state: "open" }, + { project_id: 100, iid: 13, labels: "testing,semantic-release-relevant,critical", state: "closed" }, + ]) + .get(`/projects/300/merge_requests/3/closes_issues`) + .reply(200, []) + .post(`/projects/100/issues/13/notes`) + .reply(200); + + await success(pluginConfig, { env, options, nextRelease, logger: t.context.logger, commits, releases }); + + t.true(gitlab.isDone()); +}); + +test.serial("Post comments with custom template on labeled merge requests", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { + successComment: `<% if (mergeRequest.labels?.includes('semantic-release-relevant')) { %>Next release is: <%= nextRelease.version %><% } %>`, + }; + const nextRelease = { version: "1.0.0" }; + const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }]; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const commits = [{ hash: "abcdef" }]; + const gitlab = authenticate(env) + .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`) + .reply(200, [ + { project_id: 100, iid: 1, labels: "semantic-release-relevant", state: "merged" }, + { project_id: 200, iid: 2, labels: "", state: "closed" }, + { project_id: 300, iid: 3, labels: "", state: "merged" }, + ]) + .get(`/projects/100/merge_requests/1/closes_issues`) + .reply(200, [{ project_id: 100, iid: 11, state: "closed" }]) + .get(`/projects/300/merge_requests/3/closes_issues`) + .reply(200, []) + .post(`/projects/100/merge_requests/1/notes`, { + body: "Next release is: 1.0.0", + }) + .reply(200); + + await success(pluginConfig, { env, options, nextRelease, logger: t.context.logger, commits, releases }); + + t.true(gitlab.isDone()); +});