|
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), |
|
); |