People plugin : repository template support (#78)

This commit is contained in:
Simon Lecoq
2021-01-25 22:08:29 +01:00
committed by GitHub
parent c075d49e76
commit 4879ed0136
14 changed files with 343 additions and 63 deletions

View File

@@ -181,6 +181,21 @@ But there's more with [plugins](https://github.com/lowlighter/metrics/tree/maste
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.people.following.svg" alt="" width="400"> <img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.people.following.svg" alt="" width="400">
</a> </a>
</details> </details>
<details><summary>Special thanks version</summary>
<a href="#-habits">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.people.thanks.svg" alt="" width="400">
</a>
</details>
<details><summary>Repository template version</summary>
<a href="#-habits">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.people.repository.svg" alt="" width="400">
</a>
</details>
<details><summary>Sponsorships version</summary>
<a href="#-habits">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.people.sponsorships.svg" alt="" width="400">
</a>
</details>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -1528,7 +1543,8 @@ Add the following to your workflow:
### 🧑‍🤝‍🧑 People ### 🧑‍🤝‍🧑 People
The *people* plugin displays your followers and followed users' avatars. The *people* plugin can display people you're following or sponsoring, and also users who're following or sponsoring you.
In repository mode, it's possible to display sponsors, stargazers, watchers.
![People plugin](https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.people.svg) ![People plugin](https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.people.svg)
@@ -1537,6 +1553,24 @@ The *people* plugin displays your followers and followed users' avatars.
It will consume an additional GitHub request per group of 100 users fetched. It will consume an additional GitHub request per group of 100 users fetched.
The following types are supported:
| Type | Alias | User metrics | Repository metrics |
| --------------- | ------------------------------------ | :----------------: | :----------------: |
| `followers` | | ✔️ | ❌ |
| `following` | `followed` | ✔️ | ❌ |
| `sponsoring`* | `sponsored`, `sponsorshipsAsSponsor` | ✔️ | ❌ |
| `sponsors`* | `sponsorshipsAsMaintainer` | ✔️ | ✔️ |
| `contributors`* | | ❌ | ✔️ |
| `stargazers`* | | ❌ | ✔️ |
| `watchers`* | | ❌ | ✔️ |
| `thanks`* | | ✔️ | ✔️ |
🚧 Types marked with * are available as pre-release on @master branch (unstable)
Sections will be ordered the same as specified in `plugin_people_types`.
`sponsors` for repositories will output the same as the owner's sponsors.
Add the following to your workflow: Add the following to your workflow:
```yaml ```yaml
- uses: lowlighter/metrics@latest - uses: lowlighter/metrics@latest
@@ -1557,6 +1591,17 @@ It is possible to use [identicons](https://github.blog/2013-08-14-identicons/) i
plugin_people_identicons: yes plugin_people_identicons: yes
``` ```
🚧 This feature is available as pre-release on @master branch (unstable)
It is possible to thanks personally users by adding the following to your workflow:
```yaml
- uses: lowlighter/metrics@master
with:
# ... other options
plugin_people_types: thanks
plugin_people_thanks: github-user-1, github-user-2, ...
```
</details> </details>
### 🌸 Anilist ### 🌸 Anilist

View File

@@ -469,13 +469,29 @@ inputs:
default: 28 default: 28
# List of users categories to display (comma separated) # List of users categories to display (comma separated)
# Supported values are: # For user's metrics, supported values are:
# - "followers" # - "followers"
# - "following" # - "following"/"followed"
# - "sponsors"/"sponsorshipsAsMaintainer"
# - "sponsoring"/"sponsored"/"sponsorshipsAsSponsor"
# - "thanks" (see "plugin_people_thanks" below)
# For repositories' metrics, supported values are:
# - "contributors"
# - "stargazers"
# - "watchers"
# - "sponsors"/"sponsorshipsAsMaintainer"
# - "thanks" (see "plugin_people_thanks" below)
plugin_people_types: plugin_people_types:
description: Categories to display description: Categories to display
default: followers, following default: followers, following
# List of users to thanks (comma seperated)
# When using "thanks" as a type, it'll display the users you listed in this option
# This can be used to create "Special thanks" badges that you can embed elsewhere
plugin_people_thanks:
description: Users to thanks in "thanks" section type
default: ""
# Display GitHub identicons instead of users' real avatar # Display GitHub identicons instead of users' real avatar
# Mostly for privacy purposes # Mostly for privacy purposes
plugin_people_identicons: plugin_people_identicons:

View File

@@ -239,7 +239,7 @@
if (plugins.people.enabled) { if (plugins.people.enabled) {
for (const option of ["limit", "size"]) for (const option of ["limit", "size"])
info(`People ${option}`, q[`people.${option}`] = input.number(`plugin_people_${option}`)) info(`People ${option}`, q[`people.${option}`] = input.number(`plugin_people_${option}`))
for (const option of ["types"]) for (const option of ["types", "thanks"])
info(`People ${option}`, q[`people.${option}`] = input.array(`plugin_people_${option}`)) info(`People ${option}`, q[`people.${option}`] = input.array(`plugin_people_${option}`))
for (const option of ["identicons"]) for (const option of ["identicons"])
info(`People ${option}`, q[`people.${option}`] = input.bool(`plugin_people_${option}`)) info(`People ${option}`, q[`people.${option}`] = input.bool(`plugin_people_${option}`))

View File

@@ -362,7 +362,7 @@
}) : ({ }) : ({
user:{ user:{
[type]:{ [type]:{
edges:new Array(Math.ceil(20+80*Math.random())).fill(undefined).map((login = faker.internet.userName()) => ({ edges:new Array(Math.ceil(20+80*Math.random())).fill(null).map((login = faker.internet.userName()) => ({
cursor:"MOCKED_CURSOR", cursor:"MOCKED_CURSOR",
node:{ node:{
login, login,
@@ -373,6 +373,66 @@
} }
}) })
} }
//People query (repositories)
if (/^query PeopleRepository /.test(query)) {
console.debug(`metrics/compute/mocks > mocking graphql api result > People`)
const type = query.match(/(?<type>stargazers|watchers)[(]/)?.groups?.type ?? "(unknown type)"
return /after: "MOCKED_CURSOR"/m.test(query) ? ({
user:{
repository:{
[type]:{
edges:[],
}
}
}
}) : ({
user:{
repository:{
[type]:{
edges:new Array(Math.ceil(20+80*Math.random())).fill(null).map((login = faker.internet.userName()) => ({
cursor:"MOCKED_CURSOR",
node:{
login,
avatarUrl:null,
}
}))
}
}
}
})
}
//People sponsors query
if (/^query PeopleSponsors /.test(query)) {
console.debug(`metrics/compute/mocks > mocking graphql api result > People`)
const type = query.match(/(?<type>sponsorshipsAsSponsor|sponsorshipsAsMaintainer)[(]/)?.groups?.type ?? "(unknown type)"
return /after: "MOCKED_CURSOR"/m.test(query) ? ({
user:{
login,
[type]:{
edges:[]
}
}
}) : ({
user:{
login,
[type]:{
edges:new Array(Math.ceil(20+80*Math.random())).fill(null).map((login = faker.internet.userName()) => ({
cursor:"MOCKED_CURSOR",
node:{
sponsorEntity:{
login:faker.internet.userName(),
avatarUrl:null,
},
sponsorable:{
login:faker.internet.userName(),
avatarUrl:null,
}
}
}))
}
}
})
}
//Unmocked call //Unmocked call
return target(...args) return target(...args)
} }
@@ -389,6 +449,8 @@
getViews:rest.repos.getViews, getViews:rest.repos.getViews,
getContributorsStats:rest.repos.getContributorsStats, getContributorsStats:rest.repos.getContributorsStats,
listCommits:rest.repos.listCommits, listCommits:rest.repos.listCommits,
listContributors:rest.repos.listContributors,
getByUsername:rest.users.getByUsername,
} }
//Raw request //Raw request
@@ -893,6 +955,49 @@
}) })
} }
}) })
//Repository contributors
rest.repos.listContributors = new Proxy(unmocked.listContributors, {
apply:function(target, that, [{owner, repo}]) {
console.debug(`metrics/compute/mocks > mocking rest api result > rest.repos.listContributors`)
return ({
status:200,
url:`https://api.github.com/repos/${owner}/${repo}/contributors`,
headers: {
server:"GitHub.com",
status:"200 OK",
"x-oauth-scopes":"repo",
},
data:new Array(40+faker.random.number(60)).fill(null).map(() => ({
login:faker.internet.userName(),
avatar_url:null,
contributions:faker.random.number(1000),
}))
})
}
})
//User informations
rest.users.getByUsername = new Proxy(unmocked.getByUsername, {
apply:function(target, that, [{username}]) {
console.debug(`metrics/compute/mocks > mocking rest api result > rest.repos.getByUsername`)
return ({
status:200,
url:`'https://api.github.com/users/${username}/`,
headers: {
server:"GitHub.com",
status:"200 OK",
"x-oauth-scopes":"repo",
},
data:{
login:faker.internet.userName(),
avatar_url:null,
contributions:faker.random.number(1000),
}
})
}
})
} }
//Axios mocking //Axios mocking

View File

@@ -76,7 +76,7 @@
stars:"🌟 Recently starred repositories", stars:"🌟 Recently starred repositories",
stargazers:"✨ Stargazers over last weeks", stargazers:"✨ Stargazers over last weeks",
activity:"📰 Recent activity", activity:"📰 Recent activity",
people:"🧑‍🤝‍🧑 Followers and followed", people:"🧑‍🤝‍🧑 People",
anilist:"🌸 Anilist", anilist:"🌸 Anilist",
base:"🗃️ Base content", base:"🗃️ Base content",
"base.header":"Header", "base.header":"Header",
@@ -120,6 +120,7 @@
"people.size":{text:"Limit", type:"number", min:16, max:64}, "people.size":{text:"Limit", type:"number", min:16, max:64},
"people.limit":{text:"Limit", type:"number", min:1, max:9999}, "people.limit":{text:"Limit", type:"number", min:1, max:9999},
"people.types":{text:"Types", placeholder:"followers, following"}, "people.types":{text:"Types", placeholder:"followers, following"},
"people.thanks":{text:"Special thanks", placeholder:"user1, user2, ..."},
"people.identicons":{text:"Use identicons", type:"boolean"}, "people.identicons":{text:"Use identicons", type:"boolean"},
"anilist.medias":{text:"Medias to display", placeholder:"anime, manga"}, "anilist.medias":{text:"Medias to display", placeholder:"anime, manga"},
"anilist.sections":{text:"Sections to display", placeholder:"favorites, watching, reading, characters"}, "anilist.sections":{text:"Sections to display", placeholder:"favorites, watching, reading, characters"},
@@ -157,6 +158,7 @@
"people.size":28, "people.size":28,
"people.limit":28, "people.limit":28,
"people.types":"followers, following", "people.types":"followers, following",
"people.thanks":"",
"people.identicons":false, "people.identicons":false,
"anilist.medias":"anime, manga", "anilist.medias":"anime, manga",
"anilist.sections":"favorites", "anilist.sections":"favorites",

