|
3 | 3 | const fs = require('fs');
|
4 | 4 |
|
5 | 5 | const PR_QUERY = fs.readFileSync('./queries/PR.gql', 'utf8');
|
6 |
| -const request = require('./lib/request'); |
| 6 | +const REVIEWS_QUERY = fs.readFileSync('./queries/Reviews.gql', 'utf8'); |
| 7 | +const COMMENTS_QUERY = fs.readFileSync('./queries/PRComments.gql', 'utf8'); |
| 8 | +const COMMITS_QUERY = fs.readFileSync('./queries/PRCommits.gql', 'utf8'); |
| 9 | +const { request, requestAll } = require('./lib/request'); |
| 10 | +const { getCollaborators } = require('./lib/collaborators'); |
7 | 11 | const logger = require('./lib/logger');
|
8 |
| -const PR_ID = parseInt(process.argv[2]) || 12756; |
| 12 | +const { ascending } = require('./lib/comp'); |
| 13 | +const PR_ID = parseInt(process.argv[2]) || 14782; |
9 | 14 | const OWNER = 'nodejs';
|
10 | 15 | const REPO = 'node';
|
11 | 16 |
|
12 |
| -function getReviewers(pr) { |
13 |
| - return []; // TODO |
| 17 | +const FIXES_RE = /Fixes: (\S+)/mg; |
| 18 | +const FIX_RE = /Fixes: (\S+)/; |
| 19 | +const REFS_RE = /Refs?: (\S+)/mg; |
| 20 | +const REF_RE = /Refs?: (\S+)/; |
| 21 | +const LGTM_RE = /(\W|^)lgtm(\W|$)/i; |
| 22 | + |
| 23 | +// const REFERENCE_RE = /referenced this pull request in/ |
| 24 | + |
| 25 | +const { |
| 26 | + PENDING, COMMENTED, APPROVED, CHANGES_REQUESTED, DISMISSED |
| 27 | +} = require('./lib/review_state'); |
| 28 | + |
| 29 | +function mapByGithubReviews(reviews, collaborators) { |
| 30 | + const map = new Map(); |
| 31 | + const list = reviews |
| 32 | + .filter((r) => r.state !== PENDING && r.state !== COMMENTED) |
| 33 | + .filter((r) => { |
| 34 | + return (r.author && r.author.login && // could be a ghost |
| 35 | + collaborators.get(r.author.login.toLowerCase())); |
| 36 | + }).sort((a, b) => { |
| 37 | + return ascending(a.publishedAt, b.publishedAt); |
| 38 | + }); |
| 39 | + |
| 40 | + for (const r of list) { |
| 41 | + const login = r.author.login.toLowerCase(); |
| 42 | + const entry = map.get(login); |
| 43 | + if (!entry) { // initialize |
| 44 | + map.set(login, { |
| 45 | + state: r.state, |
| 46 | + date: r.publishedAt, |
| 47 | + ref: r.url |
| 48 | + }); |
| 49 | + } |
| 50 | + switch (r.state) { |
| 51 | + case APPROVED: |
| 52 | + case CHANGES_REQUESTED: |
| 53 | + // Overwrite previous, whether initalized or not |
| 54 | + map.set(login, { |
| 55 | + state: r.state, |
| 56 | + date: r.publishedAt, |
| 57 | + ref: r.url |
| 58 | + }); |
| 59 | + break; |
| 60 | + case DISMISSED: |
| 61 | + // TODO: check the state of the dismissed review? |
| 62 | + map.delete(login); |
| 63 | + break; |
| 64 | + } |
| 65 | + } |
| 66 | + return map; |
14 | 67 | }
|
15 | 68 |
|
16 |
| -function getFixes(pr) { |
| 69 | +// TODO: count -1 ...? But they should make it explicit |
| 70 | +/** |
| 71 | + * @param {Map} oldMap |
| 72 | + * @param {{}[]} comments |
| 73 | + * @param {Map} collaborators |
| 74 | + * @returns {Map} |
| 75 | + */ |
| 76 | +function updateMapByRawReviews(oldMap, comments, collaborators) { |
| 77 | + const withLgtm = comments.filter((c) => LGTM_RE.test(c.bodyText)) |
| 78 | + .filter((c) => { |
| 79 | + return (c.author && c.author.login && // could be a ghost |
| 80 | + collaborators.get(c.author.login.toLowerCase())); |
| 81 | + }).sort((a, b) => { |
| 82 | + return ascending(a.publishedAt, b.publishedAt); |
| 83 | + }); |
| 84 | + |
| 85 | + for (const c of withLgtm) { |
| 86 | + const login = c.author.login.toLowerCase(); |
| 87 | + const entry = oldMap.get(login); |
| 88 | + if (!entry || entry.publishedAt < c.publishedAt) { |
| 89 | + logger.info(`${login} approved via LGTM in comments`); |
| 90 | + oldMap.set(login, { |
| 91 | + state: APPROVED, |
| 92 | + date: c.publishedAt, |
| 93 | + ref: c.bodyText // no url, have to use bodyText |
| 94 | + }); |
| 95 | + } |
| 96 | + } |
| 97 | + return oldMap; |
| 98 | +} |
| 99 | +/** |
| 100 | + * @param {{}[]} reviewes |
| 101 | + * @param {{}[]} comments |
| 102 | + * @param {Map} collaborators |
| 103 | + * @returns {Map} |
| 104 | + */ |
| 105 | +async function getReviewers(reviews, comments, collaborators) { |
| 106 | + const ghReviews = mapByGithubReviews(reviews, collaborators); |
| 107 | + const reviewers = updateMapByRawReviews(ghReviews, comments, collaborators); |
| 108 | + const result = []; |
| 109 | + for (const [ login, review ] of reviewers) { |
| 110 | + if (review.state !== APPROVED) { |
| 111 | + logger.warn(`${login}: ${review.state} ${review.ref}`); |
| 112 | + } else { |
| 113 | + const data = collaborators.get(login); |
| 114 | + result.push({ |
| 115 | + name: data.name, |
| 116 | + email: data.email |
| 117 | + }); |
| 118 | + } |
| 119 | + } |
| 120 | + return result; |
| 121 | +} |
| 122 | + |
| 123 | +async function getFixes(pr) { |
17 | 124 | return []; // TODO
|
18 | 125 | }
|
19 | 126 |
|
20 |
| -function getRefs(pr) { |
| 127 | +async function getRefs(pr) { |
21 | 128 | return []; // TODO
|
22 | 129 | }
|
23 | 130 |
|
24 | 131 | async function main() {
|
25 |
| - logger.info(`Requesting ${OWNER}/${REPO}/pull/${PR_ID}`); |
26 |
| - const data = await request(PR_QUERY, { |
27 |
| - prid: PR_ID, |
28 |
| - owner: OWNER, |
29 |
| - repo: REPO |
30 |
| - }); |
31 |
| - |
32 |
| - const pr = data.repository.pullRequest; |
| 132 | + const prid = PR_ID; |
| 133 | + const owner = OWNER; |
| 134 | + const repo = REPO; |
| 135 | + |
| 136 | + logger.info(`Requesting ${owner}/${repo}/pull/${prid}`); |
| 137 | + const prData = await request(PR_QUERY, { prid, owner, repo }); |
| 138 | + const pr = prData.repository.pullRequest; |
| 139 | + const prUrl = pr.url; |
| 140 | + |
| 141 | + const vars = { prid, owner, repo }; |
| 142 | + logger.info(`Requesting ${owner}/${repo}/pull/${prid}/reviews`); |
| 143 | + const reviews = await requestAll(REVIEWS_QUERY, vars, [ |
| 144 | + 'repository', 'pullRequest', 'reviews' |
| 145 | + ]); |
| 146 | + logger.info(`Requesting ${owner}/${repo}/pull/${prid}/comments`); |
| 147 | + const comments = await requestAll(COMMENTS_QUERY, vars, [ |
| 148 | + 'repository', 'pullRequest', 'comments' |
| 149 | + ]); |
| 150 | + const collaborators = await getCollaborators(owner, repo); |
| 151 | + // logger.info(`Requesting ${owner}/${repo}/pull/${prid}/commits`); |
| 152 | + // const commits = await requestAll(COMMITS_QUERY, vars, [ |
| 153 | + // 'repository', 'pullRequest', 'commits' |
| 154 | + // ]); |
| 155 | + |
| 156 | + const reviewedBy = await getReviewers(reviews, comments, collaborators); |
| 157 | + const fixes = await getFixes(reviews, comments); |
| 158 | + const refs = await getRefs(reviews, comments); |
| 159 | + |
33 | 160 | const output = {
|
34 |
| - prUrl: pr.url, |
35 |
| - reviewedBy: getReviewers(pr), |
36 |
| - fixes: getFixes(pr), |
37 |
| - refs: getRefs(pr) |
| 161 | + prUrl, reviewedBy, fixes, refs |
38 | 162 | };
|
39 | 163 |
|
40 | 164 | let meta = [
|
41 | 165 | '-------------------------------- >8 --------------------------------',
|
42 |
| - `PR-URL: ${output.prUrl}`]; |
| 166 | + `PR-URL: ${output.prUrl}` |
| 167 | + ]; |
43 | 168 | meta = meta.concat(output.reviewedBy.map((reviewer) => {
|
44 | 169 | return `Reviewed-By: ${reviewer.name} <${reviewer.email}>`;
|
45 | 170 | }));
|
|
0 commit comments