Add contributors plugin (#126)

This commit is contained in:
Simon Lecoq
2021-02-17 13:28:59 +01:00
committed by GitHub
parent 050fb2e330
commit fdf8b8f97b
10 changed files with 215 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
/**Mocked data */
export default function({faker, query, login = faker.internet.userName()}) {
console.debug("metrics/compute/mocks > mocking graphql api result > contributors/commit")
return ({
repository:{
object:{
oid:"MOCKED_SHA",
abbreviatedOid:"MOCKED_SHA",
messageHeadline:faker.lorem.sentence(),
committedDate:faker.date.recent(),
},
},
})
}

View File

@@ -11,13 +11,20 @@
}, },
data:page < 2 ? new Array(per_page).fill(null).map(() => ({ data:page < 2 ? new Array(per_page).fill(null).map(() => ({
sha:"MOCKED_SHA", sha:"MOCKED_SHA",
get author() {
return this.commit.author
},
commit:{ commit:{
author:{ author:{
name:owner, name:owner,
login:faker.internet.userName(),
avatar_url:null,
date:`${faker.date.recent(14)}`, date:`${faker.date.recent(14)}`,
}, },
committer:{ committer:{
name:owner, name:owner,
login:faker.internet.userName(),
avatar_url:null,
date:`${faker.date.recent(14)}`, date:`${faker.date.recent(14)}`,
}, },
}, },

View File

@@ -0,0 +1,25 @@
### 🏅 Contributors
The *contributors* plugin lets you display repositories contributors from a commit range, that can be specified through either sha, tags, branch, etc.
It's especially useful to acknowledge contributors on release notes.
<table>
<td align="center">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.contributors.svg">
<img width="900" height="1" alt="">
</td>
</table>
#### Examples workflows
[➡️ Available options for this plugin](metadata.yml)
```yaml
- uses: lowlighter/metrics@latest
with:
# ... other options
plugin_contributors: yes
plugin_contributors_base: "" # Base reference (commit, tag, branch, etc.)
plugin_contributors_head: master # Head reference (commit, tag, branch, etc.)
```

View File

@@ -0,0 +1,69 @@
//Setup
export default async function({login, q, imports, data, rest, graphql, queries, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
if ((!enabled)||(!q.contributors))
return null
//Load inputs
let {head, base} = imports.metadata.plugins.contributors.inputs({data, account, q})
const repo = {owner:data.repo.owner.login, repo:data.repo.name}
//Retrieve head and base commits
console.debug(`metrics/compute/${login}/plugins > contributors > querying api head and base commits`)
const ref = {
head:(await graphql(queries.contributors.commit({...repo, expression:head}))).repository.object,
base:(await graphql(queries.contributors.commit({...repo, expression:base}))).repository.object,
}
//Get commit activity
console.debug(`metrics/compute/${login}/plugins > contributors > querying api for commits between [${ref.base?.abbreviatedOid ?? null}] and [${ref.head?.abbreviatedOid ?? null}]`)
const commits = []
for (let page = 0; ; page++) {
console.debug(`metrics/compute/${login}/plugins > contributors > loading page ${page}`)
try {
const {data:loaded} = await rest.repos.listCommits({...repo, per_page:100, page})
if (loaded.map(({sha}) => sha).includes(ref.base?.oid)) {
console.debug(`metrics/compute/${login}/plugins > contributors > reached ${ref.base?.oid}`)
commits.push(...loaded.slice(0, loaded.map(({sha}) => sha).indexOf(ref.base.oid)))
break
}
if (!loaded.length) {
console.debug(`metrics/compute/${login}/plugins > contributors > no more page to load`)
break
}
commits.push(...loaded)
}
catch (error) {
if (/Git Repository is empty/.test(error))
break
throw error
}
}
//Remove commits after head
const start = Math.max(0, commits.map(({sha}) => sha).indexOf(ref.head?.oid))
commits.splice(0, start)
console.debug(`metrics/compute/${login}/plugins > contributors > ${commits.length} commits loaded (${start} removed)`)
//Compute contributors and contributions
let contributors = {}
for (const {author:{login, avatar_url:avatar}} of commits) {
if (!login)
continue
if (!(login in contributors))
contributors[login] = {avatar:avatar ? await imports.imgb64(avatar) : "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==", contributions:0}
else
contributors[login].contributions++
}
contributors = Object.fromEntries(Object.entries(contributors).sort((a, b) => b.contributions - a.contributions))
//Results
return {head, base, ref, list:contributors}
}
//Handle errors
catch (error) {
throw {error:{message:"An error occured", instance:error}}
}
}

View File

@@ -0,0 +1,23 @@
name: "🏅 Contributors"
cost: N/A
supports:
- repository
inputs:
# Enable or disable plugin
plugin_contributors:
description: Display repository contributors
type: boolean
default: no
# Base reference (commit, tag, branch, etc.)
plugin_contributors_base:
description: Base reference
type: string
default: ""
# Head reference (commit, tag, branch, etc.)
plugin_contributors_head:
description: Head reference
type: string
default: master

View File

@@ -0,0 +1,12 @@
query ContributorsCommit {
repository(owner: "$owner" name: "$repo") {
object(expression: "$expression") {
... on Commit {
oid
abbreviatedOid
messageHeadline
committedDate
}
}
}
}

View File

@@ -0,0 +1,13 @@
- name: Contributors plugin (default)
uses: lowlighter/metrics@latest
with:
token: MOCKED_TOKEN
plugin_contributors: yes
- name: Contributors plugin (default)
uses: lowlighter/metrics@latest
with:
token: MOCKED_TOKEN
plugin_contributors: yes
plugin_contributors_head: MOCKED_SHA
plugin_contributors_base: MOCKED_SHA

View File

@@ -686,6 +686,27 @@
fill: #58a6ff; fill: #58a6ff;
} }
/* Contributors */
.contributors {
display: flex;
flex-wrap: wrap;
margin-left: 6px;
}
.contributors .label {
padding-left: 0;
display: flex;
align-items: center;
position: relative;
}
.contributors .label img {
margin-left: 0;
}
.contributors .contributions {
position: absolute;
bottom: 100%;
right: 100%;
}
/* Fade animation */ /* Fade animation */
.af { .af {
opacity: 0; opacity: 0;

View File

@@ -7,5 +7,6 @@
"stargazers", "stargazers",
"people", "people",
"activity", "activity",
"contributors",
"licenses" "licenses"
] ]

View File

@@ -0,0 +1,30 @@
<% if (plugins.contributors) { %>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"></path></svg>
Contributors
<% if (plugins.contributors.base || plugins.contributors.ref?.base?.abbreviatedOid) { %>
from <%= plugins.contributors.base || plugins.contributors.ref?.base?.abbreviatedOid %> to <%= plugins.contributors.head || plugins.contributors.ref?.head?.abbreviatedOid %>
<% } else if (plugins.contributors.head || plugins.contributors.ref?.head?.abbreviatedOid) { %>
of <%= plugins.contributors.head || plugins.contributors.ref?.head?.abbreviatedOid %>
<% } %>
</h2>
<section>
<div class="contributors fill-width">
<% if (plugins.contributors.error) { %>
<div class="field error">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
<%= plugins.contributors.error.message %>
</div>
<% } else { %>
<% for (const [login, {avatar}] of Object.entries(plugins.contributors.list)) { %>
<div class="label">
<img class="avatar" src="data:image/png;base64,<%= avatar %>" width="22" height="22" alt="" />
<%= login %>
</div>
<% } %>
<% } %>
</div>
</section>
</section>
<% } %>