Skip to content

Commit 7fb46a3

Browse files
authored
fix: replace searchAPI usage with GraphQL in findSRIssue util (#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
1 parent e57dc0c commit 7fb46a3

File tree

8 files changed

+998
-303
lines changed

8 files changed

+998
-303
lines changed

lib/definitions/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export const ISSUE_ID = "<!-- semantic-release:github -->";
22

33
export const RELEASE_NAME = "GitHub release";
4+
5+
export const RELEASE_FAIL_LABEL = "semantic-release";

lib/fail.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { template } from "lodash-es";
22
import debugFactory from "debug";
33

44
import parseGithubUrl from "./parse-github-url.js";
5-
import { ISSUE_ID } from "./definitions/constants.js";
5+
import { ISSUE_ID, RELEASE_FAIL_LABEL } from "./definitions/constants.js";
66
import resolveConfig from "./resolve-config.js";
77
import { toOctokitOptions } from "./octokit.js";
88
import findSRIssues from "./find-sr-issues.js";
@@ -57,7 +57,14 @@ export default async function fail(pluginConfig, context, { Octokit }) {
5757
const body = failComment
5858
? template(failComment)({ branch, errors })
5959
: getFailComment(branch, errors);
60-
const [srIssue] = await findSRIssues(octokit, failTitle, owner, repo);
60+
const [srIssue] = await findSRIssues(
61+
octokit,
62+
logger,
63+
failTitle,
64+
labels,
65+
owner,
66+
repo,
67+
);
6168

6269
const canCommentOnOrCreateIssue = failCommentCondition
6370
? template(failCommentCondition)({ ...context, issue: srIssue })
@@ -85,7 +92,7 @@ export default async function fail(pluginConfig, context, { Octokit }) {
8592
repo,
8693
title: failTitle,
8794
body: `${body}\n\n${ISSUE_ID}`,
88-
labels: labels || [],
95+
labels: (labels || []).concat([RELEASE_FAIL_LABEL]),
8996
assignees,
9097
};
9198
debug("create issue: %O", newIssue);

lib/find-sr-issues.js

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,63 @@
1-
import { ISSUE_ID } from "./definitions/constants.js";
1+
import { uniqBy } from "lodash-es";
2+
import { ISSUE_ID, RELEASE_FAIL_LABEL } from "./definitions/constants.js";
3+
4+
export default async (octokit, logger, title, labels, owner, repo) => {
5+
let issues = [];
26

3-
export default async (octokit, title, owner, repo) => {
47
const {
5-
data: { items: issues },
6-
} = await octokit.request("GET /search/issues", {
7-
q: `in:title+repo:${owner}/${repo}+type:issue+state:open+${title}`,
8+
repository: {
9+
issues: { nodes: issueNodes },
10+
},
11+
} = await octokit.graphql(loadGetSRIssuesQuery, {
12+
owner,
13+
repo,
14+
filter: {
15+
labels: (labels || []).concat([RELEASE_FAIL_LABEL]),
16+
},
817
});
918

10-
return issues.filter((issue) => issue.body && issue.body.includes(ISSUE_ID));
19+
issues.push(...issueNodes);
20+
21+
/**
22+
* BACKWARD COMPATIBILITY: Fallback to the search API if the issue was not found in the GraphQL response.
23+
* This fallback will be removed in a future release
24+
*/
25+
if (issueNodes.length === 0) {
26+
try {
27+
const {
28+
data: { items: backwardIssues },
29+
} = await octokit.request("GET /search/issues", {
30+
q: `in:title+repo:${owner}/${repo}+type:issue+state:open+${title}`,
31+
});
32+
issues.push(...backwardIssues);
33+
} catch (error) {
34+
logger.log(
35+
"An error occured fetching issue via fallback (with GH SearchAPI)",
36+
);
37+
}
38+
}
39+
40+
const uniqueSRIssues = uniqBy(
41+
issues.filter((issue) => issue.body && issue.body.includes(ISSUE_ID)),
42+
"number",
43+
);
44+
45+
return uniqueSRIssues;
1146
};
47+
48+
/**
49+
* GraphQL Query to et the semantic-release issues for a repository.
50+
*/
51+
const loadGetSRIssuesQuery = `#graphql
52+
query getSRIssues($owner: String!, $repo: String!, $filter: IssueFilters) {
53+
repository(owner: $owner, name: $repo) {
54+
issues(first: 100, states: OPEN, filterBy: $filter) {
55+
nodes {
56+
number
57+
title
58+
body
59+
}
60+
}
61+
}
62+
}
63+
`;

lib/success.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export default async function success(pluginConfig, context, { Octokit }) {
2828
githubApiPathPrefix,
2929
githubApiUrl,
3030
proxy,
31+
labels,
3132
successComment,
3233
successCommentCondition,
3334
failTitle,
@@ -266,7 +267,14 @@ export default async function success(pluginConfig, context, { Octokit }) {
266267
if (failComment === false || failTitle === false) {
267268
logger.log("Skip closing issue.");
268269
} else {
269-
const srIssues = await findSRIssues(octokit, failTitle, owner, repo);
270+
const srIssues = await findSRIssues(
271+
octokit,
272+
logger,
273+
failTitle,
274+
labels,
275+
owner,
276+
repo,
277+
);
270278

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

test/fail.test.js

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import sinon from "sinon";
33
import test from "ava";
44
import fetchMock from "fetch-mock";
55

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

99
/* eslint camelcase: ["error", {properties: "never"}] */
@@ -36,6 +36,13 @@ test("Open a new issue with the list of errors", async (t) => {
3636
.getOnce("https://api.github.local/repos/test_user/test_repo", {
3737
full_name: `${redirectedOwner}/${redirectedRepo}`,
3838
})
39+
.postOnce("https://api.github.local/graphql", {
40+
data: {
41+
repository: {
42+
issues: { nodes: [] },
43+
},
44+
},
45+
})
3946
.getOnce(
4047
`https://api.github.local/search/issues?q=${encodeURIComponent(
4148
"in:title",
@@ -59,7 +66,7 @@ test("Open a new issue with the list of errors", async (t) => {
5966
data.body,
6067
/---\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---/,
6168
);
62-
t.deepEqual(data.labels, ["semantic-release"]);
69+
t.deepEqual(data.labels, ["semantic-release", RELEASE_FAIL_LABEL]);
6370
return true;
6471
},
6572
{
@@ -117,6 +124,13 @@ test("Open a new issue with the list of errors and custom title and comment", as
117124
full_name: `${owner}/${repo}`,
118125
clone_url: `https://api.github.local/${owner}/${repo}.git`,
119126
})
127+
.postOnce("https://api.github.local/graphql", {
128+
data: {
129+
repository: {
130+
issues: { nodes: [] },
131+
},
132+
},
133+
})
120134
.getOnce(
121135
`https://api.github.local/search/issues?q=${encodeURIComponent(
122136
"in:title",
@@ -132,7 +146,7 @@ test("Open a new issue with the list of errors and custom title and comment", as
132146
body: {
133147
title: failTitle,
134148
body: `branch master Error message 1 Error message 2 Error message 3\n\n${ISSUE_ID}`,
135-
labels: ["semantic-release"],
149+
labels: ["semantic-release", RELEASE_FAIL_LABEL],
136150
},
137151
},
138152
);
@@ -185,6 +199,13 @@ test("Open a new issue with assignees and the list of errors", async (t) => {
185199
full_name: `${owner}/${repo}`,
186200
clone_url: `https://api.github.local/${owner}/${repo}.git`,
187201
})
202+
.postOnce("https://api.github.local/graphql", {
203+
data: {
204+
repository: {
205+
issues: { nodes: [] },
206+
},
207+
},
208+
})
188209
.getOnce(
189210
`https://api.github.local/search/issues?q=${encodeURIComponent(
190211
"in:title",
@@ -203,7 +224,7 @@ test("Open a new issue with assignees and the list of errors", async (t) => {
203224
data.body,
204225
/---\n\n### Error message 1\n\nError 1 details\n\n---\n\n### Error message 2\n\nError 2 details\n\n---/,
205226
);
206-
t.deepEqual(data.labels, ["semantic-release"]);
227+
t.deepEqual(data.labels, ["semantic-release", RELEASE_FAIL_LABEL]);
207228
t.deepEqual(data.assignees, ["user1", "user2"]);
208229
return true;
209230
},
@@ -258,6 +279,13 @@ test("Open a new issue without labels and the list of errors", async (t) => {
258279
full_name: `${owner}/${repo}`,
259280
clone_url: `https://api.github.local/${owner}/${repo}.git`,
260281
})
282+
.postOnce("https://api.github.local/graphql", {
283+
data: {
284+
repository: {
285+
issues: { nodes: [] },
286+
},
287+
},
288+
})
261289
.getOnce(
262290
`https://api.github.local/search/issues?q=${encodeURIComponent(
263291
"in:title",
@@ -276,7 +304,7 @@ test("Open a new issue without labels and the list of errors", async (t) => {
276304
data.body,
277305
/---\n\n### Error message 1\n\nError 1 details\n\n---\n\n### Error message 2\n\nError 2 details\n\n---/,
278306
);
279-
t.deepEqual(data.labels, []);
307+
t.deepEqual(data.labels, [RELEASE_FAIL_LABEL]);
280308
return true;
281309
},
282310
{ html_url: "https://github.com/issues/1", number: 1 },
@@ -335,14 +363,13 @@ test("Update the first existing issue with the list of errors", async (t) => {
335363
full_name: `${owner}/${repo}`,
336364
clone_url: `https://api.github.local/${owner}/${repo}.git`,
337365
})
338-
.getOnce(
339-
`https://api.github.local/search/issues?q=${encodeURIComponent(
340-
"in:title",
341-
)}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent(
342-
"type:issue",
343-
)}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`,
344-
{ items: issues },
345-
)
366+
.postOnce("https://api.github.local/graphql", {
367+
data: {
368+
repository: {
369+
issues: { nodes: issues },
370+
},
371+
},
372+
})
346373
.postOnce(
347374
(url, { body }) => {
348375
t.is(
@@ -501,13 +528,17 @@ test('Does not post comments on existing issues when "failCommentCondition" is "
501528
.getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
502529
full_name: `${owner}/${repo}`,
503530
})
504-
.getOnce(
505-
`https://api.github.local/search/issues?q=${encodeURIComponent(
506-
"in:title",
507-
)}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent(
508-
"type:issue",
509-
)}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`,
510-
{ items: issues },
531+
.postOnce(
532+
(url, { body }) =>
533+
url === "https://api.github.local/graphql" &&
534+
JSON.parse(body).query.includes("query getSRIssues("),
535+
{
536+
data: {
537+
repository: {
538+
issues: { nodes: issues },
539+
},
540+
},
541+
},
511542
);
512543

513544
await fail(
@@ -551,6 +582,18 @@ test(`Post new issue if none exists yet, but don't comment on existing issues wh
551582
.getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
552583
full_name: `${owner}/${repo}`,
553584
})
585+
.postOnce(
586+
(url, { body }) =>
587+
url === "https://api.github.local/graphql" &&
588+
JSON.parse(body).query.includes("query getSRIssues("),
589+
{
590+
data: {
591+
repository: {
592+
issues: { nodes: [] },
593+
},
594+
},
595+
},
596+
)
554597
.getOnce(
555598
`https://api.github.local/search/issues?q=${encodeURIComponent(
556599
"in:title",

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy