Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow conditional skip on success and fail comments #874

Merged
merged 79 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
304d023
feat: add `failCommentCondition` to `fail` script
babblebey Jul 10, 2024
120d391
feat: add `successCommentCondition` and `failCommentCondition` to `re…
babblebey Jul 10, 2024
ec8f4d6
feat: add `successCommentCondition` and `failCommentCondition` to `su…
babblebey Jul 10, 2024
508e066
fix(build): lint
babblebey Jul 10, 2024
f319896
test: add `fail` case `Does not post comments if "failCommentConditio…
babblebey Jul 12, 2024
5983b75
test: add `fail` case for `Does not post comments on existing issues …
babblebey Jul 12, 2024
da630b1
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Jul 12, 2024
a71ee8a
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Jul 14, 2024
8231ec0
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Jul 15, 2024
f516292
fix(build): lint
babblebey Jul 15, 2024
460d475
test(fail): add case for `Post new issue if none exists yet, but don'…
babblebey Jul 16, 2024
7d2b4de
test(success): add case `Does not comment on issues/PR if "successCom…
babblebey Jul 16, 2024
e55733b
Update test/fail.test.js
babblebey Jul 16, 2024
2156d92
test(success): add case for `Add comment and label to found issues/as…
babblebey Jul 18, 2024
6f46d09
nits
babblebey Jul 18, 2024
e32cc69
test(success): add case for `Does not comment/label found associatedP…
babblebey Jul 18, 2024
30b6f38
Merge branch 'feat/conditional-success-fail-comment-skip' of https://…
babblebey Jul 18, 2024
5d9e5d5
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Jul 18, 2024
81c9ff0
test(success): improve case `Does not comment/label found associatedP…
babblebey Jul 18, 2024
a82c2b8
doc: add documentation for `successCommentCondition` and `failComment…
babblebey Jul 18, 2024
219386c
Update lib/success.js
babblebey Jul 22, 2024
34ae9a9
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Jul 22, 2024
1cbf8c2
refactor: modify `failTitle`, `failComment` and `successComment` fals…
babblebey Jul 22, 2024
0d3014b
refator: implement early return in `fail` and `success` lifecyccle he…
babblebey Jul 22, 2024
ad0a03f
Update README.md
babblebey Jul 24, 2024
1faae48
remove `failCommentCondition` example wrong description
babblebey Jul 24, 2024
924ca55
build: fix lint
babblebey Jul 24, 2024
66c9f28
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Jul 25, 2024
a556072
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Jul 30, 2024
7328c8c
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Aug 5, 2024
cfc61eb
feat: add validators for `successCommentCondition` and `failCommentCo…
babblebey Aug 5, 2024
9591bd0
feat: add `buildAssociatedPRs` to create pr object in form of issue o…
babblebey Aug 6, 2024
1cbb41a
doc: update README.md
babblebey Aug 6, 2024
8cfa350
build: fix lint
babblebey Aug 6, 2024
6ee6c24
build: fix failing test `Add custom comment and labels`
babblebey Aug 6, 2024
f250e71
feat: request more field for `associatedPRs` via graphql and improve …
babblebey Aug 7, 2024
60a73d6
test: modify integration tests
babblebey Aug 7, 2024
4b689fe
test: modify `success` unit tests
babblebey Aug 7, 2024
ee733fb
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Aug 9, 2024
a798fc2
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Aug 12, 2024
09cfdac
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Aug 14, 2024
a9af537
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Aug 15, 2024
627b0d8
build: fix lint
babblebey Aug 15, 2024
863b4a6
build: fix failing tests
babblebey Aug 15, 2024
2d826fa
feat: add `__typename` to `issue.user` object
babblebey Aug 15, 2024
990ed77
test: add new case `Does not comment/label associatedPR created by "B…
babblebey Aug 15, 2024
9e19862
test: modify `pull_request` mock value to `boolean`
babblebey Aug 15, 2024
a052c49
chore(test): clean debug comments
babblebey Aug 15, 2024
260bdd6
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Aug 19, 2024
54dd036
feat: re-integrate `buildAssociatedPRs`
babblebey Aug 19, 2024
a6c8442
feat: re-introduced and modifed `loadSingleCommitAssociatedPRs`
babblebey Aug 19, 2024
e0e7ff1
refactor: introduce `parsedIssues` as returned value from `prs**.body…
babblebey Aug 19, 2024
c068bb3
feat: added `buildRelatedIssuesQuery` util graphql query builder
babblebey Aug 19, 2024
63a4bc1
feat: implement computation for `responseRelatedIssues`
babblebey Aug 19, 2024
229d4a8
fix: correct `number` arg type in `buildRelatedIssuesQuery`
babblebey Aug 19, 2024
43ca24f
refactor: extract common field accross graphql queries to `baseFields`
babblebey Aug 19, 2024
c4c561d
refactor: transform `buildAssociatedPRs` to `buildIssuesOrPRsFromResp…
babblebey Aug 19, 2024
88a7118
feat: integrate `buildIssuesOrPRsFromResponseNode`
babblebey Aug 19, 2024
1bc293c
feat: implement `issueOrPR` for correctly addressing issues and pr in…
babblebey Aug 19, 2024
e95a700
feat: implement improved chunk operation helper `inChunks` and integr…
babblebey Aug 19, 2024
2572def
build: fix lints
babblebey Aug 19, 2024
201a1bb
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Aug 21, 2024
d41a1df
test: update `integrations` test
babblebey Aug 21, 2024
6473eb9
test: address PR and Issue naming in logs in `success`
babblebey Aug 21, 2024
94f3975
refactor: why the `Promise.all()`? Removed it haha
babblebey Aug 22, 2024
e29b312
feat: set default `type` param in `buildIssuesOrPRsFromResponseNode`
babblebey Aug 26, 2024
474a28a
feat: address edge cases
babblebey Aug 26, 2024
cbd0128
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Aug 26, 2024
1ef47af
Merge branch 'feat/conditional-success-fail-comment-skip' of https://…
babblebey Aug 26, 2024
34795a5
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Aug 30, 2024
8ee3679
test: fixed matchers in graphql request in `success` units
babblebey Aug 30, 2024
f617843
build: lint
babblebey Aug 30, 2024
e336619
Merge branch 'feat/conditional-success-fail-comment-skip' of https://…
babblebey Aug 30, 2024
15c3089
docs: add ignore bots pr/issues example
babblebey Aug 30, 2024
f91e8e9
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Sep 2, 2024
ff17066
fix: user issue `number` over `id`
babblebey Sep 2, 2024
601303d
test: improve case `'Does not comment/label associatedPR and relatedI…
babblebey Sep 2, 2024
367047d
doc: modify `buildIssuesOrPRsFromResponseNode` documentation
babblebey Sep 2, 2024
0d3cd0f
Merge branch 'master' into feat/conditional-success-fail-comment-skip
babblebey Sep 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 44 additions & 25 deletions lib/fail.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ export default async function fail(pluginConfig, context, { Octokit }) {
githubApiPathPrefix,
githubApiUrl,
proxy,
failComment,
failTitle,
failComment,
failCommentCondition,
labels,
assignees,
} = resolveConfig(pluginConfig, context);

if (failComment === false || failTitle === false) {
babblebey marked this conversation as resolved.
Show resolved Hide resolved
logger.log("Skip issue creation.");
logger.error(`Failure reporting should be disabled via 'failCommentCondition'.
Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed in a future major version.`);
babblebey marked this conversation as resolved.
Show resolved Hide resolved
} else if (failCommentCondition === false) {
logger.log("Skip issue creation.");
} else {
babblebey marked this conversation as resolved.
Show resolved Hide resolved
const octokit = new Octokit(
toOctokitOptions({
Expand All @@ -52,31 +57,45 @@ export default async function fail(pluginConfig, context, { Octokit }) {
: getFailComment(branch, errors);
const [srIssue] = await findSRIssues(octokit, failTitle, owner, repo);

if (srIssue) {
logger.log("Found existing semantic-release issue #%d.", srIssue.number);
const comment = { owner, repo, issue_number: srIssue.number, body };
debug("create comment: %O", comment);
const {
data: { html_url: url },
} = await octokit.request(
"POST /repos/{owner}/{repo}/issues/{issue_number}/comments",
comment,
);
logger.log("Added comment to issue #%d: %s.", srIssue.number, url);
const canCommentOnOrCreateIssue = failCommentCondition
? template(failCommentCondition)({ ...context, issue: srIssue })
: true;

if (canCommentOnOrCreateIssue) {
babblebey marked this conversation as resolved.
Show resolved Hide resolved
if (srIssue) {
logger.log(
"Found existing semantic-release issue #%d.",
srIssue.number,
);
const comment = { owner, repo, issue_number: srIssue.number, body };
debug("create comment: %O", comment);
const {
data: { html_url: url },
} = await octokit.request(
"POST /repos/{owner}/{repo}/issues/{issue_number}/comments",
comment,
);
logger.log("Added comment to issue #%d: %s.", srIssue.number, url);
} else {
const newIssue = {
owner,
repo,
title: failTitle,
body: `${body}\n\n${ISSUE_ID}`,
labels: labels || [],
assignees,
};
debug("create issue: %O", newIssue);
const {
data: { html_url: url, number },
} = await octokit.request(
"POST /repos/{owner}/{repo}/issues",
newIssue,
);
logger.log("Created issue #%d: %s.", number, url);
}
} else {
const newIssue = {
owner,
repo,
title: failTitle,
body: `${body}\n\n${ISSUE_ID}`,
labels: labels || [],
assignees,
};
debug("create issue: %O", newIssue);
const {
data: { html_url: url, number },
} = await octokit.request("POST /repos/{owner}/{repo}/issues", newIssue);
logger.log("Created issue #%d: %s.", number, url);
logger.log("Skip commenting on or creating an issue.");
}
}
}
4 changes: 4 additions & 0 deletions lib/resolve-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ export default function resolveConfig(
proxy,
assets,
successComment,
successCommentCondition,
failTitle,
failComment,
failCommentCondition,
labels,
assignees,
releasedLabels,
Expand All @@ -30,10 +32,12 @@ export default function resolveConfig(
proxy: isNil(proxy) ? env.http_proxy || env.HTTP_PROXY || false : proxy,
assets: assets ? castArray(assets) : assets,
successComment,
successCommentCondition,
failTitle: isNil(failTitle)
? "The automated release is failing 🚨"
: failTitle,
failComment,
failCommentCondition,
labels: isNil(labels)
? ["semantic-release"]
: labels === false
Expand Down
106 changes: 60 additions & 46 deletions lib/success.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ export default async function success(pluginConfig, context, { Octokit }) {
githubApiUrl,
proxy,
successComment,
failComment,
successCommentCondition,
failTitle,
failComment,
failCommentCondition,
releasedLabels,
addReleases,
} = resolveConfig(pluginConfig, context);
Expand All @@ -56,6 +58,10 @@ export default async function success(pluginConfig, context, { Octokit }) {

if (successComment === false) {
logger.log("Skip commenting on issues and pull requests.");
logger.error(`Issue and pull request comments should be disabled via 'successCommentCondition'.
Using 'false' for 'successComment' is deprecated and will be removed in a future major version.`);
babblebey marked this conversation as resolved.
Show resolved Hide resolved
babblebey marked this conversation as resolved.
Show resolved Hide resolved
} else if (successCommentCondition === false) {
logger.log("Skip commenting on issues and pull requests.");
} else {
const parser = issueParser(
"github",
Expand Down Expand Up @@ -127,54 +133,62 @@ export default async function success(pluginConfig, context, { Octokit }) {

await Promise.all(
uniqBy([...prs, ...issues], "number").map(async (issue) => {
const body = successComment
? template(successComment)({ ...context, issue })
: getSuccessComment(issue, releaseInfos, nextRelease);
try {
const comment = { owner, repo, issue_number: issue.number, body };
debug("create comment: %O", comment);
const {
data: { html_url: url },
} = await octokit.request(
"POST /repos/{owner}/{repo}/issues/{issue_number}/comments",
comment,
);
logger.log("Added comment to issue #%d: %s", issue.number, url);
const canCommentOnIssuesAndPRs = successCommentCondition
? template(successCommentCondition)({ ...context, issue })
: true;

if (releasedLabels) {
const labels = releasedLabels.map((label) =>
template(label)(context),
);
await octokit.request(
"POST /repos/{owner}/{repo}/issues/{issue_number}/labels",
{
owner,
repo,
issue_number: issue.number,
data: labels,
},
if (canCommentOnIssuesAndPRs) {
const body = successComment
? template(successComment)({ ...context, issue })
: getSuccessComment(issue, releaseInfos, nextRelease);
try {
const comment = { owner, repo, issue_number: issue.number, body };
debug("create comment: %O", comment);
const {
data: { html_url: url },
} = await octokit.request(
"POST /repos/{owner}/{repo}/issues/{issue_number}/comments",
comment,
);
logger.log("Added labels %O to issue #%d", labels, issue.number);
}
} catch (error) {
if (error.status === 403) {
logger.error(
"Not allowed to add a comment to the issue #%d.",
issue.number,
);
} else if (error.status === 404) {
logger.error(
"Failed to add a comment to the issue #%d as it doesn't exist.",
issue.number,
);
} else {
errors.push(error);
logger.error(
"Failed to add a comment to the issue #%d.",
issue.number,
);
// Don't throw right away and continue to update other issues
logger.log("Added comment to issue #%d: %s", issue.number, url);

if (releasedLabels) {
const labels = releasedLabels.map((label) =>
template(label)(context),
);
await octokit.request(
"POST /repos/{owner}/{repo}/issues/{issue_number}/labels",
{
owner,
repo,
issue_number: issue.number,
data: labels,
},
);
logger.log("Added labels %O to issue #%d", labels, issue.number);
}
} catch (error) {
if (error.status === 403) {
logger.error(
"Not allowed to add a comment to the issue #%d.",
issue.number,
);
} else if (error.status === 404) {
logger.error(
"Failed to add a comment to the issue #%d as it doesn't exist.",
issue.number,
);
} else {
errors.push(error);
logger.error(
"Failed to add a comment to the issue #%d.",
issue.number,
);
// Don't throw right away and continue to update other issues
}
}
} else {
logger.log("Skip commenting on issue #%d.", issue.id);
}
}),
);
Expand Down
146 changes: 146 additions & 0 deletions test/fail.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,149 @@ test('Skip if "failTitle" is "false"', async (t) => {

t.true(t.context.log.calledWith("Skip issue creation."));
});

test('Does not post comments if "failCommentCondition" is "false"', async (t) => {
const owner = "test_user";
const repo = "test_repo";
const env = { GITHUB_TOKEN: "github_token" };
const pluginConfig = { failCommentCondition: false };
babblebey marked this conversation as resolved.
Show resolved Hide resolved
const options = { repositoryUrl: `https://github.com/${owner}/${repo}.git` };

