Skip to content

Commit

Permalink
feat(fail-comment): allow to skip based on provided condition #480 #636
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasSchubert committed Jun 20, 2024
1 parent c402511 commit 2a6eaf9
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 27 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ If you need to bypass the proxy for some hosts, configure the `NO_PROXY` environ
| `successCommentCondition` | Use this as condition, when to comment on issues or merge requests. See [successCommentCondition](#successCommentCondition). | - |
| `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. |
| `failTitle` | The title of the issue created when a release fails. | `The automated release is failing 🚨` |
| `failCommentCondition` | Use this as condition, when to comment on or create an issues in case of failures. See [failCommentCondition](#failCommentCondition). | - |
| `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` |
| `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. | - |

Expand Down Expand Up @@ -188,6 +189,27 @@ The `failComment` `This release from branch ${branch.name} had failed due to the
> - Error message 1
> - Error message 2
#### failCommentCondition

The fail comment condition 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 - only available if an open issue exists. |

##### failCommentCondition example

- do no create any comments at all: `"<% return false; %>"`
- to only comment on main branch: `"<% return branch.name === 'main' %>"`
- you can use labels to filter issues, i.e. to not comment if the issue is labeled with `wip`: `"<% return !issue.labels?.includes('wip') %>"`

> 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
## Compatibility

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.
Expand Down
59 changes: 32 additions & 27 deletions lib/fail.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ export default async (pluginConfig, context) => {
errors,
logger,
} = context;
const { gitlabToken, gitlabUrl, gitlabApiUrl, failComment, failTitle, labels, assignee } = resolveConfig(
pluginConfig,
context
);
const { gitlabToken, gitlabUrl, gitlabApiUrl, failComment, failTitle, failCommentCondition, labels, assignee } =
resolveConfig(pluginConfig, context);
const repoId = getRepoId(context, gitlabUrl, repositoryUrl);
const encodedRepoId = encodeURIComponent(repoId);
const apiOptions = { headers: { "PRIVATE-TOKEN": gitlabToken } };
Expand All @@ -36,32 +34,39 @@ Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed
const openFailTitleIssues = await got(openFailTitleIssueEndpoint, { ...apiOptions }).json();
const existingIssue = openFailTitleIssues.find((openFailTitleIssue) => openFailTitleIssue.title === failTitle);

if (existingIssue) {
debug("comment on issue: %O", existingIssue);
const canCommentOnOrCreateIssue = failCommentCondition
? template(failCommentCondition)({ ...context, issue: existingIssue })
: true;
if (canCommentOnOrCreateIssue) {
if (existingIssue) {
debug("comment on issue: %O", existingIssue);

const issueNotesEndpoint = urlJoin(
gitlabApiUrl,
`/projects/${existingIssue.project_id}/issues/${existingIssue.iid}/notes`
);
await got.post(issueNotesEndpoint, {
...apiOptions,
json: { body: description },
});
const issueNotesEndpoint = urlJoin(
gitlabApiUrl,
`/projects/${existingIssue.project_id}/issues/${existingIssue.iid}/notes`
);
await got.post(issueNotesEndpoint, {
...apiOptions,
json: { body: description },
});

const { id, web_url } = existingIssue;
logger.log("Commented on issue #%d: %s.", id, web_url);
} else {
const newIssue = { id: encodedRepoId, description, labels, title: failTitle, assignee_id: assignee };
debug("create issue: %O", newIssue);
const { id, web_url } = existingIssue;
logger.log("Commented on issue #%d: %s.", id, web_url);
} else {
const newIssue = { id: encodedRepoId, description, labels, title: failTitle, assignee_id: assignee };
debug("create issue: %O", newIssue);

/* eslint camelcase: off */
const { id, web_url } = await got
.post(issuesEndpoint, {
...apiOptions,
json: newIssue,
})
.json();
logger.log("Created issue #%d: %s.", id, web_url);
/* eslint camelcase: off */
const { id, web_url } = await got
.post(issuesEndpoint, {
...apiOptions,
json: newIssue,
})
.json();
logger.log("Created issue #%d: %s.", id, web_url);
}
} else {
logger.log("Skip commenting on or creating an issue.");
}
}
};
2 changes: 2 additions & 0 deletions lib/resolve-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default (
successCommentCondition,
failTitle,
failComment,
failCommentCondition,
labels,
assignee,
},
Expand Down Expand Up @@ -60,6 +61,7 @@ export default (
proxy: getProxyConfiguration(defaultedGitlabUrl, HTTP_PROXY, HTTPS_PROXY, NO_PROXY),
failTitle: isNil(failTitle) ? "The automated release is failing 🚨" : failTitle,
failComment,
failCommentCondition,
labels: isNil(labels) ? "semantic-release" : labels === false ? false : labels,
assignee,
};
Expand Down
127 changes: 127 additions & 0 deletions test/fail.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,130 @@ test.serial("Does not post comments when failComment is set to false", async (t)

t.true(gitlab.isDone());
});

test.serial("Does not post comments when failCommentCondition disables it", async (t) => {
const owner = "test_user";
const repo = "test_repo";
const env = { GITLAB_TOKEN: "gitlab_token" };
const pluginConfig = { failCommentCondition: "<% return false; %>" };
const branch = { name: "main" };
const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
const errors = [{ message: "An error occured" }];
const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
const gitlab = authenticate(env)
.get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`)
.reply(200, [
{
id: 2,
iid: 2,
project_id: 1,
web_url: "https://gitlab.com/test_user/test_repo/issues/2",
title: "API should implemented authentication",
},
]);