View File

@@ -222,17 +222,25 @@
}) : null), }) : null),
//People //People
...(set.plugins.enabled.people ? ({ ...(set.plugins.enabled.people ? ({
people:{ get people() {
types:options["people.types"].split(",").map(x => x.trim()), const types = options["people.types"].split(",").map(x => x.trim())
size:options["people.size"], .map(x => ({followed:"following", sponsors:"sponsorshipsAsMaintainer", sponsored:"sponsorshipsAsSponsor", sponsoring:"sponsorshipsAsSponsor"})[x] ?? x)
followers:new Array(Number(options["people.limit"])).fill(null).map(_ => ({ .filter(x => ["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor"].includes(x))
login:faker.internet.userName(), return {
avatar:"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==", types,
})), size:options["people.size"],
following:new Array(Number(options["people.limit"])).fill(null).map(_ => ({ ...(Object.fromEntries(types.map(type => [
login:faker.internet.userName(), type,
avatar:"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==", new Array(Number(options["people.limit"])).fill(null).map(_ => ({
})) login:faker.internet.userName(),
avatar:"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
}))
]))),
thanks:options["people.thanks"].split(",").map(x => x.trim()).map(login => ({
login,
avatar:"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
}))
}
} }
}) : null), }) : null),
//Music //Music

