feat(plugins/notable): add indepth mode (#635) [skip ci]
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
//Setup
|
//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
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//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
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//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
|
//Iterate through contributed repositories
|
||||||
const notable = new Map()
|
const commits = []
|
||||||
{
|
{
|
||||||
let cursor = null
|
let cursor = null
|
||||||
let pushed = 0
|
let pushed = 0
|
||||||
@@ -21,15 +21,89 @@ export default async function({login, q, imports, graphql, data, account, querie
|
|||||||
edges
|
edges
|
||||||
.filter(({node}) => ({all:true, organization:node.isInOrganization, user:!node.isInOrganization}[from]))
|
.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}))
|
.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
|
pushed = edges.length
|
||||||
} while ((pushed) && (cursor))
|
} while ((pushed) && (cursor))
|
||||||
}
|
}
|
||||||
|
|
||||||
//Set contributions
|
//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`)
|
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
|
//Results
|
||||||
return {contributions}
|
return {contributions}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,3 +37,9 @@ inputs:
|
|||||||
description: Also display repository name
|
description: Also display repository name
|
||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
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 { %>
|
<% } else { %>
|
||||||
<% if (plugins.notable.contributions.length) { %>
|
<% if (plugins.notable.contributions.length) { %>
|
||||||
<div class="row organization contributions">
|
<div class="row organization contributions">
|
||||||
<% for (const {name, avatar, organization} of plugins.notable.contributions) { %>
|
<% for (const {name, avatar, organization, user:{commits = 0, maintainer = false, percentage = 0} = {}} of plugins.notable.contributions) { %>
|
||||||
<div class="organization contribution">
|
<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" />
|
<img class="<%= organization ? "organization" : "" %> avatar" src="<%= avatar %>" width="16" height="16" />
|
||||||
<span class="name">@<%= name %></span>
|
<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>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -171,6 +171,61 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
background-color: #959da520;
|
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 {
|
.contribution.organization .avatar {
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
|
|||||||
Reference in New Issue
Block a user