Skip to content

Commit 4ef1b73

Browse files
authored
Add people plugin (lowlighter#48)
1 parent c5723ad commit 4ef1b73

File tree

12 files changed

+279
-7
lines changed

12 files changed

+279
-7
lines changed

README.md

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,19 +164,36 @@ But there's more with [plugins](https://github.com/lowlighter/metrics/tree/maste
164164
</tr>
165165
<tr>
166166
<th><a href="https://github.com/lowlighter/metrics#-gists">🎫 Gists plugin</a></th>
167-
<th><a href="https://github.com/lowlighter/metrics#%EF%B8%8F-base-content">🗃️ Header special features</a></th>
167+
<th><a href="https://github.com/lowlighter/metrics#-people">🧑‍🤝‍🧑 People plugin <sup><code>🚧 @master</code></sup></a></th>
168168
</tr>
169169
<tr>
170170
<td>
171171
<a href="https://github.com/lowlighter/metrics#-gists">
172172
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.gists.svg" alt="" width="400">
173173
</a>
174174
</td>
175+
<td>
176+
<a href="https://github.com/lowlighter/metrics#-people">
177+
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.people.followers.svg" alt="" width="400">
178+
</a>
179+
<details><summary>Followed people version</summary>
180+
<a href="https://github.com/lowlighter/metrics#-habits">
181+
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.people.following.svg" alt="" width="400">
182+
</a>
183+
</details>
184+
</td>
185+
</tr>
186+
<tr>
187+
<th><a href="https://github.com/lowlighter/metrics#%EF%B8%8F-base-content">🗃️ Header special features</a></th>
188+
<th></th>
189+
</tr>
190+
<tr>
175191
<td>
176192
<a href="https://github.com/lowlighter/metrics#%EF%B8%8F-base-content">
177193
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.header.svg" alt="" width="400">
178194
</a>
179195
</td>
196+
<td></td>
180197
</tr>
181198
<tr>
182199
<td colspan="2" align="center">
@@ -534,6 +551,7 @@ Used template defaults to the `classic` one.
534551
<th><span title="Stars">🌟</span></th>
535552
<th><span title="Stargazers">✨</span></th>
536553
<th><span title="Gists">🎫</span></th>
554+
<th><span title="People">🧑‍🤝‍🧑</span></th>
537555
</tr>
538556
<tr>
539557
<th>Classic</th>
@@ -550,10 +568,11 @@ Used template defaults to the `classic` one.
550568
<td data-for="tweets">✔️</td>
551569
<td data-for="posts">✔️</td>
552570
<td data-for="habits">✔️</td>
553-
<th><span title="Available on @master">✔️<sup>M</sup></span></th>
571+
<td data-for="activity"><span title="Available on @master">✔️<sup>M</sup></span></td>
554572
<td data-for="stars">✔️</td>
555573
<td data-for="stargazers">✔️</td>
556574
<td data-for="gists">✔️</td>
575+
<td data-for="people"><span title="Available on @master">✔️<sup>M</sup></span></td>
557576
</tr>
558577
<tr>
559578
<th>Terminal</th>
@@ -574,6 +593,7 @@ Used template defaults to the `classic` one.
574593
<td data-for="stars">❌</td>
575594
<td data-for="stargazers">❌</td>
576595
<td data-for="gists">✔️</td>
596+
<td data-for="people">❌</td>
577597
</tr>
578598
<tr>
579599
<th>Repository<sup>R</sup></th>
@@ -594,6 +614,7 @@ Used template defaults to the `classic` one.
594614
<td data-for="stars">❌</td>
595615
<td data-for="stargazers">✔️</td>
596616
<td data-for="gists">❌</td>
617+
<td data-for="people">❌</td>
597618
</tr>
598619
</table>
599620

@@ -1381,6 +1402,41 @@ Add the following to your workflow :
13811402

13821403
</details>
13831404

1405+
### 🧑‍🤝‍🧑 People
1406+
1407+
🚧 This plugin is available as pre-release on @master branch (unstable)
1408+
1409+
The *people* plugin displays your followers and followed users' avatars.
1410+
1411+
![People plugin](https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.people.svg)
1412+
1413+
<details>
1414+
<summary>💬 About</summary>
1415+
1416+
It will consume an additional GitHub request per group of 100 users fetched.
1417+
1418+
Add the following to your workflow :
1419+
```yaml
1420+
- uses: lowlighter/metrics@master
1421+
with:
1422+
# ... other options
1423+
plugin_people: yes
1424+
plugin_people_types: followers, following
1425+
plugin_people_limit: 28
1426+
plugin_people_size: 28 # Size in pixels of displayed avatars
1427+
```
1428+
1429+
It is possible to use [identicons](https://github.blog/2013-08-14-identicons/) instead of their avatar for privacy purposes.
1430+
1431+
```yaml
1432+
- uses: lowlighter/metrics@master
1433+
with:
1434+
# ... other options
1435+
plugin_people_identicons: yes
1436+
```
1437+
1438+
</details>
1439+
13841440
### 🔧 Other options
13851441

13861442
A few additional options are available.

action.yml

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ inputs:
377377
description: Number of activity events to display
378378
default: 5
379379

380-
# Disacard older events
380+
# Discard older events
381381
# Use 0 to display activity whatever the date
382382
plugin_activity_days:
383383
description: Maximum activity event age
@@ -400,6 +400,35 @@ inputs:
400400
description: Events to display
401401
default: all
402402

403+
# Display followed and following users
404+
plugin_people:
405+
description: Display
406+
default: no
407+
408+
# Limit the number of users displayed
409+
plugin_people_limit:
410+
description: Number of users to display per categorie
411+
default: 28
412+
413+
# Configure image size of users' avatar
414+
plugin_people_size:
415+
description: Size of users' avatars
416+
default: 28
417+
418+
# List of users categories to display (comma separated)
419+
# Supported values are:
420+
# - "followers"
421+
# - "following"
422+
plugin_people_types:
423+
description: Categories to display
424+
default: followers, following
425+
426+
# Display GitHub identicons instead of users' real avatar
427+
# Mostly for privacy purposes
428+
plugin_people_identicons:
429+
description: Use identicons instead of real avatars
430+
default: no
431+
403432
# ====================================================================================
404433
# Options below are mostly used for testing
405434

@@ -497,7 +526,7 @@ runs:
497526
set -e
498527
echo "Is released version: $METRICS_IS_RELEASED"
499528
# Rebuild image for unreleased version
500-
if [[ $METRICS_IS_RELEASED ]]; then
529+
if [[ "$METRICS_IS_RELEASED" -gt "0" ]]; then
501530
echo "Using released version $METRICS_TAG, will pull docker image from GitHub registry"
502531
METRICS_IMAGE=ghcr.io/lowlighter/metrics:$METRICS_TAG
503532
# Use registry for released version

settings.example.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
},
6868
"activity":{ "//":"Activity plugin",
6969
"enabled":false, "//":"Enable or disable recent activity display"
70+
},
71+
"people":{ "//":"People plugin",
72+
"enabled":false, "//":"Enable or disable people display"
7073
}
7174
}
7275
}