await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger });

t.true(gitlab.isDone());
});

test.serial("Does not post comments on existing issues when failCommentCondition disables this", async (t) => {
const owner = "test_user";
const repo = "test_repo";
const env = { GITLAB_TOKEN: "gitlab_token" };
const pluginConfig = { failCommentCondition: "<% return !issue; %>" };
const branch = { name: "main" };
const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
const errors = [{ message: "An error occured" }];
const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
const gitlab = authenticate(env)
.get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`)
.reply(200, [
{
id: 1,
iid: 1,
project_id: 1,
web_url: "https://gitlab.com/test_user%2Ftest_repo/issues/1",
title: "The automated release is failing 🚨",
},
{
id: 2,
iid: 2,
project_id: 1,
web_url: "https://gitlab.com/test_user%2Ftest_repo/issues/2",
title: "API should implemented authentication",
},
]);

await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger });

t.true(gitlab.isDone());
});

test.serial("Post new issue if none exists yet with disabled comment on existing issues", async (t) => {
const owner = "test_user";
const repo = "test_repo";
const env = { GITLAB_TOKEN: "gitlab_token" };
const pluginConfig = { failCommentCondition: "<% return !issue; %>" };
const branch = { name: "main" };
const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
const errors = [{ message: "An error occured" }];
const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
const gitlab = authenticate(env)
.get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`)
.reply(200, [
{
id: 2,
iid: 2,
project_id: 1,
web_url: "https://gitlab.com/test_user/test_repo/issues/2",
title: "API should implemented authentication",
},
])
.post(`/projects/${encodedRepoId}/issues`, {
id: "test_user%2Ftest_repo",
description: `## :rotating_light: The automated release from the \`main\` branch failed. :rotating_light:
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.
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 💪.
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.
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**.
If you are not sure how to resolve this, here are some links that can help you:
- [Usage documentation](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/README.md)
- [Frequently Asked Questions](https://github.com/semantic-release/semantic-release/blob/master/docs/support/FAQ.md)
- [Support channels](https://github.com/semantic-release/semantic-release#get-help)
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)**.
---
### An error occured
Unfortunately this error doesn't have any additional information.
---
Good luck with your project ✨
Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package: :rocket:`,
labels: "semantic-release",
title: "The automated release is failing 🚨",
})
.reply(200, { id: 3, web_url: "https://gitlab.com/test_user/test_repo/-/issues/3" });

await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger });

t.true(gitlab.isDone());
t.deepEqual(t.context.log.args[0], [
"Created issue #%d: %s.",
3,
"https://gitlab.com/test_user/test_repo/-/issues/3",
]);
});
1 change: 1 addition & 0 deletions test/resolve-config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const defaultOptions = {
successCommentCondition: undefined,
failTitle: "The automated release is failing 🚨",
failComment: undefined,
failCommentCondition: undefined,
labels: "semantic-release",
assignee: undefined,
proxy: {},
Expand Down

0 comments on commit 2a6eaf9

Please sign in to comment.