Skip to content

Commit 327b847

Browse files
committed
feat(fail-comment): allow to skip based on provided condition #480 #636
1 parent f07706b commit 327b847

File tree

5 files changed

+184
-27
lines changed

5 files changed

+184
-27
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ If you need to bypass the proxy for some hosts, configure the `NO_PROXY` environ
9292
| `successCommentCondition` | Use this as condition, when to comment on issues or merge requests. See [successCommentCondition](#successCommentCondition). | - |
9393
| `failComment` | The content of the issue created 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. |
9494
| `failTitle` | The title of the issue created when a release fails. | `The automated release is failing 🚨` |
95+
| `failCommentCondition` | Use this as condition, when to comment on or create an issues in case of failures. See [failCommentCondition](#failCommentCondition). | - |
9596
| `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` |
9697
| `assignee` | The [assignee](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#assignee) to add to the issue created when a release fails. | - |
9798

@@ -188,6 +189,27 @@ The `failComment` `This release from branch ${branch.name} had failed due to the
188189
> - Error message 1
189190
> - Error message 2
190191
192+
#### failCommentCondition
193+
194+
The fail comment condition is generated with [Lodash template](https://lodash.com/docs#template). The following variables are available:
195+
196+
| Parameter | Description |
197+
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
198+
| `branch` | `Object` with `name`, `type`, `channel`, `range` and `prerelease` properties of the branch from which the release is done. |
199+
| `lastRelease` | `Object` with `version`, `channel`, `gitTag` and `gitHead` of the last release. |
200+
| `nextRelease` | `Object` with `version`, `channel`, `gitTag`, `gitHead` and `notes` of the release being done. |
201+
| `commits` | `Array` of commit `Object`s with `hash`, `subject`, `body` `message` and `author`. |
202+
| `releases` | `Array` with a release `Object`s for each release published, with optional release data such as `name` and `url`. |
203+
| `issue` | A [GitLab API Issue object](https://docs.gitlab.com/ee/api/issues.html#single-issue) the comment will be posted to - only available if an open issue exists. |
204+
205+
##### failCommentCondition example
206+
207+
- do no create any comments at all: `"<% return false; %>"`
208+
- to only comment on main branch: `"<% return branch.name === 'main' %>"`
209+
- you can use labels to filter issues, i.e. to not comment if the issue is labeled with `wip`: `"<% return !issue.labels?.includes('wip') %>"`
210+
211+
> 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
212+
191213
## Compatibility
192214

193215
The latest version of this plugin is compatible with all currently-supported versions of GitLab, [which is the current major version and previous two major versions](https://about.gitlab.com/support/statement-of-support.html#version-support). This plugin is not guaranteed to work with unsupported versions of GitLab.

lib/fail.js

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@ export default async (pluginConfig, context) => {
1414
errors,
1515
logger,
1616
} = context;
17-
const { gitlabToken, gitlabUrl, gitlabApiUrl, failComment, failTitle, labels, assignee } = resolveConfig(
18-
pluginConfig,
19-
context
20-
);
17+
const { gitlabToken, gitlabUrl, gitlabApiUrl, failComment, failTitle, failCommentCondition, labels, assignee } =
18+
resolveConfig(pluginConfig, context);
2119
const repoId = getRepoId(context, gitlabUrl, repositoryUrl);
2220
const encodedRepoId = encodeURIComponent(repoId);
2321
const apiOptions = { headers: { "PRIVATE-TOKEN": gitlabToken } };
@@ -36,32 +34,39 @@ Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed
3634
const openFailTitleIssues = await got(openFailTitleIssueEndpoint, { ...apiOptions }).json();
3735
const existingIssue = openFailTitleIssues.find((openFailTitleIssue) => openFailTitleIssue.title === failTitle);
3836

39-
if (existingIssue) {
40-
debug("comment on issue: %O", existingIssue);
37+
const canCommentOnOrCreateIssue = failCommentCondition
38+
? template(failCommentCondition)({ ...context, issue: existingIssue })
39+
: true;
40+
if (canCommentOnOrCreateIssue) {
41+
if (existingIssue) {
42+
debug("comment on issue: %O", existingIssue);
4143

42-
const issueNotesEndpoint = urlJoin(
43-
gitlabApiUrl,
44-
`/projects/${existingIssue.project_id}/issues/${existingIssue.iid}/notes`
45-
);
46-
await got.post(issueNotesEndpoint, {
47-
...apiOptions,
48-
json: { body: description },
49-
});
44+
const issueNotesEndpoint = urlJoin(
45+
gitlabApiUrl,
46+
`/projects/${existingIssue.project_id}/issues/${existingIssue.iid}/notes`
47+
);
48+
await got.post(issueNotesEndpoint, {
49+
...apiOptions,
50+
json: { body: description },
51+
});
5052

51-
const { id, web_url } = existingIssue;
52-
logger.log("Commented on issue #%d: %s.", id, web_url);
53-
} else {
54-
const newIssue = { id: encodedRepoId, description, labels, title: failTitle, assignee_id: assignee };
55-
debug("create issue: %O", newIssue);
53+
const { id, web_url } = existingIssue;
54+
logger.log("Commented on issue #%d: %s.", id, web_url);
55+
} else {
56+
const newIssue = { id: encodedRepoId, description, labels, title: failTitle, assignee_id: assignee };
57+
debug("create issue: %O", newIssue);
5658

57-
/* eslint camelcase: off */
58-
const { id, web_url } = await got
59-
.post(issuesEndpoint, {
60-
...apiOptions,
61-
json: newIssue,
62-
})
63-
.json();
64-
logger.log("Created issue #%d: %s.", id, web_url);
59+
/* eslint camelcase: off */
60+
const { id, web_url } = await got
61+
.post(issuesEndpoint, {
62+
...apiOptions,
63+
json: newIssue,
64+
})
65+
.json();
66+
logger.log("Created issue #%d: %s.", id, web_url);
67+
}
68+
} else {
69+
logger.log("Skip commenting on or creating an issue.");
6570
}
6671
}
6772
};

lib/resolve-config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default (
1212
successCommentCondition,
1313
failTitle,
1414
failComment,
15+
failCommentCondition,
1516
labels,
1617
assignee,
1718
},
@@ -60,6 +61,7 @@ export default (
6061
proxy: getProxyConfiguration(defaultedGitlabUrl, HTTP_PROXY, HTTPS_PROXY, NO_PROXY),
6162
failTitle: isNil(failTitle) ? "The automated release is failing 🚨" : failTitle,
6263
failComment,
64+
failCommentCondition,
6365
labels: isNil(labels) ? "semantic-release" : labels === false ? false : labels,
6466
assignee,
6567
};

test/fail.test.js

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,130 @@ test.serial("Does not post comments when failComment is set to false", async (t)
243243

244244
t.true(gitlab.isDone());
245245
});
246+
247+
test.serial("Does not post comments when failCommentCondition disables it", async (t) => {
248+
const owner = "test_user";
249+
const repo = "test_repo";
250+
const env = { GITLAB_TOKEN: "gitlab_token" };
251+
const pluginConfig = { failCommentCondition: "<% return false; %>" };
252+
const branch = { name: "main" };
253+
const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
254+
const errors = [{ message: "An error occured" }];
255+
const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
256+
const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
257+
const gitlab = authenticate(env)
258+
.get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`)
259+
.reply(200, [
260+
{
261+
id: 2,
262+
iid: 2,
263+
project_id: 1,
264+
web_url: "https://gitlab.com/test_user/test_repo/issues/2",
265+
title: "API should implemented authentication",
266+
},
267+
]);
268+
269+
await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger });
270+
271+
t.true(gitlab.isDone());
272+
});
273+
274+
test.serial("Does not post comments on existing issues when failCommentCondition disables this", async (t) => {
275+
const owner = "test_user";
276+
const repo = "test_repo";
277+
const env = { GITLAB_TOKEN: "gitlab_token" };
278+
const pluginConfig = { failCommentCondition: "<% return !issue; %>" };
279+
const branch = { name: "main" };
280+
const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
281+
const errors = [{ message: "An error occured" }];
282+
const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
283+
const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
284+
const gitlab = authenticate(env)
285+
.get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`)
286+
.reply(200, [
287+
{
288+
id: 1,
289+
iid: 1,
290+
project_id: 1,
291+
web_url: "https://gitlab.com/test_user%2Ftest_repo/issues/1",
292+
title: "The automated release is failing 🚨",
293+
},
294+
{
295+
id: 2,
296+
iid: 2,
297+
project_id: 1,
298+
web_url: "https://gitlab.com/test_user%2Ftest_repo/issues/2",
299+
title: "API should implemented authentication",
300+
},
301+
]);
302+
303+
await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger });
304+
305+
t.true(gitlab.isDone());
306+
});
307+
308+
test.serial("Post new issue if none exists yet with disabled comment on existing issues", async (t) => {
309+
const owner = "test_user";
310+
const repo = "test_repo";
311+
const env = { GITLAB_TOKEN: "gitlab_token" };
312+
const pluginConfig = { failCommentCondition: "<% return !issue; %>" };
313+
const branch = { name: "main" };
314+
const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
315+
const errors = [{ message: "An error occured" }];
316+
const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
317+
const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
318+
const gitlab = authenticate(env)
319+
.get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`)
320+
.reply(200, [
321+
{
322+
id: 2,
323+
iid: 2,
324+
project_id: 1,
325+
web_url: "https://gitlab.com/test_user/test_repo/issues/2",
326+
title: "API should implemented authentication",
327+
},
328+
])
329+
.post(`/projects/${encodedRepoId}/issues`, {
330+
id: "test_user%2Ftest_repo",
331+
description: `## :rotating_light: The automated release from the \`main\` branch failed. :rotating_light:
332+
333+
I recommend you give this issue a high priority, so other packages depending on you can benefit from your bug fixes and new features again.
334+
335+
You can find below the list of errors reported by **semantic-release**. Each one of them has to be resolved in order to automatically publish your package. I'm sure you can fix this 💪.
336+
337+
Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.
338+
339+
Once all the errors are resolved, **semantic-release** will release your package the next time you push a commit to the \`main\` branch. You can also manually restart the failed CI job that runs **semantic-release**.
340+
341+
If you are not sure how to resolve this, here are some links that can help you:
342+
- [Usage documentation](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/README.md)
343+
- [Frequently Asked Questions](https://github.com/semantic-release/semantic-release/blob/master/docs/support/FAQ.md)
344+
- [Support channels](https://github.com/semantic-release/semantic-release#get-help)
345+
346+
If those don't help, or if this issue is reporting something you think isn't right, you can always ask the humans behind **[semantic-release](https://github.com/semantic-release/semantic-release/issues/new)**.
347+
348+
---
349+
350+
### An error occured
351+
352+
Unfortunately this error doesn't have any additional information.
353+
354+
---
355+
356+
Good luck with your project ✨
357+
358+
Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package: :rocket:`,
359+
labels: "semantic-release",
360+
title: "The automated release is failing 🚨",
361+
})
362+
.reply(200, { id: 3, web_url: "https://gitlab.com/test_user/test_repo/-/issues/3" });
363+
364+
await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger });
365+
366+
t.true(gitlab.isDone());
367+
t.deepEqual(t.context.log.args[0], [
368+
"Created issue #%d: %s.",
369+
3,
370+
"https://gitlab.com/test_user/test_repo/-/issues/3",
371+
]);
372+
});

test/resolve-config.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const defaultOptions = {
1313
successCommentCondition: undefined,
1414
failTitle: "The automated release is failing 🚨",
1515
failComment: undefined,
16+
failCommentCondition: undefined,
1617
labels: "semantic-release",
1718
assignee: undefined,
1819
proxy: {},

0 commit comments

Comments
 (0)