View File

@@ -1,33 +1,66 @@
//Setup //Setup
export default async function ({login, graphql, q, queries, imports}, {enabled = false} = {}) { export default async function ({login, data, graphql, rest, q, queries, imports}, {enabled = 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
if ((!enabled)||(!q.people)) if ((!enabled)||(!q.people))
return null return null
//Context
let context = {
mode:"user",
types:["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "thanks"],
default:"followers, following",
alias:{followed:"following", sponsors:"sponsorshipsAsMaintainer", sponsored:"sponsorshipsAsSponsor", sponsoring:"sponsorshipsAsSponsor"},
sponsorships:{sponsorshipsAsMaintainer:"sponsorEntity", sponsorshipsAsSponsor:"sponsorable"}
}
if (q.repo) {
console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`)
const {owner, repo} = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})).shift()
context = {...context, mode:"repo", types:["contributors", "stargazers", "watchers", "sponsorshipsAsMaintainer", "thanks"], default:"stargazers, watchers", owner, repo}
}
//Parameters override //Parameters override
let {"people.limit":limit = 28, "people.types":types = "followers, following", "people.size":size = 28, "people.identicons":identicons = false} = q let {"people.limit":limit = 28, "people.types":types = context.default, "people.size":size = 28, "people.identicons":identicons = false, "people.thanks":thanks = []} = q
//Limit //Limit
limit = Math.max(1, limit) limit = Math.max(1, limit)
//Repositories projects //Repositories projects
types = decodeURIComponent(types ?? "").split(",").map(type => type.trim()).filter(type => ["followers", "following"].includes(type)) ?? [] types = [...new Set(decodeURIComponent(types ?? "").split(",").map(type => type.trim()).map(type => (context.alias[type] ?? type)).filter(type => context.types.includes(type)) ?? [])]
//Special thanks
thanks = decodeURIComponent(thanks ?? "").split(",").map(user => user.trim()).filter(user => user)
//Retrieve followers from graphql api //Retrieve followers from graphql api
console.debug(`metrics/compute/${login}/plugins > people > querying api`) console.debug(`metrics/compute/${login}/plugins > people > querying api`)
const result = {followers:[], following:[]} const result = Object.fromEntries(types.map(type => [type, []]))
for (const type of types) { for (const type of types) {
//Iterate through people //Iterate through people
console.debug(`metrics/compute/${login}/plugins > people > retrieving ${type}`) console.debug(`metrics/compute/${login}/plugins > people > retrieving ${type}`)
let cursor = null //Rest
let pushed = 0 if (type === "contributors") {
do { const {owner, repo} = context
console.debug(`metrics/compute/${login}/plugins > people > retrieving ${type} after ${cursor}`) const {data:nodes} = await rest.repos.listContributors({owner, repo})
const {user:{[type]:{edges}}} = await graphql(queries.people({login, type, size, after:cursor ? `after: "${cursor}"` : ""})) result[type].push(...nodes.map(({login, avatar_url}) => ({login, avatarUrl:avatar_url})))
cursor = edges?.[edges?.length-1]?.cursor }
result[type].push(...edges.map(({node}) => node)) else if (type === "thanks") {
pushed = edges.length const nodes = await Promise.all(thanks.map(async username => (await rest.users.getByUsername({username})).data))
} while ((pushed)&&(cursor)) result[type].push(...nodes.map(({login, avatar_url}) => ({login, avatarUrl:avatar_url})))
}
//GraphQL
else {
let cursor = null
let pushed = 0
do {
console.debug(`metrics/compute/${login}/plugins > people > retrieving ${type} after ${cursor}`)
const {[type]:{edges}} = (
type in context.sponsorships ? (await graphql(queries["people.sponsors"]({login:context.owner ?? login, type, size, after:cursor ? `after: "${cursor}"` : "", target:context.sponsorships[type]}))).user :
context.mode === "repo" ? (await graphql(queries["people.repository"]({login:context.owner, repository:context.repo, type, size, after:cursor ? `after: "${cursor}"` : ""}))).user.repository :
(await graphql(queries.people({login, type, size, after:cursor ? `after: "${cursor}"` : ""}))).user
)
cursor = edges?.[edges?.length-1]?.cursor
result[type].push(...edges.map(({node}) => node[context.sponsorships[type]] ?? node))
pushed = edges.length
} while ((pushed)&&(cursor)&&(result[type].length <= limit))
}
//Limit people //Limit people
if (limit > 0) { if (limit > 0) {
console.debug(`metrics/compute/${login}/plugins > people > keeping only ${limit} ${type}`) console.debug(`metrics/compute/${login}/plugins > people > keeping only ${limit} ${type}`)

View File

@@ -1,14 +1,13 @@
query Repository { query PeopleRepository {
user(login: "$login") { user(login: "$login") {
repository(name: "$repository") { repository(name: "$repository") {
$type(first: 100) { $type($after first: 100) {
pageInfo { edges {
hasNextPage cursor
endCursor node {
} login
nodes { avatarUrl(size: $size)
avatarUrl(size: 24) }
login
} }
} }
} }

View File

@@ -0,0 +1,18 @@
query PeopleSponsors {
user(login: "$login") {
login
$type($after first: 100) {
edges {
cursor
node {
$target {
... on User {
login
avatarUrl(size: $size)
}
}
}
}
}
}
}

View File

@@ -15,11 +15,15 @@
</div> </div>
</section> </section>
<% } else { %> <% } else { %>
<% if (plugins.people.types?.includes("followers")) { %> <% for (const type of plugins.people.types) { %>
<section> <section>
<h2 class="field"> <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="M5.5 3.5a2 2 0 100 4 2 2 0 000-4zM2 5.5a3.5 3.5 0 115.898 2.549 5.507 5.507 0 013.034 4.084.75.75 0 11-1.482.235 4.001 4.001 0 00-7.9 0 .75.75 0 01-1.482-.236A5.507 5.507 0 013.102 8.05 3.49 3.49 0 012 5.5zM11 4a.75.75 0 100 1.5 1.5 1.5 0 01.666 2.844.75.75 0 00-.416.672v.352a.75.75 0 00.574.73c1.2.289 2.162 1.2 2.522 2.372a.75.75 0 101.434-.44 5.01 5.01 0 00-2.56-3.012A3 3 0 0011 4z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M5.5 3.5a2 2 0 100 4 2 2 0 000-4zM2 5.5a3.5 3.5 0 115.898 2.549 5.507 5.507 0 013.034 4.084.75.75 0 11-1.482.235 4.001 4.001 0 00-7.9 0 .75.75 0 01-1.482-.236A5.507 5.507 0 013.102 8.05 3.49 3.49 0 012 5.5zM11 4a.75.75 0 100 1.5 1.5 1.5 0 01.666 2.844.75.75 0 00-.416.672v.352a.75.75 0 00.574.73c1.2.289 2.162 1.2 2.522 2.372a.75.75 0 101.434-.44 5.01 5.01 0 00-2.56-3.012A3 3 0 0011 4z"></path></svg>
<%= user.followers.totalCount %> follower<%= s(user.followers.totalCount) %> <% if (type === "thanks") { %>
Special thanks
<% } else { %>
<%= user[type].totalCount %> <%= {followers:`follower${s(user[type].totalCount)}`, following:"followed", sponsorshipsAsSponsor:"sponsored", sponsorshipsAsMaintainer:`sponsor${s(user[type].totalCount)}`}[type] %>
<% } %>
</h2> </h2>
<div class="row"> <div class="row">
<section class="people"> <section class="people">
@@ -29,27 +33,7 @@
<%= plugins.people.error.message %> <%= plugins.people.error.message %>
</div> </div>
<% } else { %> <% } else { %>
<% for (const user of plugins.people.followers) { %><img class="avatar" src="data:image/png;base64,<%= user.avatar %>" width="<%= plugins.people.size %>" height="<%= plugins.people.size %>" alt="" /><% } %> <% for (const user of plugins.people[type]) { %><img class="avatar" src="data:image/png;base64,<%= user.avatar %>" width="<%= plugins.people.size %>" height="<%= plugins.people.size %>" alt="" /><% } %>
<% } %>
</section>
</div>
</section>
<% } %>
<% if (plugins.people.types?.includes("following")) { %>
<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="M5.5 3.5a2 2 0 100 4 2 2 0 000-4zM2 5.5a3.5 3.5 0 115.898 2.549 5.507 5.507 0 013.034 4.084.75.75 0 11-1.482.235 4.001 4.001 0 00-7.9 0 .75.75 0 01-1.482-.236A5.507 5.507 0 013.102 8.05 3.49 3.49 0 012 5.5zM11 4a.75.75 0 100 1.5 1.5 1.5 0 01.666 2.844.75.75 0 00-.416.672v.352a.75.75 0 00.574.73c1.2.289 2.162 1.2 2.522 2.372a.75.75 0 101.434-.44 5.01 5.01 0 00-2.56-3.012A3 3 0 0011 4z"></path></svg>
<%= user.following.totalCount %> followed
</h2>
<div class="row">
<section class="people">
<% if (plugins.people.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.people.error.message %>
</div>
<% } else { %>
<% for (const user of plugins.people.following) { %><img class="avatar" src="data:image/png;base64,<%= user.avatar %>" width="<%= plugins.people.size %>" height="<%= plugins.people.size %>" alt="" /><% } %>
<% } %> <% } %>
</section> </section>
</div> </div>

View File

@@ -4,5 +4,6 @@
"languages", "languages",
"projects", "projects",
"pagespeed", "pagespeed",
"stargazers" "stargazers",
"people"
] ]

View File

@@ -0,0 +1,43 @@
<% if (plugins.people) { %>
<% if (plugins.people.error) { %>
<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="M5.5 3.5a2 2 0 100 4 2 2 0 000-4zM2 5.5a3.5 3.5 0 115.898 2.549 5.507 5.507 0 013.034 4.084.75.75 0 11-1.482.235 4.001 4.001 0 00-7.9 0 .75.75 0 01-1.482-.236A5.507 5.507 0 013.102 8.05 3.49 3.49 0 012 5.5zM11 4a.75.75 0 100 1.5 1.5 1.5 0 01.666 2.844.75.75 0 00-.416.672v.352a.75.75 0 00.574.73c1.2.289 2.162 1.2 2.522 2.372a.75.75 0 101.434-.44 5.01 5.01 0 00-2.56-3.012A3 3 0 0011 4z"></path></svg>
People
</h2>
<div class="row">
<section>
<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.people.error.message %>
</div>
</section>
</div>
</section>
<% } else { %>
<% for (const type of plugins.people.types) { %>
<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="M5.5 3.5a2 2 0 100 4 2 2 0 000-4zM2 5.5a3.5 3.5 0 115.898 2.549 5.507 5.507 0 013.034 4.084.75.75 0 11-1.482.235 4.001 4.001 0 00-7.9 0 .75.75 0 01-1.482-.236A5.507 5.507 0 013.102 8.05 3.49 3.49 0 012 5.5zM11 4a.75.75 0 100 1.5 1.5 1.5 0 01.666 2.844.75.75 0 00-.416.672v.352a.75.75 0 00.574.73c1.2.289 2.162 1.2 2.522 2.372a.75.75 0 101.434-.44 5.01 5.01 0 00-2.56-3.012A3 3 0 0011 4z"></path></svg>
<% if (type === "thanks") { %>
Special thanks
<% } else { %>
<%= repo[type].totalCount %> <%= {watchers:`watcher${s(repo[type].totalCount)}`, stargazers:`stargazer${s(repo[type].totalCount)}`, contributors:`contributor${s(repo[type].totalCount)}`, sponsorshipsAsSponsor:"sponsored", sponsorshipsAsMaintainer:`sponsor${s(repo[type].totalCount)}`}[type] %>
<% } %>
</h2>
<div class="row">
<section class="people">
<% if (plugins.people.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.people.error.message %>
</div>
<% } else { %>
<% for (const user of plugins.people[type]) { %><img class="avatar" src="data:image/png;base64,<%= user.avatar %>" width="<%= plugins.people.size %>" height="<%= plugins.people.size %>" alt="" /><% } %>
<% } %>
</section>
</div>
</section>
<% } %>
<% } %>
<% } %>

View File

@@ -15,6 +15,11 @@
console.debug(`metrics/compute/${login}/${repo} > retrieving single repository ${repo}`) console.debug(`metrics/compute/${login}/${repo} > retrieving single repository ${repo}`)
const {user:{repository}} = await graphql(queries.repository({login, repo})) const {user:{repository}} = await graphql(queries.repository({login, repo}))
data.user.repositories.nodes = [repository] data.user.repositories.nodes = [repository]
data.repo = repository
//Contributors and sponsors
data.repo.contributors = {totalCount:(await rest.repos.listContributors({owner:data.repo.owner.login, repo})).data.length}
data.repo.sponsorshipsAsMaintainer = data.user.sponsorshipsAsMaintainer
//Get commit activity //Get commit activity
console.debug(`metrics/compute/${login}/${repo} > querying api for commits`) console.debug(`metrics/compute/${login}/${repo} > querying api for commits`)

View File

@@ -286,6 +286,27 @@
plugin_people:true, plugin_people:true,
plugin_people_types:"following", plugin_people_types:"following",
}, {skip:["terminal", "repository"]}], }, {skip:["terminal", "repository"]}],
["People plugin (sponsoring)", {
plugin_people:true,
plugin_people_types:"sponsoring",
}, {skip:["terminal", "repository"]}],
["People plugin (sponsors)", {
plugin_people:true,
plugin_people_types:"sponsors",
}, {skip:["terminal"]}],
["People plugin (stargazers)", {
plugin_people:true,
plugin_people_types:"stargazers",
}, {skip:["classic", "terminal"]}],
["People plugin (watchers)", {
plugin_people:true,
plugin_people_types:"watchers",
}, {skip:["classic", "terminal"]}],
["People plugin (thanks)", {
plugin_people:true,
plugin_people_types:"thanks",
plugin_people_thanks:"lowlighter",
}, {skip:["classic", "terminal"]}],
["People plugin (identicons)", { ["People plugin (identicons)", {
plugin_people:true, plugin_people:true,
plugin_people_identicons:true, plugin_people_identicons:true,