await fail(
pluginConfig,
{
env,
options,
branch: { name: "master" },
logger: t.context.logger,
},
{
Octokit: TestOctokit.defaults((options) => ({
...options,
request: { ...options.request, fetch },
})),
},
);

t.true(t.context.log.calledWith("Skip issue creation."));
});

test('Does not post comments on existing issues when "failCommentCondition" is "false"', async (t) => {
const owner = "test_user";
const repo = "test_repo";
const failTitle = "The automated release is failing 🚨";
const env = { GITHUB_TOKEN: "github_token" };
const pluginConfig = { failCommentCondition: "<% return false; %>" };
const options = { repositoryUrl: `https://github.com/${owner}/${repo}.git` };
const errors = [
new SemanticReleaseError("Error message 1", "ERR1", "Error 1 details"),
new SemanticReleaseError("Error message 2", "ERR2", "Error 2 details"),
new SemanticReleaseError("Error message 3", "ERR3", "Error 3 details"),
];
const issues = [
{ number: 1, body: "Issue 1 body", title: failTitle },
{ number: 2, body: `Issue 2 body\n\n${ISSUE_ID}`, title: failTitle },
{ number: 3, body: `Issue 3 body\n\n${ISSUE_ID}`, title: failTitle },
];

const fetch = fetchMock
.sandbox()
.getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
full_name: `${owner}/${repo}`,
})
.getOnce(
`https://api.github.local/search/issues?q=${encodeURIComponent(
"in:title",
)}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent(
"type:issue",
)}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`,
{ items: issues },
);

