feat(plugins/notable): add indepth mode (#635) [skip ci]
This commit is contained in:
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
20
source/plugins/notable/queries/commits.graphql
Normal file
20
source/plugins/notable/queries/commits.graphql
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user