forked from lowlighter/metrics
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.mjs
More file actions
144 lines (132 loc) · 7.49 KB
/
index.mjs
File metadata and controls
144 lines (132 loc) · 7.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//Setup
export default async function({login, q, imports, rest, graphql, data, account, queries}, {enabled = false, extras = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
if ((!q.notable) || (!imports.metadata.plugins.notable.enabled(enabled, {extras})))
return null
//Load inputs
let {filter, skipped, repositories, types, from, indepth, self} = imports.metadata.plugins.notable.inputs({data, account, q})
skipped.push(...data.shared["repositories.skipped"])
//Iterate through contributed repositories
let contributions = []
{
let cursor = null
let pushed = 0
do {
console.debug(`metrics/compute/${login}/plugins > notable > retrieving contributed repositories after ${cursor}`)
const {user: {repositoriesContributedTo: {edges}}} = await graphql(queries.notable.contributions({login, types: types.map(x => x.toLocaleUpperCase()).join(", "), after: cursor ? `after: "${cursor}"` : "", self, repositories: data.shared["repositories.batch"] || 100}))
cursor = edges?.[edges?.length - 1]?.cursor
edges
.filter(({node}) => imports.filters.repo(node, skipped))
.filter(({node}) => ({all: true, organization: node.isInOrganization, user: !node.isInOrganization}[from]))
.filter(({node}) => imports.filters.github(filter, {name: node.nameWithOwner, user: node.owner.login, stars: node.stargazers.totalCount, watchers: node.watchers.totalCount, forks: node.forks.totalCount}))
.map(({node}) => contributions.push({handle: node.nameWithOwner, stars: node.stargazers.totalCount, issues: node.issues.totalCount, pulls: node.pullRequests.totalCount, organization: node.isInOrganization, avatarUrl: node.owner.avatarUrl}))
pushed = edges.length
} while ((pushed) && (cursor))
}
//Set contributions
contributions = await Promise.all(contributions.map(async ({handle, stars, issues, pulls, avatarUrl, organization}) => ({name: handle.split("/").shift(), handle, stars, issues, pulls, avatar: await imports.imgb64(avatarUrl), organization})))
console.debug(`metrics/compute/${login}/plugins > notable > found ${contributions.length} notable contributions`)
//Indepth
if ((indepth) && (imports.metadata.plugins.notable.extras("indepth", {extras}))) {
console.debug(`metrics/compute/${login}/plugins > notable > indepth`)
//Fetch issues
const issues = {}
if (types.includes("issue")) {
let cursor = null
let pushed = 0
do {
console.debug(`metrics/compute/${login}/plugins > notable > retrieving user issues after ${cursor}`)
const {user: {issues: {edges}}} = await graphql(queries.notable.issues({login, type: "issues", after: cursor ? `after: "${cursor}"` : ""}))
cursor = edges?.[edges?.length - 1]?.cursor
edges.map(({node: {repository: {nameWithOwner: repository}}}) => issues[repository] = (issues[repositories] ?? 0) + 1)
pushed = edges.length
} while ((pushed) && (cursor))
}
//Fetch pull requests
const pulls = {}
if (types.includes("pull_request")) {
let cursor = null
let pushed = 0
do {
console.debug(`metrics/compute/${login}/plugins > notable > retrieving user pull requests after ${cursor}`)
const {user: {pullRequests: {edges}}} = await graphql(queries.notable.issues({login, type: "pullRequests", after: cursor ? `after: "${cursor}"` : ""}))
cursor = edges?.[edges?.length - 1]?.cursor
edges.map(({node: {repository: {nameWithOwner: repository}}}) => pulls[repository] = (pulls[repositories] ?? 0) + 1)
pushed = edges.length
} while ((pushed) && (cursor))
}
//Fetch commits
for (const contribution of contributions) {
//Prepare data
const {handle, stars} = contribution
const [owner, repo] = handle.split("/")
try {
//Count total commits on repository
const {repository: {defaultBranchRef: {target: {history}}}} = await graphql(queries.notable.commits({owner, repo}))
contribution.history = history.totalCount
//Load maintainers (errors probably means that token is not allowed to list contributors hence not a maintainer of said repo)
const {data: collaborators} = await rest.repos.listCollaborators({owner, repo}).catch(() => ({data: []}))
const maintainers = collaborators.filter(({role_name: role}) => ["admin", "maintain", "write"].includes(role)).map(({login}) => login)
//Count total commits of user
const {data: contributions = []} = await rest.repos.getContributorsStats({owner, repo})
const commits = contributions?.filter(({author}) => author.login.toLocaleLowerCase() === login.toLocaleLowerCase()).reduce((a, {total: b}) => a + b, 0) ?? NaN
//Save user data
contribution.user = {
commits,
percentage: commits / contribution.history,
maintainer: maintainers.includes(login),
issues: issues[handle] ?? 0,
pulls: pulls[handle] ?? 0,
get stars() {
return Math.round(this.maintainer ? stars : this.percentage * stars)
},
}
console.debug(`metrics/compute/${login}/plugins > notable > indepth > successfully processed ${owner}/${repo}`)
}
catch (error) {
console.debug(error)
console.debug(`metrics/compute/${login}/plugins > notable > indepth > failed to compute for ${owner}/${repo}`)
}
}
}
//Aggregate contributions
console.debug(`metrics/compute/${login}/plugins > notable > aggregating results`)
const aggregated = new Map()
for (const {name, handle, avatar, organization = handle.split("/").shift() ?? "", stars, ..._extras} of contributions) {
const key = repositories ? handle : name
if (aggregated.has(key)) {
const aggregate = aggregated.get(key)
aggregate.aggregated++
if (indepth) {
const {history = 0, user: {commits = 0, percentage = 0, maintainer = false} = {}} = _extras
aggregate.history = aggregate.history ?? 0
aggregate.history += history
aggregate.user = aggregate.user ?? {}
aggregate.user.commits += commits
aggregate.user.percentage += percentage
aggregate.user.maintainer = aggregate.user.maintainer || maintainer
}
}
else {
aggregated.set(key, {name: key, handle, avatar, organization, stars, aggregated: 1, ..._extras})
}
}
contributions = [...aggregated.values()]
if (indepth) {
//Normalize contribution percentage
contributions.map(aggregate => aggregate.user ? aggregate.user.percentage /= aggregate.aggregated : null)
//Additional filtering (no user commits means that API wasn't able to answer back, considering it as matching by default)
contributions = contributions.filter(({handle, user}) => !user?.commits ? true : imports.filters.github(filter, {handle, commits: contributions.history, "commits.user": user.commits, "commits.user%": user.percentage * 100, maintainer: user.maintainer}))
//Sort contribution by maintainer first and then by contribution percentage
contributions = contributions.sort((a, b) => ((b.user?.percentage + b.user?.maintainer) || 0) - ((a.user?.percentage + a.user?.maintainer) || 0))
}
//Results
return {contributions, types}
}
//Handle errors
catch (error) {
throw imports.format.error(error)
}
}