Skip to content

Commit

Permalink
fix: replace searchAPI usage with GraphQL in findSRIssue util (#…
Browse files Browse the repository at this point in the history
…907)

* feat: add graphql query loader `loadGetSRIssuesQuery` to fetch SRIssues

* chore: add new `RELEASE_FAIL_LABEL` constant

* feat: integrate issues fetch with graphql

* feat: add fallback to searchAPI for backward compatibility

* feat: integrated config label in SRIssues search

* fix: error `getSRIssue` graphql query label param type

* fix: undefined `data` property destructed from graphql reponse

* refactor: modified wrong `body` property in query

* refactor: remove conditions from searchAPI fallback logic

* refactor: replace `getSRIssues` graphql query `label` param with `filter`

* feat: implement unique issue sorting to address fallback `backwardIssues` conflict

* feat: modify `findSRIssue` integration in `success` script; add `logger` to its params;

* feat: integrate opinionated `RELEASE_FAIL_LABEL` into `fail` script

* chore: Questions and lint fixes

* refactor: modified `findSRIssues` integration in `fail` script

* test: fixed `findSRIssue` units test

* test: fixed `fail` unit tests

* test: fix integrations test

* test: fixed `success` unit tests

* test: `Verify, release and notify success` fix attempt

* test: addressed `"Verify, release and notify success"` case in `integrations`

* test: fix `success` units

* test: fix `fail` units

* refactor: remove error object from searchAPI fallback error handle

* test: add new case `"Handle error in searchAPI fallback"`

* Revert "refactor: remove conditions from searchAPI fallback logic"

This reverts commit a478a2b.

* modified `RELEASE_FAIL_LABEL` value to `semantic-release`

* test: fix cases for conditional `searchAPI` fallback consumption

* Update lib/resolve-config.js
  • Loading branch information
babblebey committed Sep 20, 2024
1 parent e57dc0c commit 7fb46a3
Show file tree
Hide file tree
Showing 8 changed files with 998 additions and 303 deletions.
2 changes: 2 additions & 0 deletions lib/definitions/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const ISSUE_ID = "<!-- semantic-release:github -->";

export const RELEASE_NAME = "GitHub release";

export const RELEASE_FAIL_LABEL = "semantic-release";
13 changes: 10 additions & 3 deletions lib/fail.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { template } from "lodash-es";
import debugFactory from "debug";