source/app/action/index.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@
142142
stars:{enabled:input.bool("plugin_stars")},
143143
stargazers:{enabled:input.bool("plugin_stargazers")},
144144
activity:{enabled:input.bool("plugin_activity")},
145+
people:{enabled:input.bool("plugin_people")},
145146
}
146147
let q = Object.fromEntries(Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => [key, true]))
147148
info("Plugins enabled", Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => key))
@@ -223,6 +224,15 @@
223224
for (const option of ["filter"])
224225
info(`Activity ${option}`, q[`activity.${option}`] = input.array(`plugin_activity_${option}`))
225226
}
227+
//People
228+
if (plugins.people.enabled) {
229+
for (const option of ["limit", "size"])
230+
info(`People ${option}`, q[`people.${option}`] = input.number(`plugin_people_${option}`))
231+
for (const option of ["types"])
232+
info(`People ${option}`, q[`people.${option}`] = input.array(`plugin_people_${option}`))
233+
for (const option of ["identicons"])
234+
info(`People ${option}`, q[`people.${option}`] = input.bool(`plugin_people_${option}`))
235+
}
226236

227237
//Repositories to use
228238
const repositories = input.number("repositories")

source/app/metrics.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
//Additional SVG transformations
9191
if (/svg/.test(mime)) {
9292
//Optimize rendering
93-
if ((conf.optimize)&&(!q.raw)) {
93+
if ((conf.settings?.optimize)&&(!q.raw)) {
9494
console.debug(`metrics/compute/${login} > optimize`)
9595
const svgo = new SVGO({full:true, plugins:[{cleanupAttrs:true}, {inlineStyles:false}]})
9696
const {data:optimized} = await svgo.optimize(rendered)

source/app/mocks.mjs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,30 @@
316316
}
317317
})
318318
}
319+
//People query
320+
if (/^query People /.test(query)) {
321+
console.debug(`metrics/compute/mocks > mocking graphql api result > People`)
322+
const type = query.match(/(?<type>followers|following)[(]/)?.groups?.type ?? "(unknown type)"
323+
return /after: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/m.test(query) ? ({
324+
user:{
325+
[type]:{
326+
edges:[],
327+
}
328+
}
329+
}) : ({
330+
user:{
331+
[type]:{
332+
edges:new Array(Math.ceil(20+80*Math.random())).fill(null).map(() => ({
333+
cursor:"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
334+
node:{
335+
login:"user",
336+
avatarUrl:"https://github.com/identicons/user.png",
337+
}
338+
}))
339+
}
340+
}
341+
})
342+
}
319343
//Unmocked call
320344
return target(...args)
321345
}

source/plugins/people/index.mjs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//Setup
2+
export default async function ({login, graphql, q, queries, imports}, {enabled = false} = {}) {
3+
//Plugin execution
4+
try {
5+
//Check if plugin is enabled and requirements are met
6+
if ((!enabled)||(!q.people))
7+
return null
8+
9+
//Parameters override
10+
let {"people.limit":limit = 28, "people.types":types = "followers, following", "people.size":size = 28, "people.identicons":identicons = false} = q
11+
//Limit
12+
limit = Math.max(1, limit)
13+
//Repositories projects
14+
types = decodeURIComponent(types ?? "").split(",").map(type => type.trim()).filter(type => ["followers", "following"].includes(type)) ?? []
15+
16+
//Retrieve followers from graphql api
17+
console.debug(`metrics/compute/${login}/plugins > people > querying api`)
18+
const result = {followers:[], following:[]}
19+
for (const type of types) {
20+
//Iterate through people
21+
console.debug(`metrics/compute/${login}/plugins > people > retrieving ${type}`)
22+
let cursor = null
23+
let pushed = 0
24+
do {
25+
console.debug(`metrics/compute/${login}/plugins > people > retrieving ${type} after ${cursor}`)
26+
const {user:{[type]:{edges}}} = await graphql(queries.people({login, type, size, after:cursor ? `after: "${cursor}"` : ""}))
27+
cursor = edges?.[edges?.length-1]?.cursor
28+
result[type].push(...edges.map(({node}) => node))
29+
pushed = edges.length
30+
} while ((pushed)&&(cursor))
31+
//Limit people
32+
if (limit > 0) {
33+
console.debug(`metrics/compute/${login}/plugins > people > keeping only ${limit} ${type}`)
34+
result[type].splice(limit)
35+
}
36+
//Hide real avator with identicons if enabled
37+
if (identicons) {
38+
console.debug(`metrics/compute/${login}/plugins > people > using identicons`)
39+
result[type].map(user => user.avatarUrl = `https://github.com/identicons/${user.login}.png`)
40+
}
41+
//Convert avatars to base64
42+
console.debug(`metrics/compute/${login}/plugins > people > loading avatars`)
43+
await Promise.all(result[type].map(async user => user.avatar = await imports.imgb64(user.avatarUrl)))
44+
}
45+
46+
//Results
47+
return {types, size, ...result}
48+
}
49+
//Handle errors
50+
catch (error) {
51+
throw {error:{message:"An error occured", instance:error}}
52+
}
53+
}

source/plugins/projects/index.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//Parameters override
99
let {"projects.limit":limit = 4, "projects.repositories":repositories = ""} = q
1010
//Repositories projects
11-
repositories = repositories?.split(",").map(repository => repository.trim()).filter(repository => /[-\w]+[/][-\w]+[/]projects[/]\d+/.test(repository)) ?? []
11+
repositories = decodeURIComponent(repositories ?? "").split(",").map(repository => repository.trim()).filter(repository => /[-\w]+[/][-\w]+[/]projects[/]\d+/.test(repository)) ?? []
1212
//Limit
1313
limit = Math.max(repositories.length, Math.min(100, Number(limit)))
1414
//Retrieve user owned projects from graphql api

source/queries/people.graphql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
query People {
2+
user(login: "$login") {
3+
login
4+
$type($after first: 100) {
5+
edges {
6+
cursor
7+
node {
8+
login
9+
avatarUrl(size: $size)
10+
}
11+
}
12+
}
13+
}
14+
}

source/templates/classic/image.svg

Lines changed: 60 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)