-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Upgrade packages
- Loading branch information
Showing
14 changed files
with
391 additions
and
153 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
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 |
---|---|---|
@@ -1,2 +1,3 @@ | ||
coverage/ | ||
node_modules/ | ||
node_modules/ | ||
yarn-error.log |
Large diffs are not rendered by default.
Oops, something went wrong.
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
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 |
---|---|---|
@@ -1,139 +1,172 @@ | ||
function getLink(headers) { | ||
const result = { | ||
next: null, | ||
last: null, | ||
first: null, | ||
prev: null, | ||
}; | ||
|
||
const link = headers.get('Link'); | ||
if (link == null) | ||
return result; | ||
|
||
const urls = /<([^<>]+)>; rel="(next|last|first|prev)"/.exec(link); | ||
if (urls == null || urls.length < 3) { | ||
throw new Error('Pagination error'); | ||
} | ||
|
||
let i = urls.length - 1; | ||
while (i) { | ||
result[urls[i]] = urls[i - 1]; | ||
i -= 2; | ||
import {fetchJSON} from './utils'; | ||
|
||
async function getAccessToken(code) { | ||
const response = await fetchJSON( | ||
OAUTH_GATEWAY_URL + '?' + | ||
'client_id=' + OAUTH_CLIENT_ID + '&' + | ||
'code=' + code, | ||
{method: 'POST'}, | ||
); | ||
if (response.error || !response.access_token) { | ||
throw new Error('Unable to get access token'); | ||
} | ||
|
||
return result; | ||
return response.access_token; | ||
} | ||
|
||
async function fetchURL(url) { | ||
const response = await fetch(url); | ||
if (!response.ok) { | ||
throw new Error('Error fetching ' + url); | ||
class AuthorizationError extends Error {} | ||
|
||
class GitHub { | ||
constructor(accessToken) { | ||
this._authorization = {Authorization: 'token ' + accessToken}; | ||
} | ||
return response; | ||
} | ||
|
||
async function fetchJSON(url) { | ||
const response = await fetchURL(url); | ||
return await response.json(); | ||
} | ||
// Parses the Link header and returns a corresponding object. | ||
// Please see https://developer.github.com/v3/guides/traversing-with-pagination/. | ||
static _getPageLinks(headers) { | ||
const result = { | ||
next: null, | ||
last: null, | ||
first: null, | ||
prev: null, | ||
}; | ||
|
||
const link = headers.get('Link'); | ||
if (link == null) | ||
return result; | ||
|
||
const urls = /<([^<>]+)>; rel="(next|last|first|prev)"/.exec(link); | ||
if (urls == null || urls.length < 3) { | ||
throw new Error('Pagination error'); | ||
} | ||
|
||
async function fetchPages(url, items) { | ||
const response = await fetchURL(url); | ||
const result = await response.json(); | ||
if (items) { | ||
Array.prototype.push.apply(items, result.items); | ||
let i = urls.length - 1; | ||
while (i) { | ||
result[urls[i]] = urls[i - 1]; | ||
i -= 2; | ||
} | ||
|
||
return result; | ||
} | ||
|
||
const link = getLink(response.headers); | ||
if (link.next) { | ||
await fetchPages(link.next, result.items); | ||
static _htmlURL(type, author, repo) { | ||
const q = encodeURIComponent(`type:${type} author:${author} repo:${repo}`); | ||
return 'https://github.com/search?utf8=✓&q=' + q; | ||
} | ||
|
||
return result; | ||
} | ||
static _reducePullRequests(items) { | ||
return items.reduce((result, value) => { | ||
const url = value.repository_url; | ||
const repository = result[url] || { | ||
open: 0, | ||
merged: 0, | ||
closed: 0, | ||
}; | ||
|
||
async function isMerged(url) { | ||
const pr = await fetchJSON(url); | ||
return pr.merged; | ||
} | ||
repository[value.state] += 1; | ||
result[url] = repository; | ||
|
||
async function fetchPullRequests(author) { | ||
const q = encodeURIComponent('type:pr author:' + author); | ||
const pullRequests = await fetchPages('https://api.github.com/search/issues?per_page=100&q=' + q); | ||
return result; | ||
}, {}); | ||
} | ||
|
||
static _sortPullRequests(items) { | ||
return items.sort((a, b) => { | ||
const aCount = a.open + a.closed + a.merged; | ||
const bCount = b.open + b.closed + b.merged; | ||
|
||
const promises = pullRequests.items.map(async (item) => { | ||
if (item.state == 'closed' && await isMerged(item.pull_request.url)) { | ||
item.state = 'merged'; | ||
if (aCount == bCount) { | ||
return a.repository.stargazers_count < b.repository.stargazers_count; | ||
} | ||
return aCount < bCount; | ||
}); | ||
} | ||
|
||
async _fetch(url) { | ||
const response = await fetch(url, {headers: this._authorization}); | ||
if (response.status == 401) { | ||
throw new AuthorizationError(); | ||
} | ||
if (!response.ok) { | ||
throw new Error('Could not fetch ' + url); | ||
} | ||
return item; | ||
}); | ||
|
||
return await Promise.all(promises); | ||
} | ||
return response; | ||
} | ||
|
||
function htmlURL(type, author, repo) { | ||
const q = encodeURIComponent(`type:${type} author:${author} repo:${repo}`); | ||
return 'https://github.com/search?utf8=✓&q=' + q; | ||
} | ||
async _fetchJSON(url) { | ||
const response = await this._fetch(url); | ||
return await response.json(); | ||
} | ||
|
||
function reducePullRequests(items) { | ||
return items.reduce((result, value) => { | ||
const url = value.repository_url; | ||
const repository = result[url] || { | ||
open: 0, | ||
merged: 0, | ||
closed: 0, | ||
}; | ||
async _fetchRepositoryData(items, author) { | ||
const promises = Object.entries(items).map(async (entry) => { | ||
const repository = await this._fetchJSON(entry[0]); | ||
return { | ||
repository: { | ||
html_url: repository.html_url, | ||
full_name: repository.full_name, | ||
stargazers_count: repository.stargazers_count, | ||
language: repository.language, | ||
}, | ||
open: entry[1].open, | ||
closed: entry[1].closed, | ||
merged: entry[1].merged, | ||
html_url: GitHub._htmlURL('pr', author, repository.full_name), | ||
}; | ||
}); | ||
|
||
return await Promise.all(promises); | ||
} | ||
|
||
repository[value.state] += 1; | ||
result[url] = repository; | ||
async _fetchPages(url, items) { | ||
const response = await this._fetch(url); | ||
const result = await response.json(); | ||
if (items) { | ||
Array.prototype.push.apply(items, result.items); | ||
} | ||
|
||
return result; | ||
}, {}); | ||
} | ||
const links = GitHub._getPageLinks(response.headers); | ||
if (links.next) { | ||
await this._fetchPages(links.next, result.items); | ||
} | ||
|
||
async function fetchRepositoryData(items, author) { | ||
const promises = Object.entries(items).map(async (entry) => { | ||
const repository = await fetchJSON(entry[0]); | ||
return { | ||
repository: { | ||
html_url: repository.html_url, | ||
full_name: repository.full_name, | ||
stargazers_count: repository.stargazers_count, | ||
language: repository.language, | ||
}, | ||
open: entry[1].open, | ||
closed: entry[1].closed, | ||
merged: entry[1].merged, | ||
html_url: htmlURL('pr', author, repository.full_name), | ||
}; | ||
}); | ||
return result; | ||
} | ||
|
||
return await Promise.all(promises); | ||
} | ||
async _isMerged(url) { | ||
const pr = await this._fetchJSON(url); | ||
return pr.merged; | ||
} | ||
|
||
function sort(items) { | ||
return items.sort((a, b) => { | ||
const aCount = a.open + a.closed + a.merged; | ||
const bCount = b.open + b.closed + b.merged; | ||
async _fetchPullRequests(author) { | ||
const q = encodeURIComponent('type:pr author:' + author); | ||
const pullRequests = await this._fetchPages('https://api.github.com/search/issues?per_page=100&q=' + q); | ||
|
||
if (aCount == bCount) { | ||
return a.repository.stargazers_count < b.repository.stargazers_count; | ||
} | ||
return aCount < bCount; | ||
}); | ||
} | ||
const promises = pullRequests.items.map(async (item) => { | ||
if (item.state == 'closed' && await this._isMerged(item.pull_request.url)) { | ||
item.state = 'merged'; | ||
} | ||
return item; | ||
}); | ||
|
||
async function aggregatePullRequests(author) { | ||
if (/[ :]/.test(author)) { | ||
throw new Error('Invalid author'); | ||
return await Promise.all(promises); | ||
} | ||
|
||
const pullRequests = await fetchPullRequests(author); | ||
const reduced = reducePullRequests(pullRequests); | ||
const augmented = await fetchRepositoryData(reduced, author); | ||
async aggregatePullRequests(author) { | ||
if (/[ :]/.test(author)) { | ||
throw new Error('Invalid author'); | ||
} | ||
|
||
const pullRequests = await this._fetchPullRequests(author); | ||
const reduced = GitHub._reducePullRequests(pullRequests); | ||
const augmented = await this._fetchRepositoryData(reduced, author); | ||
|
||
return sort(augmented); | ||
return GitHub._sortPullRequests(augmented); | ||
} | ||
} | ||
|
||
export default aggregatePullRequests; | ||
const aggregatePullRequests = (author, accessToken) => | ||
new GitHub(accessToken).aggregatePullRequests(author); | ||
|
||
export {aggregatePullRequests, getAccessToken, AuthorizationError}; |
Oops, something went wrong.