import parseGithubUrl from "./parse-github-url.js";
import { ISSUE_ID } from "./definitions/constants.js";
import { ISSUE_ID, RELEASE_FAIL_LABEL } from "./definitions/constants.js";
import resolveConfig from "./resolve-config.js";
import { toOctokitOptions } from "./octokit.js";
import findSRIssues from "./find-sr-issues.js";
Expand Down Expand Up @@ -57,7 +57,14 @@ export default async function fail(pluginConfig, context, { Octokit }) {
const body = failComment
? template(failComment)({ branch, errors })
: getFailComment(branch, errors);
const [srIssue] = await findSRIssues(octokit, failTitle, owner, repo);
const [srIssue] = await findSRIssues(
octokit,
logger,
failTitle,
labels,
owner,
repo,
);

const canCommentOnOrCreateIssue = failCommentCondition
? template(failCommentCondition)({ ...context, issue: srIssue })
Expand Down Expand Up @@ -85,7 +92,7 @@ export default async function fail(pluginConfig, context, { Octokit }) {
repo,
title: failTitle,
body: `${body}\n\n${ISSUE_ID}`,
labels: labels || [],
labels: (labels || []).concat([RELEASE_FAIL_LABEL]),
assignees,
};
debug("create issue: %O", newIssue);
Expand Down
64 changes: 58 additions & 6 deletions lib/find-sr-issues.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,63 @@
import { ISSUE_ID } from "./definitions/constants.js";
import { uniqBy } from "lodash-es";
import { ISSUE_ID, RELEASE_FAIL_LABEL } from "./definitions/constants.js";

export default async (octokit, logger, title, labels, owner, repo) => {
let issues = [];

export default async (octokit, title, owner, repo) => {
const {
data: { items: issues },
} = await octokit.request("GET /search/issues", {
q: `in:title+repo:${owner}/${repo}+type:issue+state:open+${title}`,
repository: {
issues: { nodes: issueNodes },
},
} = await octokit.graphql(loadGetSRIssuesQuery, {
owner,
repo,
filter: {
labels: (labels || []).concat([RELEASE_FAIL_LABEL]),
},
});

return issues.filter((issue) => issue.body && issue.body.includes(ISSUE_ID));
issues.push(...issueNodes);

/**
* BACKWARD COMPATIBILITY: Fallback to the search API if the issue was not found in the GraphQL response.
* This fallback will be removed in a future release
*/
if (issueNodes.length === 0) {
try {
const {
data: { items: backwardIssues },
} = await octokit.request("GET /search/issues", {
q: `in:title+repo:${owner}/${repo}+type:issue+state:open+${title}`,
});
issues.push(...backwardIssues);
} catch (error) {
logger.log(
"An error occured fetching issue via fallback (with GH SearchAPI)",
);
}
}

const uniqueSRIssues = uniqBy(
issues.filter((issue) => issue.body && issue.body.includes(ISSUE_ID)),
"number",
);

return uniqueSRIssues;
};

/**
* GraphQL Query to et the semantic-release issues for a repository.
*/
const loadGetSRIssuesQuery = `#graphql
query getSRIssues($owner: String!, $repo: String!, $filter: IssueFilters) {
repository(owner: $owner, name: $repo) {
issues(first: 100, states: OPEN, filterBy: $filter) {
nodes {
number
title
body
}
}
}
}
`;
10 changes: 9 additions & 1 deletion lib/success.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default async function success(pluginConfig, context, { Octokit }) {
githubApiPathPrefix,
githubApiUrl,
proxy,
labels,
successComment,
successCommentCondition,
failTitle,
Expand Down Expand Up @@ -266,7 +267,14 @@ export default async function success(pluginConfig, context, { Octokit }) {
if (failComment === false || failTitle === false) {
logger.log("Skip closing issue.");
} else {
const srIssues = await findSRIssues(octokit, failTitle, owner, repo);
const srIssues = await findSRIssues(
octokit,
logger,
failTitle,
labels,
owner,
repo,
);

debug("found semantic-release issues: %O", srIssues);

Expand Down
83 changes: 63 additions & 20 deletions test/fail.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import sinon from "sinon";
import test from "ava";
import fetchMock from "fetch-mock";

import { ISSUE_ID } from "../lib/definitions/constants.js";
import { ISSUE_ID, RELEASE_FAIL_LABEL } from "../lib/definitions/constants.js";
import { TestOctokit } from "./helpers/test-octokit.js";

/* eslint camelcase: ["error", {properties: "never"}] */
Expand Down Expand Up @@ -36,6 +36,13 @@ test("Open a new issue with the list of errors", async (t) => {
.getOnce("https://api.github.local/repos/test_user/test_repo", {
full_name: `${redirectedOwner}/${redirectedRepo}`,
})
.postOnce("https://api.github.local/graphql", {
data: {
repository: {
issues: { nodes: [] },
},
},
})
.getOnce(
`https://api.github.local/search/issues?q=${encodeURIComponent(
"in:title",
Expand All @@ -59,7 +66,7 @@ test("Open a new issue with the list of errors", async (t) => {
data.body,
/---\n\n### Error message 1\n\nError 1 details\n\n---\n\n### Error message 2\n\nError 2 details\n\n---\n\n### Error message 3\n\nError 3 details\n\n---/,
);
t.deepEqual(data.labels, ["semantic-release"]);
t.deepEqual(data.labels, ["semantic-release", RELEASE_FAIL_LABEL]);
return true;
},
{
Expand Down Expand Up @@ -117,6 +124,13 @@ test("Open a new issue with the list of errors and custom title and comment", as
full_name: `${owner}/${repo}`,
clone_url: `https://api.github.local/${owner}/${repo}.git`,
})
.postOnce("https://api.github.local/graphql", {
data: {
repository: {
issues: { nodes: [] },
},
},
})
.getOnce(
`https://api.github.local/search/issues?q=${encodeURIComponent(
"in:title",
Expand All @@ -132,7 +146,7 @@ test("Open a new issue with the list of errors and custom title and comment", as
body: {
title: failTitle,
body: `branch master Error message 1 Error message 2 Error message 3\n\n${ISSUE_ID}`,
labels: ["semantic-release"],
labels: ["semantic-release", RELEASE_FAIL_LABEL],
},
},
);
Expand Down Expand Up @@ -185,6 +199,13 @@ test("Open a new issue with assignees and the list of errors", async (t) => {
full_name: `${owner}/${repo}`,
clone_url: `https://api.github.local/${owner}/${repo}.git`,
})
.postOnce("https://api.github.local/graphql", {
data: {
repository: {
issues: { nodes: [] },
},
},
})
.getOnce(
`https://api.github.local/search/issues?q=${encodeURIComponent(
"in:title",
Expand All @@ -203,7 +224,7 @@ test("Open a new issue with assignees and the list of errors", async (t) => {
data.body,
/---\n\n### Error message 1\n\nError 1 details\n\n---\n\n### Error message 2\n\nError 2 details\n\n---/,
);
t.deepEqual(data.labels, ["semantic-release"]);
t.deepEqual(data.labels, ["semantic-release", RELEASE_FAIL_LABEL]);
t.deepEqual(data.assignees, ["user1", "user2"]);
return true;
},
Expand Down Expand Up @@ -258,6 +279,13 @@ test("Open a new issue without labels and the list of errors", async (t) => {
full_name: `${owner}/${repo}`,
clone_url: `https://api.github.local/${owner}/${repo}.git`,
})
.postOnce("https://api.github.local/graphql", {
data: {
repository: {
issues: { nodes: [] },
},
},
})
.getOnce(
`https://api.github.local/search/issues?q=${encodeURIComponent(
"in:title",
Expand All @@ -276,7 +304,7 @@ test("Open a new issue without labels and the list of errors", async (t) => {
data.body,
/---\n\n### Error message 1\n\nError 1 details\n\n---\n\n### Error message 2\n\nError 2 details\n\n---/,
);
t.deepEqual(data.labels, []);
t.deepEqual(data.labels, [RELEASE_FAIL_LABEL]);
return true;
},
{ html_url: "https://github.com/issues/1", number: 1 },
Expand Down Expand Up @@ -335,14 +363,13 @@ test("Update the first existing issue with the list of errors", async (t) => {
full_name: `${owner}/${repo}`,
clone_url: `https://api.github.local/${owner}/${repo}.git`,
})
.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 },
)
.postOnce("https://api.github.local/graphql", {
data: {
repository: {
issues: { nodes: issues },
},
},
})
.postOnce(
(url, { body }) => {
t.is(
Expand Down Expand Up @@ -501,13 +528,17 @@ test('Does not post comments on existing issues when "failCommentCondition" is "
.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 },
.postOnce(
(url, { body }) =>
url === "https://api.github.local/graphql" &&
JSON.parse(body).query.includes("query getSRIssues("),
{
data: {
repository: {
issues: { nodes: issues },
},
},
},
);

await fail(
Expand Down Expand Up @@ -551,6 +582,18 @@ test(`Post new issue if none exists yet, but don't comment on existing issues wh
.getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
full_name: `${owner}/${repo}`,
})
.postOnce(
(url, { body }) =>
url === "https://api.github.local/graphql" &&
JSON.parse(body).query.includes("query getSRIssues("),
{
data: {
repository: {
issues: { nodes: [] },
},
},
},
)
.getOnce(
`https://api.github.local/search/issues?q=${encodeURIComponent(
"in:title",
Expand Down
Loading

0 comments on commit 7fb46a3

Please sign in to comment.