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
123 changes: 51 additions & 72 deletions lib/success.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { isNil, uniqBy, template, flatten, isEmpty, merge } from "lodash-es";
import {
isNil,
uniq,
uniqBy,
template,
flatten,
isEmpty,
merge,
} from "lodash-es";
import pFilter from "p-filter";
import AggregateError from "aggregate-error";
import issueParser from "issue-parser";
Expand Down Expand Up @@ -75,47 +83,41 @@ export default async function success(pluginConfig, context, { Octokit }) {
const releaseInfos = releases.filter((release) => Boolean(release.name));
const shas = commits.map(({ hash }) => hash);

// Get associatedPRs
const associatedPRs = await inChunks(shas, 100, async (chunk) => {
const responsePRs = [];
// Get associatedPRs in two steps. Many commits share the same PRs, so we
// first get only the PR numbers and then load the full fields once per PR.
// This keeps each GraphQL query below GitHub's timeout of about 10 seconds.
const associatedPRNumbers = await inChunks(shas, 50, async (chunk) => {
const { repository } = await octokit.graphql(
buildAssociatedPRsQuery(chunk),
buildAssociatedPRNumbersQuery(chunk),
{ owner, repo },
);
const responseAssociatedPRs = Object.values(repository).map(
(item) => item.associatedPullRequests,
);
for (const { nodes, pageInfo } of responseAssociatedPRs) {
if (nodes.length === 0) continue;

responsePRs.push(...buildIssuesOrPRsFromResponseNode(nodes));
if (pageInfo.hasNextPage) {
let cursor = pageInfo.endCursor;
let hasNextPage = true;
while (hasNextPage) {
const { repository } = await octokit.graphql(
loadSingleCommitAssociatedPRs,
{ owner, repo, sha: response.commit.oid, cursor },
);
const { associatedPullRequests } = repository.commit;
responsePRs.push(
...buildIssuesOrPRsFromResponseNode(
associatedPullRequests.nodes,
"PR",
),
);
if (associatedPullRequests.pageInfo.hasNextPage) {
cursor = associatedPullRequests.pageInfo.endCursor;
} else {
hasNextPage = false;
}
}
const numbers = [];
for (const commit of Object.values(repository)) {
let { nodes, pageInfo } = commit.associatedPullRequests;
numbers.push(...nodes.map(({ number }) => number));
while (pageInfo.hasNextPage) {
const { repository } = await octokit.graphql(
loadSingleCommitAssociatedPRNumbers,
{ owner, repo, sha: commit.oid, cursor: pageInfo.endCursor },
);
({ nodes, pageInfo } = repository.commit.associatedPullRequests);
numbers.push(...nodes.map(({ number }) => number));
}
}
return responsePRs;
return numbers;
});

const uniqueAssociatedPRs = uniqBy(flatten(associatedPRs), "number");
const uniqueAssociatedPRs = await inChunks(
uniq(associatedPRNumbers),
10,
async (chunk) => {
const { repository } = await octokit.graphql(
buildRelatedIssuesQuery(chunk),
{ owner, repo },
);
return buildIssuesOrPRsFromResponseNode(Object.values(repository));
},
);

const prs = await pFilter(uniqueAssociatedPRs, async ({ number }) => {
const commits = await octokit.paginate(
Expand Down Expand Up @@ -172,7 +174,7 @@ export default async function success(pluginConfig, context, { Octokit }) {
const uniqueParsedIssues = uniqBy(flatten(parsedIssues), "number");

// Get relatedIssues (or relatedPRs i.e. Issues/PRs that are closed by an associatedPR)
issues = await inChunks(uniqueParsedIssues, 100, async (chunk) => {
issues = await inChunks(uniqueParsedIssues, 10, async (chunk) => {
const { repository } = await octokit.graphql(
buildRelatedIssuesQuery(chunk.map((issue) => issue.number)),
{ owner, repo },
Expand Down Expand Up @@ -333,9 +335,11 @@ 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
* results again.
* Split a big array of items in chunks and call the function for each chunk, one after
* another. At the end we combine the results again.
*
* The chunks do not run in parallel: many heavy GraphQL queries at the same time can make
* GitHub fail with timeouts or 502 errors for large releases.
*
* @template TItem
* @template TCallbackResult
Expand All @@ -345,11 +349,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 Expand Up @@ -447,11 +450,11 @@ function buildRelatedIssuesQuery(numbers) {
}

/**
* Builds GraphQL query for fetching associated PRs to a list of commit hash (sha)
* Builds GraphQL query for fetching the numbers of PRs associated to a list of commit hash (sha)
* @param {Array<string>} shas
* @returns {string}
*/
function buildAssociatedPRsQuery(shas) {
function buildAssociatedPRNumbersQuery(shas) {
return `#graphql
query getAssociatedPRs($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
Expand All @@ -466,19 +469,7 @@ function buildAssociatedPRsQuery(shas) {
hasNextPage
}
nodes {
${baseFields}
mergeable
changedFiles
mergedAt
isDraft
mergedBy {
login
avatarUrl
url
}
commits {
totalCount
}
number
}
}
}
Expand All @@ -491,9 +482,9 @@ function buildAssociatedPRsQuery(shas) {
}

/**
* GraphQL Query to fetch additional associatedPR for commits that has more than 100 associatedPRs
* GraphQL Query to fetch additional associatedPR numbers for commits that has more than 100 associatedPRs
*/
const loadSingleCommitAssociatedPRs = `#graphql
const loadSingleCommitAssociatedPRNumbers = `#graphql
query getCommitAssociatedPRs($owner: String!, $repo: String!, $sha: String!, $cursor: String) {
repository(owner: $owner, name: $repo) {
commit: object(oid: $sha) {
Expand All @@ -505,19 +496,7 @@ const loadSingleCommitAssociatedPRs = `#graphql
hasNextPage
}
nodes {
${baseFields}
mergeable
changedFiles
mergedAt
isDraft
mergedBy {
login
avatarUrl
url
}
commits {
totalCount
}
number
}
}
}
Expand Down
Loading