feat(plugins/notable): add indepth mode (#635) [skip ci]

This commit is contained in:
Simon Lecoq
2021-11-04 00:24:17 -04:00
committed by GitHub
parent 57d4ebb9eb
commit 5498cdc8c1
5 changed files with 170 additions and 7 deletions

View File

@@ -1,5 +1,5 @@
//Setup
export default async function({login, q, imports, graphql, data, account, queries}, {enabled = false} = {}) {
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
@@ -7,10 +7,10 @@ export default async function({login, q, imports, graphql, data, account, querie
return null
//Load inputs
let {filter, repositories, from} = imports.metadata.plugins.notable.inputs({data, account, q})
let {filter, repositories, from, indepth} = imports.metadata.plugins.notable.inputs({data, account, q})
//Iterate through contributed repositories
const notable = new Map()
const commits = []
{
let cursor = null
let pushed = 0
@@ -21,15 +21,89 @@ export default async function({login, q, imports, graphql, data, account, querie
edges
.filter(({node}) => ({all:true, organization:node.isInOrganization, user:!node.isInOrganization}[from]))
.filter(({node}) => imports.ghfilter(filter, {name:node.nameWithOwner, user:node.owner.login, stars:node.stargazers.totalCount, watchers:node.watchers.totalCount, forks:node.forks.totalCount}))
.map(({node}) => notable.set((repositories || !node.isInOrganization) ? node.nameWithOwner : node.owner.login, {organization:node.isInOrganization, avatarUrl:node.owner.avatarUrl}))
.map(({node}) => commits.push({handle:node.nameWithOwner, stars:node.stargazers.totalCount, organization:node.isInOrganization, avatarUrl:node.owner.avatarUrl}))
pushed = edges.length
} while ((pushed) && (cursor))
}
//Set contributions
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))
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))
console.debug(`metrics/compute/${login}/plugins > notable > found ${contributions.length} notable contributions`)
//Extras features
if (extras) {
//Indepth
if (indepth) {
console.debug(`metrics/compute/${login}/plugins > notable > indepth`)
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)
//Save user data
contribution.user = {
commits,
percentage:commits/contribution.history,
maintainer:maintainers.includes(login),
get stars() {
return 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
if (from !== "all") {
console.debug(`metrics/compute/${login}/plugins > notable > aggregating results`)
contributions = contributions.filter(({organization}) => (from === "organization")&&(organization))
const aggregated = new Map()
for (const {name, handle, avatar, organization, stars, ..._extras} of contributions) {
const key = repositories ? handle : name
if (aggregated.has(key)) {
const aggregate = aggregated.get(key)
aggregate.aggregated++
if (extras) {
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 (extras) {
//Normalize contribution percentage
contributions.map(aggregate => aggregate.user ? aggregate.user.percentage /= aggregate.aggregated : null)
//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}
}

View File

@@ -36,4 +36,10 @@ inputs:
plugin_notable_repositories:
description: Also display repository name
type: boolean
default: no
# Compute notable contributions with measured impact
plugin_notable_indepth:
description: Indepth notable contributions processing
type: boolean
default: no

View File

@@ -0,0 +1,20 @@
{
repository(owner: "$owner", name: "$repo") {
...RepoFragment
}
}
fragment RepoFragment on Repository {
name
defaultBranchRef {
name
target {
... on Commit {
id
history(first: 0) {
totalCount
}
}
}
}
}

View File

@@ -16,10 +16,18 @@
<% } else { %>
<% if (plugins.notable.contributions.length) { %>
<div class="row organization contributions">
<% for (const {name, avatar, organization} of plugins.notable.contributions) { %>
<div class="organization contribution">
<% for (const {name, avatar, organization, user:{commits = 0, maintainer = false, percentage = 0} = {}} of plugins.notable.contributions) { %>
<div class="organization contribution <%= maintainer ? "s" : percentage > .2 ? "a" : percentage > .1 ? "b" : percentage > .05 ? "c" : "" %> ">
<img class="<%= organization ? "organization" : "" %> avatar" src="<%= avatar %>" width="16" height="16" />
<span class="name">@<%= name %></span>
<% if (commits) { %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="16" height="16" class="gauge">
<circle class="gauge-base" r="12.5" cx="15" cy="15"></circle>
<circle class="gauge-arc" transform="rotate(-90 15 15)" r="12.5" cx="15" cy="15" stroke-dasharray="<%= percentage * 329 %> 329"></circle>
<text x="15" y="15" dominant-baseline="central" ><%= commits %></text>
</svg>
<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>
<% } %>
</div>
<% } %>
</div>

View File

@@ -171,6 +171,61 @@
font-size: 12px;
background-color: #959da520;
}
.contribution.organization.s {
color: #EB355E;
background-color: #EB355E26;
border-color: #EB355E;
}
.contribution.organization.a {
color: #D79533;
background-color: #E7BD6926;
border-color: #E7BD69;
}
.contribution.organization.b {
color: #9D8FFF;
background-color: #9E91FF26;
border-color: #9E91FF;
}
.contribution.organization.c {
color: #58A6FF;
background-color: #58A6FF26;
border-color: #58A6FF;
}
.contribution .gauge-base, .contribution .gauge-arc {
stroke: currentColor;
stroke-width: 4;
}
.contribution .gauge text {
fill: currentColor;
font-size: 12px;
font-size: 15px;
letter-spacing: -2px;
}
.contribution .gauge {
margin: 0 4px;
color: inherit;
}
.contribution .commits-icon {
fill: currentColor;
width: 10px;
height: 10px;
margin-left: -9px;
margin-top: 8px;
color: inherit;
filter: brightness(.5);
}
.contribution.s .side {
background-color: #EB355E26;
}
.contribution.a .side {
background-color: #E7BD6926;
}
.contribution.b .side {
background-color: #9E91FF26;
}
.contribution.c .side {
background-color: #58A6FF26;
}
.contribution.organization .avatar {
margin: 0 4px;