Skip to content

Instantly share code, notes, and snippets.

@mjroeleveld
Last active April 12, 2021 09:48
Show Gist options
  • Save mjroeleveld/d33cfc30bafaf8802596103cf19842d8 to your computer and use it in GitHub Desktop.
Save mjroeleveld/d33cfc30bafaf8802596103cf19842d8 to your computer and use it in GitHub Desktop.
Iterator that loops over Github GraphQL API result using cursor
import { getOctokit } from '@actions/github';
import { GraphQlQueryResponseData } from '@octokit/graphql';
const MAX_PAGE_SIZE = 100;
export interface IterableListSearch<Iterable> {
nodes: Iterable[];
pageInfo: PageInfo;
}
export interface IterableListQuery<Iterable> {
edges: Array<{
node: Iterable;
}>;
pageInfo: PageInfo;
}
export type IterableList<Iterable> =
| IterableListQuery<Iterable>
| IterableListSearch<Iterable>;
export async function* makeGraphqlIterator<IterableData>(
octokit: ReturnType<typeof getOctokit>,
query: string,
parameters: object,
extractListFunction: (
response: GraphQlQueryResponseData,
) => IterableList<IterableData> | undefined,
): AsyncGenerator<IterableData> {
let cursor: string | undefined = undefined;
let hasNextPage: boolean = true;
const { pageSize = MAX_PAGE_SIZE }: { pageSize?: number } = parameters;
while (hasNextPage) {
const response = await octokit.graphql<GraphQlQueryResponseData>(query, {
...parameters,
endCursor: cursor,
pageSize,
});
const list = extractListFunction(response);
if (list === undefined) {
return;
}
cursor = list.pageInfo.endCursor;
hasNextPage = list.pageInfo.hasNextPage;
let iterables: IterableData[] | undefined = undefined;
if ('edges' in list) {
iterables = list.edges.map(
(edge: { node: IterableData }): IterableData => edge.node,
);
} else if ('nodes' in list) {
iterables = list.nodes;
} else {
throw new Error('unexpected list format');
}
for (const iterable of iterables) {
yield iterable;
}
}
}
@mjroeleveld
Copy link
Author

mjroeleveld commented Apr 9, 2021

Use it like:

const octokit = graphql.defaults({
  headers: {
    authorization: `token ${process.env.TOKEN}`,
  },
});

const parameters: {
    pullRequestNumber: number;
    repositoryName: string;
    repositoryOwner: string;
  } = { 
  // ... 
}

const query = `
  query FindPullRequestCommitAuthors($repositoryOwner: String!, $repositoryName: String!, $pullRequestNumber: Int!, $pageSize: Int!, $endCursor: String) {
    repository(owner: $repositoryOwner, name: $repositoryName) {
      pullRequest(number: $pullRequestNumber) {
        commits(first: $pageSize, after: $endCursor) {
          edges {
            node {
              commit {
                author {
                  user {
                    login
                  }
                }
              }
            }
          }
          pageInfo {
            endCursor
            hasNextPage
          }
        }
      }
    }
  }
`;

const iterator = makeGraphqlIterator<PullRequestCommitNode>(
  octokit,
  query,
  parameters,
  (response: GraphQlQueryResponseData): IterableList<PullRequestCommitNode> =>
    response.repository.pullRequest?.commits,
);

const firstResult: IteratorResult<PullRequestCommitNode> = await iterator.next();

if (firstResult.done === true) {
  logWarning('Could not find PR commits, aborting.');

  return true;
}

for await (const commitNode of iterator) {
  const { author } = commitNode.commit;

  // ...
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment