Skip to content

Instantly share code, notes, and snippets.

@cpojer
Last active January 2, 2019 12:27
Show Gist options
  • Save cpojer/25a0c4af786360a81d9f508b3962e031 to your computer and use it in GitHub Desktop.
Save cpojer/25a0c4af786360a81d9f508b3962e031 to your computer and use it in GitHub Desktop.
GitHub RN Issue Parser
node_modules
comments.json
comments.md
const {GraphQLClient} = require('graphql-request');
const fs = require('fs');
const path = require('path');
const CACHE_PATH = path.join(__dirname, 'comments.json');
const GITHUB_API = 'https://api.github.com/graphql';
const GITHUB_TOKEN = '<YOUR TOKEN HERE>';
const OUTPUT_FILE = path.join(__dirname, 'comments.md');
const REACTIONS_FRAGMENT = variables => `
reactions(first:100${variables ? ',' + variables : ''}) {
edges {
node {
content,
}
}
pageInfo {
endCursor,
hasNextPage
}
}
`;
const COMMENTS_FRAGMENT = () => `
id,
body,
url,
author {
... on User {
name
}
}
${REACTIONS_FRAGMENT()}
`;
const COMMENT_FRAGMENT = id => variables => `
{
node(id:"${id}") {
... on IssueComment {
${REACTIONS_FRAGMENT(variables)}
}
}
}
`;
const ISSUE_QUERY = variables => `
{
repository(owner:"react-native-community", name:"discussions-and-proposals") {
issue(number:64) {
comments(first:100${variables ? ',' + variables : ''}) {
edges {
node {
${COMMENTS_FRAGMENT()}
}
}
pageInfo {
endCursor,
hasNextPage
}
}
}
}
}
`;
const REACTIONS_WEIGHT = {
CONFUSED: -1,
HEART: 1,
HOORAY: 1,
LAUGH: -1,
THUMBS_DOWN: -1,
THUMBS_UP: 1,
};
const COMMENT_TITLE_MAP = {
'MDEyOklzc3VlQ29tbWVudDQ0NDk3MTIyNA==': 'react-native link issues',
'MDEyOklzc3VlQ29tbWVudDQ0NDg5NzU2NQ==': 'Native API Support',
'MDEyOklzc3VlQ29tbWVudDQ0NTAyODYwMg==': 'File System API',
'MDEyOklzc3VlQ29tbWVudDQ0NTEwNjUyOA==': 'AsyncStorage is broken',
'MDEyOklzc3VlQ29tbWVudDQ0NDk4NDM1OA==': 'Android Image Flickering',
'MDEyOklzc3VlQ29tbWVudDQ0NDk3NDE5Nw==': 'Debugging Style Issues',
'MDEyOklzc3VlQ29tbWVudDQ0NTA4NTc4Nw==': 'Official RN Navigation Solution',
'MDEyOklzc3VlQ29tbWVudDQ0NTAxOTYwNg==': 'Battery Usage',
'MDEyOklzc3VlQ29tbWVudDQ0NTAxNDE4Mw==': 'React Native Target Audience',
'MDEyOklzc3VlQ29tbWVudDQ0NDk4MzI4OQ==': 'Expo & Ejecting',
'MDEyOklzc3VlQ29tbWVudDQ0NTUwOTU1Nw==': 'TypeScript Support',
'MDEyOklzc3VlQ29tbWVudDQ0NTAxOTI5Ng==': 'Latex / MathML Support',
'MDEyOklzc3VlQ29tbWVudDQ0NTA2OTE0Ng==': 'Image URIs on iOS',
'MDEyOklzc3VlQ29tbWVudDQ0NDk4NTI4Mg==': 'Animated Flow Support',
};
const IGNORED_COMMENTS = {
'MDEyOklzc3VlQ29tbWVudDQ0NDkxMzkwNQ==':
'This comment has few votes and lists many issues',
};
const PRINT_COMMENT = ({
body,
tags,
title,
upvotes,
url,
}) => `${title} [#](${url})
${body}
* **Votes:** ${upvotes}${tags.length ? `\n* **Tags:** ${tags.join(', ')}` : ''}
* **Estimated time to fix:**
* **Point of contact:**
* **Comment by Facebook:**
`;
const fetchAll = async (client, queryFn, selector) => {
return fetchRemaining(
selector(await client.request(queryFn())),
client,
queryFn,
selector,
);
};
const fetchRemaining = async (data, client, queryFn, selector) => {
while (data.pageInfo.hasNextPage) {
const {edges, pageInfo} = selector(
await client.request(queryFn(`after:"${data.pageInfo.endCursor}"`)),
);
data.edges.push(...edges);
data.pageInfo = pageInfo;
}
return data;
};
const fetchAllComments = async () => {
try {
return JSON.parse(fs.readFileSync(CACHE_PATH, 'utf8'));
} catch (e) {}
const client = new GraphQLClient(GITHUB_API, {
headers: {
authorization: `bearer ${GITHUB_TOKEN}`,
},
});
const comments = await fetchAll(
client,
ISSUE_QUERY,
data => data.repository.issue.comments,
);
// Fetch all reactions for all comments
await Promise.all(
comments.edges.map(async edge => {
edge.node.reactions = await fetchRemaining(
edge.node.reactions,
client,
COMMENT_FRAGMENT(edge.node.id),
data => data.node.reactions,
);
return edge;
}),
);
fs.writeFileSync(CACHE_PATH, JSON.stringify(comments), 'utf8');
return comments;
};
const processComments = rawComments => {
// Flatten the data structure and sum up all the reactions
const comments = [];
rawComments.edges.forEach(({node: comment}) => {
const {id, body, author, reactions, url} = comment;
const upvotes = reactions.edges.reduce(
(count, {node: {content}}) => count + REACTIONS_WEIGHT[content],
0,
);
if (
!IGNORED_COMMENTS[id] &&
author.name !== 'Christoph Nakazawa' &&
upvotes > 0
) {
comments.push({
id,
body,
upvotes,
url,
});
}
});
comments.sort((a, b) => b.upvotes - a.upvotes);
// Format comments
const allContent = comments.map(comment => {
const {id} = comment;
const content = comment.body.split('\n');
content[0] = content[0].trim();
if (content[0].startsWith('**')) {
content[0] = '## ' + content[0].replace(/\*\*/g, '');
} else if (content[0].startsWith('##')) {
content[0] = '## ' + content[0].replace(/#/g, '');
} else if (COMMENT_TITLE_MAP[id]) {
// upvotes-- :P
content.unshift('## ' + COMMENT_TITLE_MAP[id]);
} else {
throw new Error(`Comment ${id} has no title: ${body}`);
}
const [title, ...body] = content;
comment.title = title.trim();
comment.body =
body
.join('\n')
.replace(/#([A-z\-]{2,})/g, '')
.trim() + '\n';
comment.tags = [];
body
.join('\n')
.replace(/#([A-z\-]{2,})/g, (_, tag) => comment.tags.push(tag));
return PRINT_COMMENT(comment);
});
fs.writeFileSync(OUTPUT_FILE, allContent.join('\n'), 'utf8');
};
(async () => processComments(await fetchAllComments()))().catch(error =>
console.log(error),
);
{
"name": "react-native-github-issue",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"graphql-request": "^1.8.2",
"prettier": "^1.15.3"
},
"scripts": {
"prettier": "prettier index.js --write --single-quote --trailing-comma=all --no-bracket-spacing"
}
}
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
[email protected]:
version "2.2.2"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.2.tgz#a47ff4f7fc712daba8f6a695a11c948440d45723"
dependencies:
node-fetch "2.1.2"
whatwg-fetch "2.0.4"
graphql-request@^1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-1.8.2.tgz#398d10ae15c585676741bde3fc01d5ca948f8fbe"
dependencies:
cross-fetch "2.2.2"
[email protected]:
version "2.1.2"
resolved "http://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
prettier@^1.15.3:
version "1.15.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a"
[email protected]:
version "2.0.4"
resolved "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment