-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6b5223d
commit bfa287b
Showing
7 changed files
with
351 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
#!/usr/bin/env node | ||
|
||
'use strict'; | ||
|
||
const Request = require('../lib/request'); | ||
const auth = require('../lib/auth'); | ||
const { writeFile } = require('../lib/file'); | ||
const { runPromise } = require('../lib/run'); | ||
const CLI = require('../lib/cli'); | ||
const ContributionAnalyzer = require('../lib/contribution-analyzer'); | ||
|
||
const yargs = require('yargs'); | ||
// eslint-disable-next-line no-unused-vars | ||
const argv = yargs | ||
.command({ | ||
command: 'collaborators', | ||
desc: 'Getting contributions from collaborators', | ||
handler: handler | ||
}) | ||
.command({ | ||
command: 'tsc', | ||
desc: 'Getting contributions from TSC members', | ||
handler: handler | ||
}) | ||
.command({ | ||
command: 'for <ids..>', | ||
desc: 'Getting contributions from GitHub handles', | ||
builder: (yargs) => { | ||
yargs | ||
.positional('ids', { | ||
describe: 'GitHub handles', | ||
type: 'array' | ||
}); | ||
}, | ||
handler: handler | ||
}) | ||
.string('repo') | ||
.string('owner') | ||
.string('type') | ||
.string('branch') | ||
.string('readme') | ||
.string('output') | ||
.default({ | ||
repo: 'node', | ||
owner: 'nodejs', | ||
type: 'participation', | ||
branch: 'master' | ||
}) | ||
.demandCommand(1, 'must provide a valid command') | ||
.help() | ||
.argv; | ||
|
||
async function main(argv) { | ||
const cli = new CLI(); | ||
const credentials = await auth(); | ||
const request = new Request(credentials); | ||
const config = require('../lib/config').getMergedConfig(); | ||
argv = Object.assign(argv, config); | ||
const analyzer = new ContributionAnalyzer(request, cli, argv); | ||
|
||
const [ command ] = argv._; | ||
let result; | ||
switch (command) { | ||
case 'collaborators': | ||
result = await analyzer.getLatestContributionForCollaborators(); | ||
break; | ||
case 'tsc': | ||
result = await analyzer.getLatestContributionForTSC(); | ||
break; | ||
case 'for': | ||
result = await analyzer.getLatestContributionForIds(argv.ids); | ||
break; | ||
default: | ||
throw new Error(`Unknown command ${command}`); | ||
} | ||
if (argv.output) { | ||
const txt = analyzer.formatContributionList(result); | ||
writeFile(argv.output, txt); | ||
} | ||
} | ||
|
||
function handler(argv) { | ||
runPromise(main(argv)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
'use strict'; | ||
|
||
const SEARCH_ISSUE = 'SearchIssue'; | ||
const SEARCH_COMMIT = 'SearchCommit'; | ||
const USER = 'User'; | ||
|
||
const { getCollaborators } = require('./collaborators'); | ||
const Cache = require('./cache'); | ||
const { ascending } = require('./comp'); | ||
const { isTheSamePerson } = require('./user'); | ||
|
||
class ContributionAnalyzer { | ||
constructor(request, cli, argv) { | ||
this.request = request; | ||
this.cli = cli; | ||
this.argv = argv; | ||
} | ||
|
||
async getCommits(user) { | ||
const { request, argv } = this; | ||
const { owner, repo, branch } = argv; | ||
|
||
const userData = await request.gql(USER, { login: user }); | ||
const authorId = userData.user.id; | ||
const results = await request.gql(SEARCH_COMMIT, { | ||
owner, repo, branch, authorId | ||
}, [ 'repository', 'ref', 'target', 'history' ]); | ||
return results | ||
.sort((a, b) => { | ||
return a.authoredDate > b.authoredDate ? -1 : 1; | ||
}); | ||
} | ||
|
||
async getParticipation(user) { | ||
const { request } = this; | ||
const results = await request.gql(SEARCH_ISSUE, { | ||
queryString: `involves:${user} repo:nodejs/node`, | ||
mustBeAuthor: false | ||
}); | ||
if (results.search.nodes.length === 0) { | ||
return [{ | ||
url: 'N/A', | ||
date: 'N/A', | ||
isRelavent: false, | ||
type: 'N/A' | ||
}]; | ||
} | ||
|
||
const res = results.search.nodes | ||
.map(issue => this.participationByUser(issue, user)) | ||
// .filter((res) => res.isRelavent) | ||
.sort((a, b) => { | ||
return a.date > b.date ? -1 : 1; | ||
}); | ||
return res; | ||
} | ||
|
||
participationByUser(issue, user) { | ||
const result = { | ||
url: issue.url, | ||
date: new Date(issue.publishedAt).toISOString(), | ||
isRelavent: false, | ||
type: '' | ||
}; | ||
|
||
// Author | ||
if (isTheSamePerson(issue.author, user)) { | ||
result.date = issue.publishedAt; | ||
result.isRelavent = true; | ||
result.type = /pull/.test(issue.url) ? 'pull' : 'issue'; | ||
} | ||
|
||
if (issue.reviews) { | ||
issue.reviews.nodes.forEach((review) => { | ||
if (!isTheSamePerson(review.author, user)) { | ||
return; | ||
} | ||
|
||
result.isRelavent = true; | ||
if (review.publishedAt > result.date) { | ||
result.date = review.publishedAt; | ||
result.type = 'review'; | ||
} | ||
}); | ||
} | ||
|
||
issue.comments.nodes.forEach((comment) => { | ||
if (!isTheSamePerson(comment.author, user)) { | ||
return; | ||
} | ||
|
||
result.isRelavent = true; | ||
if (comment.publishedAt > result.date) { | ||
result.date = comment.publishedAt; | ||
result.type = 'comment'; | ||
} | ||
}); | ||
|
||
return result; | ||
} | ||
|
||
async getContributionsForId(user) { | ||
const { argv } = this; | ||
if (argv.type === 'participation') { | ||
return this.getParticipation(user); | ||
} else if (argv.type === 'commit') { | ||
return this.getCommits(user); | ||
} | ||
} | ||
|
||
async getLatestContributionForId(user) { | ||
const contributions = await this.getContributionsForId(user); | ||
if (contributions.length) { | ||
return Object.assign({ user }, contributions[0]); | ||
} | ||
return { | ||
user: user, | ||
url: 'N/A', | ||
date: 'N/A', | ||
isRelavent: false, | ||
type: 'N/A' | ||
}; | ||
} | ||
|
||
formatContribution(data) { | ||
if (this.argv.type === 'participation') { | ||
const type = | ||
data.type.padEnd(8).toUpperCase(); | ||
const date = data.date.slice(0, 10); | ||
return `${date} ${type} @${data.user.padEnd(22)} ${data.url}`; | ||
} else if (this.argv.type === 'commit') { | ||
const hash = data.oid.slice(0, 7); | ||
const date = data.authoredDate.slice(0, 10); | ||
const message = data.messageHeadline; | ||
return `${date} ${hash} @${data.user.padEnd(22)} ${message}`; | ||
} | ||
} | ||
|
||
getResult(user, data) { | ||
if (this.argv.type === 'participation') { | ||
return { | ||
user, | ||
date: data.date, | ||
url: data.url, | ||
type: data.type | ||
}; | ||
} else if (this.argv.type === 'commit') { | ||
return { | ||
user, | ||
authoredDate: data.authoredDate, | ||
messageHeadline: data.messageHeadline, | ||
oid: data.oid | ||
}; | ||
} | ||
} | ||
|
||
getDate(item) { | ||
if (this.argv.type === 'participation') { | ||
return item.date; | ||
} else if (this.argv.type === 'commit') { | ||
return item.authoredDate; | ||
} | ||
} | ||
|
||
async getLatestContributionForIds(ids) { | ||
const { cli } = this; | ||
const total = ids.length; | ||
let counter = 1; | ||
const latestContrib = {}; | ||
for (const user of ids) { | ||
cli.startSpinner(`Grabbing data for @${user}, ${counter++}/${total}..`); | ||
const data = await this.getLatestContributionForId(user); | ||
latestContrib[user] = data; | ||
cli.stopSpinner(this.formatContribution(data)); | ||
} | ||
|
||
const sorted = ids.sort((a, b) => { | ||
const aa = latestContrib[a]; | ||
const bb = latestContrib[b]; | ||
return ascending(this.getDate(aa), this.getDate(bb)); | ||
}).map((user) => { | ||
const data = latestContrib[user]; | ||
return this.getResult(user, data); | ||
}); | ||
return sorted; | ||
} | ||
|
||
formatContributionList(list) { | ||
let txt = ''; | ||
for (const item of list) { | ||
txt += this.formatContribution(item) + '\n'; | ||
} | ||
return txt; | ||
} | ||
|
||
async getLatestContributionForCollaborators() { | ||
const { cli, argv, request } = this; | ||
const collaborators = await getCollaborators(cli, request, argv); | ||
const ids = [...collaborators.keys()]; | ||
return this.getLatestContributionForIds(ids); | ||
} | ||
|
||
async getLatestContributionForTSC() { | ||
const { cli, argv, request } = this; | ||
const collaborators = await getCollaborators(cli, request, argv); | ||
const tsc = [...collaborators.values()].filter((user) => user.isTSC()); | ||
const ids = tsc.map(user => user.login); | ||
return this.getLatestContributionForIds(ids); | ||
} | ||
} | ||
|
||
const contribCache = new Cache(); | ||
contribCache.wrap(ContributionAnalyzer, { | ||
getCommits(user) { | ||
return { key: `commits-${user}`, ext: '.json' }; | ||
}, | ||
getParticipation(user) { | ||
return { key: `participation-${user}`, ext: '.json' }; | ||
} | ||
}); | ||
contribCache.enable(); | ||
|
||
module.exports = ContributionAnalyzer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
query SearchCommitByAuthor($repo: String!, $owner: String!, $branch: String!, $authorId: ID!, $after: String) { | ||
repository(name: $repo, owner: $owner) { | ||
ref(qualifiedName: $branch) { | ||
target { | ||
... on Commit { | ||
history(first: 100, after: $after, author: { id: $authorId } ) { | ||
totalCount | ||
pageInfo { hasNextPage, endCursor } | ||
nodes { | ||
oid | ||
authoredDate | ||
messageHeadline | ||
author { | ||
name | ||
user { | ||
login | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
query User($login: String!) { | ||
user(login: $login) { | ||
login, | ||
email, | ||
id | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.