Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 5 additions & 6 deletions lib/success.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export default async function success(pluginConfig, context, { Octokit }) {
const shas = commits.map(({ hash }) => hash);

// Get associatedPRs
const associatedPRs = await inChunks(shas, 100, async (chunk) => {
const associatedPRs = await inChunks(shas, 25, async (chunk) => {
const responsePRs = [];
const { repository } = await octokit.graphql(
buildAssociatedPRsQuery(chunk),
Expand Down Expand Up @@ -333,8 +333,8 @@ export default async function success(pluginConfig, context, { Octokit }) {
}

/**
* In order to speed up a function call that handles a big array of items, we split up the
* array in chunks and call the function for each chunk in parallel. At the end we combine the
* Split a big array of items in chunks and call the function for each chunk serially, so that
* large releases do not send many heavy GraphQL queries at once. At the end we combine the
* results again.
*
* @template TItem
Expand All @@ -345,11 +345,10 @@ export default async function success(pluginConfig, context, { Octokit }) {
* @returns TCallbackResult
*/
async function inChunks(items, chunkSize, callback) {
const chunkCalls = [];
const results = [];
for (let i = 0; i < items.length; i += chunkSize) {
chunkCalls.push(callback(items.slice(i, i + chunkSize)));
results.push(await callback(items.slice(i, i + chunkSize)));
}
const results = await Promise.all(chunkCalls);

return results.flat();
}
Expand Down
77 changes: 77 additions & 0 deletions test/success.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1387,6 +1387,83 @@ test("Do not add comment and labels if no PR is associated with release commits"
t.true(fetch.done());
});

test("Query associated PRs in serial chunks of 25 commits", async (t) => {
const owner = "test_user";
const repo = "test_repo";
const env = { GITHUB_TOKEN: "github_token" };
const options = {
branch: "master",
repositoryUrl: `https://github.com/${owner}/${repo}.git`,
};
const commits = Array.from({ length: 30 }, (_, index) => ({
hash: `${index}`,
message: `Commit ${index} message`,
}));
const nextRelease = { version: "1.0.0" };
const releases = [
{ name: "GitHub release", url: "https://github.com/release" },
];
const chunkSizes = [];
let inFlight = 0;

const fetch = fetchMock
.sandbox()
.getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
full_name: `${owner}/${repo}`,
clone_url: `https://api.github.local/${owner}/${repo}.git`,
})
.post(
(url, { body }) =>
JSON.parse(body).query.includes("query getAssociatedPRs("),
async (url, { body }) => {
inFlight += 1;
t.is(inFlight, 1);
const shas = [...JSON.parse(body).query.matchAll(/oid: "(\d+)"/g)].map(
([, sha]) => sha,
);
chunkSizes.push(shas.length);
await new Promise((resolve) => setTimeout(resolve, 10));
inFlight -= 1;
const nodes = { pageInfo: { hasNextPage: false }, nodes: [] };
return {
data: {
repository: Object.fromEntries(
shas.map((sha) => [
`commit${sha}`,
{ oid: sha, associatedPullRequests: nodes },
]),
),
},
};
},
)
.postOnce(
(url, { body }) => JSON.parse(body).query.includes("query getSRIssues("),
{ data: { repository: { issues: { nodes: [] } } } },
);

await success(
{},
{
env,
options,
commits,
nextRelease,
releases,
logger: t.context.logger,
},
{
Octokit: TestOctokit.defaults((options) => ({
...options,
request: { ...options.request, fetch },
})),
},
);

t.deepEqual(chunkSizes, [25, 5]);
t.true(fetch.done());
});

test("Do not add comment and labels if no commits is found for release", async (t) => {
const owner = "test_user";
const repo = "test_repo";
Expand Down