How to download pull request metadata using the GitHub GraphQL API
This is a TypeScript Node.js script to download GitHub pull request information (title, body, comments, etc.) using the GitHub GraphQL API. The data is saved in a JSON file.
The GitHub repo is here: download-github-prs
.
Create a GitHub personal access token
Create a GitHub personal access token as described here (no checkboxes need to be selected for public repos): https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token This is used to access the GitHub GraphQL API.
Intall libraries
- Create a directory and cd into it
$ mkdir -p /tmp/my-prs $ cd /tmp/my-prs
- Create package.json file:
{ "scripts": { "download": "ts-node index.ts" }, "dependencies": { "node-fetch": "^2.6.1" }, "devDependencies": { "@types/node-fetch": "^2.5.7", "ts-node": "^9.0.0", "typescript": "^4.1.2" } }
- Install
$ npm install
Create script
Creat a file, /tmp/my-prs/index.ts
, replacing XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
with the GitHub personal access token described above.
import * as fs from "fs";
import fetch from "node-fetch";
const GITHUB_TOKEN = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
const GITHUB_GRAPHQL_API_URL = "https://api.github.com/graphql";
const OUTPUT_DIR = "/tmp/my-prs";
const REPO_OWNER = "facebookexperimental";
const REPO_NAME = "Recoil";
fetchPullRequest(1);
/**
* fetchPullRequest
*/
async function fetchPullRequest(prNumber: number) {
const reactionFragment = `
content
user {
login
}
`;
const userContentEditFragment = `
createdAt
deletedAt
deletedBy {
login
}
diff
editedAt
editor {
login
}
updatedAt
`;
const commentFragment = `
author {
login
}
body
createdAt
reactions(first: 100) {
nodes {
${reactionFragment}
}
}
userContentEdits(first: 100) {
nodes {
${userContentEditFragment}
}
}
`;
const query = `
query {
repository(owner: "${REPO_OWNER}", name: "${REPO_NAME}") {
nameWithOwner
pullRequest(number: ${prNumber}) {
author { login }
baseRefName
baseRefOid
body
closedAt
comments(first: 100) {
nodes {
${commentFragment}
}
}
commits(first: 250) {
nodes {
commit {
oid
}
}
}
createdAt
files(first: 100) {
nodes { path }
}
headRefName
headRefOid
mergeCommit { oid }
merged
mergedAt
mergedBy { login }
number
publishedAt
reactions(first: 10) {
nodes {
${reactionFragment}
}
}
reviews(first: 10) {
nodes {
author { login }
body
comments(first: 10) {
nodes {
${commentFragment}
}
}
commit {
oid
}
createdAt
editor { login }
publishedAt
reactions(first: 10) {
nodes {
${reactionFragment}
}
}
resourcePath
submittedAt
updatedAt
userContentEdits(first: 10) {
nodes {
${userContentEditFragment}
}
}
}
}
state
title
updatedAt
userContentEdits(first: 10) {
nodes {
${userContentEditFragment}
}
}
}
}
}
`;
// make graphql query and strigify the response
const resp = await fetchQuery(query);
const respStr = JSON.stringify(resp, null, 2);
// save json file
const filepath = [
`${OUTPUT_DIR}/`,
`${REPO_NAME}-pr-${String(prNumber).padStart(4, "0")}.json`,
].join("");
console.log(`Saving ${filepath}...`);
fs.writeFileSync(filepath, respStr);
}
/**
* fetchQuery
*/
function fetchQuery(query: string, variables: Record<string, any> = {}) {
return fetch(GITHUB_GRAPHQL_API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `bearer ${GITHUB_TOKEN}`,
},
body: JSON.stringify({
query,
variables,
}),
}).then((response) => {
return response.json();
});
}
Run it
$ npm run download
Output
This produces the following JSON file
$ cat /tmp/my-prs/Recoil-pr-0001.json
{
"data": {
"repository": {
"nameWithOwner": "facebookexperimental/Recoil",
"pullRequest": {
"author": {
"login": "facebook-github-bot"
},
"baseRefName": "master",
"baseRefOid": "40e870caadc159a87e81be291ff641410ab32e8f",
"body": "This is pull request was created automatically because we noticed your project was missing a Contributing file.\n\nCONTRIBUTING files explain how a developer can contribute to the project - which you should actively encourage.\n\nThis PR was crafted with love by Facebook's Open Source Team.",
"closedAt": "2020-05-13T04:12:15Z",
"comments": {
"nodes": [
{
"author": {
"login": "davidmccabe"
},
"body": "Already added this manually.",
"createdAt": "2020-05-13T04:12:15Z",
"reactions": {
"nodes": []
},
"userContentEdits": {
"nodes": []
}
}
]
},
"commits": {
"nodes": [
{
"commit": {
"oid": "96f91679540362fa96a6c92611a8ef5621447b42"
}
}
]
},
"createdAt": "2020-05-06T22:31:01Z",
"files": {
"nodes": [
{
"path": "CONTRIBUTING.md"
}
]
},
"headRefName": "automated_fixup_contributing_file_exists",
"headRefOid": "96f91679540362fa96a6c92611a8ef5621447b42",
"mergeCommit": null,
"merged": false,
"mergedAt": null,
"mergedBy": null,
"number": 1,
"publishedAt": "2020-05-06T22:31:01Z",
"reactions": {
"nodes": []
},
"reviews": {
"nodes": []
},
"state": "CLOSED",
"title": "Adding Contributing file",
"updatedAt": "2020-10-07T20:23:05Z",
"userContentEdits": {
"nodes": []
}
}
}
}
}
Related posts
- How to use ast-grep with GraphQL — posted 2024-09-24
- Next.js App Router (RSC) projects w/ open source code — posted 2024-07-30
- Next.js Relay GraphQL Pokemon example — posted 2024-05-22
- Example Node.js Passport.js SAML app using OneLogin — posted 2024-05-10
- Aphrodite to CSS Modules codemod — posted 2022-12-09
- Simple codemod example with jscodeshift — posted 2021-05-03