await fail(
pluginConfig,
{
env,
options,
branch: { name: "master" },
errors,
logger: t.context.logger,
},
{
Octokit: TestOctokit.defaults((options) => ({
...options,
request: { ...options.request, fetch },
})),
},
);

t.true(fetch.done());
t.true(t.context.log.calledWith("Skip commenting on or creating an issue."));
});

test(`Post new issue if none exists yet, but don't comment on existing issues when "failCommentCondition" is disallows it`, async (t) => {
babblebey marked this conversation as resolved.
Show resolved Hide resolved
const owner = "test_user";
const repo = "test_repo";
const env = { GITHUB_TOKEN: "github_token" };
const errors = [{ message: "An error occured" }];
const failTitle = "The automated release is failing 🚨";
const pluginConfig = {
failTitle,
failComment: `Error: Release for branch \${branch.name} failed with error: \${errors.map(error => error.message).join(';')}`,
failCommentCondition: "<% return !issue; %>",
};
const options = {
repositoryUrl: `https://github.com/${owner}/${repo}.git`,
};

const fetch = fetchMock
.sandbox()
.getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
full_name: `${owner}/${repo}`,
})
.getOnce(
`https://api.github.local/search/issues?q=${encodeURIComponent(
"in:title",
)}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent(
"type:issue",
)}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`,
{ items: [] },
)
.postOnce(
(url, { body }) => {
t.is(url, `https://api.github.local/repos/${owner}/${repo}/issues`);
t.regex(
JSON.parse(body).body,
/Error: Release for branch master failed with error: An error occured\n\n<!-- semantic-release:github -->/,
);
return true;
},
{ html_url: "https://github.com/issues/2", number: 2 },
);

await fail(
pluginConfig,
{
env,
options,
branch: { name: "master" },
errors,
logger: t.context.logger,
},
{
Octokit: TestOctokit.defaults((options) => ({
...options,
request: { ...options.request, fetch },
})),
},
);

t.true(fetch.done());
t.true(
t.context.log.calledWith(
"Created issue #%d: %s.",
2,
"https://github.com/issues/2",
),
);
});