Skip to content

Commit 3132c19

Browse files
authored
Merge pull request #730 from JonasSchubert/feat/allow-to-skip-success-comment-#480-#636
feat: allow to skip success and fail comments based on provided condition
2 parents 6b12097 + 4c651c5 commit 3132c19

7 files changed

+440
-63
lines changed

README.md

+57-11
Large diffs are not rendered by default.

lib/fail.js

+36-27
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@ 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 } };
2422

2523
if (failComment === false || failTitle === false) {
2624
logger.log("Skip issue creation.");
25+
logger.error(`Failure reporting should be disabled via 'failCommentCondition'.
26+
Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed in a future major version.`);
27+
} else if (failCommentCondition === false) {
28+
logger.log("Skip issue creation.");
2729
} else {
2830
const encodedFailTitle = encodeURIComponent(failTitle);
2931
const description = failComment ? template(failComment)({ branch, errors }) : getFailComment(branch, errors);
@@ -34,32 +36,39 @@ export default async (pluginConfig, context) => {
3436
const openFailTitleIssues = await got(openFailTitleIssueEndpoint, { ...apiOptions }).json();
3537
const existingIssue = openFailTitleIssues.find((openFailTitleIssue) => openFailTitleIssue.title === failTitle);
3638

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

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

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

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

lib/resolve-config.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,19 @@ import urlJoin from "url-join";
33
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
44

55
export default (
6-
{ gitlabUrl, gitlabApiPathPrefix, assets, milestones, successComment, failTitle, failComment, labels, assignee },
6+
{
7+
gitlabUrl,
8+
gitlabApiPathPrefix,
9+
assets,
10+
milestones,
11+
successComment,
12+
successCommentCondition,
13+
failTitle,
14+
failComment,
15+
failCommentCondition,
16+
labels,
17+
assignee,
18+
},
719
{
820
envCi: { service } = {},
921
env: {
@@ -45,9 +57,11 @@ export default (
4557
assets: assets ? castArray(assets) : assets,
4658
milestones: milestones ? castArray(milestones) : milestones,
4759
successComment,
60+
successCommentCondition,
4861
proxy: getProxyConfiguration(defaultedGitlabUrl, HTTP_PROXY, HTTPS_PROXY, NO_PROXY),
4962
failTitle: isNil(failTitle) ? "The automated release is failing 🚨" : failTitle,
5063
failComment,
64+
failCommentCondition,
5165
labels: isNil(labels) ? "semantic-release" : labels === false ? false : labels,
5266
assignee,
5367
};

lib/success.js

+45-24
Original file line numberDiff line numberDiff line change
@@ -15,43 +15,64 @@ export default async (pluginConfig, context) => {
1515
commits,
1616
releases,
1717
} = context;
18-
const { gitlabToken, gitlabUrl, gitlabApiUrl, successComment, proxy } = resolveConfig(pluginConfig, context);
18+
const { gitlabToken, gitlabUrl, gitlabApiUrl, successComment, successCommentCondition, proxy } = resolveConfig(
19+
pluginConfig,
20+
context
21+
);
1922
const repoId = getRepoId(context, gitlabUrl, repositoryUrl);
2023
const encodedRepoId = encodeURIComponent(repoId);
2124
const apiOptions = { headers: { "PRIVATE-TOKEN": gitlabToken } };
2225

2326
if (successComment === false) {
2427
logger.log("Skip commenting on issues and pull requests.");
28+
logger.error(`Issue and pull request comments should be disabled via 'successCommentCondition'.
29+
Using 'false' for 'successComment' is deprecated and will be removed in a future major version.`);
30+
} else if (successCommentCondition === false) {
31+
logger.log("Skip commenting on issues and pull requests.");
2532
} else {
2633
const releaseInfos = releases.filter((release) => Boolean(release.name));
2734
try {
2835
const postCommentToIssue = (issue) => {
29-
const issueNotesEndpoint = urlJoin(gitlabApiUrl, `/projects/${issue.project_id}/issues/${issue.iid}/notes`);
30-
debug("Posting issue note to %s", issueNotesEndpoint);
31-
const body = successComment
32-
? template(successComment)({ ...context, issue, mergeRequest: false })
33-
: getSuccessComment(issue, releaseInfos, nextRelease);
34-
return got.post(issueNotesEndpoint, {
35-
...apiOptions,
36-
...proxy,
37-
json: { body },
38-
});
36+
const canCommentOnIssue = successCommentCondition
37+
? template(successCommentCondition)({ ...context, issue, mergeRequest: false })
38+
: true;
39+
if (canCommentOnIssue) {
40+
const issueNotesEndpoint = urlJoin(gitlabApiUrl, `/projects/${issue.project_id}/issues/${issue.iid}/notes`);
41+
debug("Posting issue note to %s", issueNotesEndpoint);
42+
const body = successComment
43+
? template(successComment)({ ...context, issue, mergeRequest: false })
44+
: getSuccessComment(issue, releaseInfos, nextRelease);
45+
return got.post(issueNotesEndpoint, {
46+
...apiOptions,
47+
...proxy,
48+
json: { body },
49+
});
50+
} else {
51+
logger.log("Skip commenting on issue #%d.", issue.id);
52+
}
3953
};
4054

4155
const postCommentToMergeRequest = (mergeRequest) => {
42-
const mergeRequestNotesEndpoint = urlJoin(
43-
gitlabApiUrl,
44-
`/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/notes`
45-
);
46-
debug("Posting MR note to %s", mergeRequestNotesEndpoint);
47-
const body = successComment
48-
? template(successComment)({ ...context, issue: false, mergeRequest })
49-
: getSuccessComment({ isMergeRequest: true, ...mergeRequest }, releaseInfos, nextRelease);
50-
return got.post(mergeRequestNotesEndpoint, {
51-
...apiOptions,
52-
...proxy,
53-
json: { body },
54-
});
56+
const canCommentOnMergeRequest = successCommentCondition
57+
? template(successCommentCondition)({ ...context, issue: false, mergeRequest })
58+
: true;
59+
if (canCommentOnMergeRequest) {
60+
const mergeRequestNotesEndpoint = urlJoin(
61+
gitlabApiUrl,
62+
`/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/notes`
63+
);
64+
debug("Posting MR note to %s", mergeRequestNotesEndpoint);
65+
const body = successComment
66+
? template(successComment)({ ...context, issue: false, mergeRequest })
67+
: getSuccessComment({ isMergeRequest: true, ...mergeRequest }, releaseInfos, nextRelease);
68+
return got.post(mergeRequestNotesEndpoint, {
69+
...apiOptions,
70+
...proxy,
71+
json: { body },
72+
});
73+
} else {
74+
logger.log("Skip commenting on merge request #%d.", mergeRequest.iid);
75+
}
5576
};
5677

5778
const getRelatedMergeRequests = async (commitHash) => {

test/fail.test.js

+118
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,121 @@ 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 = {
313+
failComment: `Error: Release for branch \${branch.name} failed with error: \${errors.map(error => error.message).join(';')}`,
314+
failCommentCondition: "<% return !issue; %>",
315+
};
316+
const branch = { name: "main" };
317+
const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
318+
const errors = [{ message: "An error occured" }];
319+
const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
320+
const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
321+
const gitlab = authenticate(env)
322+
.get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`)
323+
.reply(200, [
324+
{
325+
id: 2,
326+
iid: 2,
327+
project_id: 1,
328+
web_url: "https://gitlab.com/test_user/test_repo/issues/2",
329+
title: "API should implemented authentication",
330+
},
331+
])
332+
.post(`/projects/${encodedRepoId}/issues`, {
333+
id: "test_user%2Ftest_repo",
334+
description: `Error: Release for branch main failed with error: An error occured`,
335+
labels: "semantic-release",
336+
title: "The automated release is failing 🚨",
337+
})
338+
.reply(200, { id: 3, web_url: "https://gitlab.com/test_user/test_repo/-/issues/3" });
339+
340+
await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger });
341+
342+
t.true(gitlab.isDone());
343+
t.deepEqual(t.context.log.args[0], [
344+
"Created issue #%d: %s.",
345+
3,
346+
"https://gitlab.com/test_user/test_repo/-/issues/3",
347+
]);
348+
});
349+
350+
test.serial("Does not post comments when failCommentCondition is set to false", async (t) => {
351+
const owner = "test_user";
352+
const repo = "test_repo";
353+
const env = { GITLAB_TOKEN: "gitlab_token" };
354+
const pluginConfig = { failCommentCondition: false };
355+
const branch = { name: "main" };
356+
const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
357+
const errors = [{ message: "An error occured" }];
358+
const gitlab = authenticate(env);
359+
360+
await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger });
361+
362+
t.true(gitlab.isDone());
363+
});

test/resolve-config.test.js

+2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ const defaultOptions = {
1010
assets: undefined,
1111
milestones: undefined,
1212
successComment: undefined,
13+
successCommentCondition: undefined,
1314
failTitle: "The automated release is failing 🚨",
1415
failComment: undefined,
16+
failCommentCondition: undefined,
1517
labels: "semantic-release",
1618
assignee: undefined,
1719
proxy: {},

0 commit comments

Comments
 (0)