Skip to content

Commit 5498cdc

Browse files
authoredNov 4, 2021
feat(plugins/notable): add indepth mode (#635) [skip ci]
1 parent 57d4ebb commit 5498cdc

File tree

5 files changed

+170
-7
lines changed

5 files changed

+170
-7
lines changed
 

‎source/plugins/notable/index.mjs

+79-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
//Setup
2-
export default async function({login, q, imports, graphql, data, account, queries}, {enabled = false} = {}) {
2+
export default async function({login, q, imports, rest, graphql, data, account, queries}, {enabled = false, extras = false} = {}) {
33
//Plugin execution
44
try {
55
//Check if plugin is enabled and requirements are met
66
if ((!enabled) || (!q.notable))
77
return null
88

99
//Load inputs
10-
let {filter, repositories, from} = imports.metadata.plugins.notable.inputs({data, account, q})
10+
let {filter, repositories, from, indepth} = imports.metadata.plugins.notable.inputs({data, account, q})
1111

1212
//Iterate through contributed repositories
13-
const notable = new Map()
13+
const commits = []
1414
{
1515
let cursor = null
1616
let pushed = 0
@@ -21,15 +21,89 @@ export default async function({login, q, imports, graphql, data, account, querie
2121
edges
2222
.filter(({node}) => ({all:true, organization:node.isInOrganization, user:!node.isInOrganization}[from]))
2323
.filter(({node}) => imports.ghfilter(filter, {name:node.nameWithOwner, user:node.owner.login, stars:node.stargazers.totalCount, watchers:node.watchers.totalCount, forks:node.forks.totalCount}))
24-
.map(({node}) => notable.set((repositories || !node.isInOrganization) ? node.nameWithOwner : node.owner.login, {organization:node.isInOrganization, avatarUrl:node.owner.avatarUrl}))
24+
.map(({node}) => commits.push({handle:node.nameWithOwner, stars:node.stargazers.totalCount, organization:node.isInOrganization, avatarUrl:node.owner.avatarUrl}))
2525
pushed = edges.length
2626
} while ((pushed) && (cursor))
2727
}
2828

2929
//Set contributions
30-
const contributions = (await Promise.all([...notable.entries()].map(async ([name, {avatarUrl, organization}]) => ({name, avatar:await imports.imgb64(avatarUrl), organization})))).sort((a, b) => a.name.localeCompare(b.name))
30+
let contributions = (await Promise.all(commits.map(async ({handle, stars, avatarUrl, organization}) => ({name:handle.split("/").shift(), handle, stars, avatar:await imports.imgb64(avatarUrl), organization})))).sort((a, b) => a.name.localeCompare(b.name))
3131
console.debug(`metrics/compute/${login}/plugins > notable > found ${contributions.length} notable contributions`)
3232

33+
//Extras features
34+
if (extras) {
35+
//Indepth
36+
if (indepth) {
37+
console.debug(`metrics/compute/${login}/plugins > notable > indepth`)
38+
for (const contribution of contributions) {
39+
//Prepare data
40+
const {handle, stars} = contribution
41+
const [owner, repo] = handle.split("/")
42+
try {
43+
//Count total commits on repository
44+
const {repository:{defaultBranchRef:{target:{history}}}} = await graphql(queries.notable.commits({owner, repo}))
45+
contribution.history = history.totalCount
46+
47+
//Load maintainers (errors probably means that token is not allowed to list contributors hence not a maintainer of said repo)
48+
const {data:collaborators} = await rest.repos.listCollaborators({owner, repo}).catch(() => ({data:[]}))
49+
const maintainers = collaborators.filter(({role_name:role}) => ["admin", "maintain", "write"].includes(role)).map(({login}) => login)
50+
51+
//Count total commits of user
52+
const {data:contributions = []} = await rest.repos.getContributorsStats({owner, repo})
53+
const commits = contributions.filter(({author}) => author.login.toLocaleLowerCase() === login.toLocaleLowerCase()).reduce((a, {total:b}) => a + b, 0)
54+
55+
//Save user data
56+
contribution.user = {
57+
commits,
58+
percentage:commits/contribution.history,
59+
maintainer:maintainers.includes(login),
60+
get stars() {
61+
return this.maintainer ? stars : this.percentage*stars
62+
}
63+
}
64+
console.debug(`metrics/compute/${login}/plugins > notable > indepth > successfully processed ${owner}/${repo}`)
65+
}
66+
catch (error) {
67+
console.debug(error)
68+
console.debug(`metrics/compute/${login}/plugins > notable > indepth > failed to compute for ${owner}/${repo}`)
69+
}
70+
}
71+
}
72+
}
73+
74+
//Aggregate contributions
75+
if (from !== "all") {
76+
console.debug(`metrics/compute/${login}/plugins > notable > aggregating results`)
77+
contributions = contributions.filter(({organization}) => (from === "organization")&&(organization))
78+
const aggregated = new Map()
79+
for (const {name, handle, avatar, organization, stars, ..._extras} of contributions) {
80+
const key = repositories ? handle : name
81+
if (aggregated.has(key)) {
82+
const aggregate = aggregated.get(key)
83+
aggregate.aggregated++
84+
if (extras) {
85+
const {history = 0, user:{commits = 0, percentage = 0, maintainer = false} = {}} = _extras
86+
aggregate.history = aggregate.history ?? 0
87+
aggregate.history += history
88+
aggregate.user = aggregate.user ?? {}
89+
aggregate.user.commits += commits
90+
aggregate.user.percentage += percentage
91+
aggregate.user.maintainer = aggregate.user.maintainer || maintainer
92+
}
93+
}
94+
else
95+
aggregated.set(key, {name:key, handle, avatar, organization, stars, aggregated:1, ..._extras})
96+
}
97+
contributions = [...aggregated.values()]
98+
if (extras) {
99+
//Normalize contribution percentage
100+
contributions.map(aggregate => aggregate.user ? aggregate.user.percentage /= aggregate.aggregated : null)
101+
//Sort contribution by maintainer first and then by contribution percentage
102+
contributions = contributions.sort((a, b) => ((b.user?.percentage + b.user?.maintainer) || 0) - ((a.user?.percentage + a.user?.maintainer) || 0))
103+
}
104+
}
105+
106+
33107
//Results
34108
return {contributions}
35109
}

‎source/plugins/notable/metadata.yml

+6
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,10 @@ inputs:
3636
plugin_notable_repositories:
3737
description: Also display repository name
3838
type: boolean
39+
default: no
40+
41+
# Compute notable contributions with measured impact
42+
plugin_notable_indepth:
43+
description: Indepth notable contributions processing
44+
type: boolean
3945
default: no
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
repository(owner: "$owner", name: "$repo") {
3+
...RepoFragment
4+
}
5+
}
6+
7+
fragment RepoFragment on Repository {
8+
name
9+
defaultBranchRef {
10+
name
11+
target {
12+
... on Commit {
13+
id
14+
history(first: 0) {
15+
totalCount
16+
}
17+
}
18+
}
19+
}
20+
}

‎source/templates/classic/partials/notable.ejs

+10-2
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,18 @@
1616
<% } else { %>
1717
<% if (plugins.notable.contributions.length) { %>
1818
<div class="row organization contributions">
19-
<% for (const {name, avatar, organization} of plugins.notable.contributions) { %>
20-
<div class="organization contribution">
19+
<% for (const {name, avatar, organization, user:{commits = 0, maintainer = false, percentage = 0} = {}} of plugins.notable.contributions) { %>
20+
<div class="organization contribution <%= maintainer ? "s" : percentage > .2 ? "a" : percentage > .1 ? "b" : percentage > .05 ? "c" : "" %> ">
2121
<img class="<%= organization ? "organization" : "" %> avatar" src="<%= avatar %>" width="16" height="16" />
2222
<span class="name">@<%= name %></span>
23+
<% if (commits) { %>
24+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="16" height="16" class="gauge">
25+
<circle class="gauge-base" r="12.5" cx="15" cy="15"></circle>
26+
<circle class="gauge-arc" transform="rotate(-90 15 15)" r="12.5" cx="15" cy="15" stroke-dasharray="<%= percentage * 329 %> 329"></circle>
27+
<text x="15" y="15" dominant-baseline="central" ><%= commits %></text>
28+
</svg>
29+
<svg class="commits-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M10.5 7.75a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm1.43.75a4.002 4.002 0 01-7.86 0H.75a.75.75 0 110-1.5h3.32a4.001 4.001 0 017.86 0h3.32a.75.75 0 110 1.5h-3.32z"></path></svg>
30+
<% } %>
2331
</div>
2432
<% } %>
2533
</div>

‎source/templates/classic/style.css

+55
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,61 @@
171171
font-size: 12px;
172172
background-color: #959da520;
173173
}
174+
.contribution.organization.s {
175+
color: #EB355E;
176+
background-color: #EB355E26;
177+
border-color: #EB355E;
178+
}
179+
.contribution.organization.a {
180+
color: #D79533;
181+
background-color: #E7BD6926;
182+
border-color: #E7BD69;
183+
}
184+
.contribution.organization.b {
185+
color: #9D8FFF;
186+
background-color: #9E91FF26;
187+
border-color: #9E91FF;
188+
}
189+
.contribution.organization.c {
190+
color: #58A6FF;
191+
background-color: #58A6FF26;
192+
border-color: #58A6FF;
193+
}
194+
.contribution .gauge-base, .contribution .gauge-arc {
195+
stroke: currentColor;
196+
stroke-width: 4;
197+
}
198+
.contribution .gauge text {
199+
fill: currentColor;
200+
font-size: 12px;
201+
font-size: 15px;
202+
letter-spacing: -2px;
203+
}
204+
.contribution .gauge {
205+
margin: 0 4px;
206+
color: inherit;
207+
}
208+
.contribution .commits-icon {
209+
fill: currentColor;
210+
width: 10px;
211+
height: 10px;
212+
margin-left: -9px;
213+
margin-top: 8px;
214+
color: inherit;
215+
filter: brightness(.5);
216+
}
217+
.contribution.s .side {
218+
background-color: #EB355E26;
219+
}
220+
.contribution.a .side {
221+
background-color: #E7BD6926;
222+
}
223+
.contribution.b .side {
224+
background-color: #9E91FF26;
225+
}
226+
.contribution.c .side {
227+
background-color: #58A6FF26;
228+
}
174229

175230
.contribution.organization .avatar {
176231
margin: 0 4px;

0 commit comments

Comments
 (0)
Failed to load comments.