Skip to content

Commit b06d9ff

Browse files
authored
Add Stargazers plugin (lowlighter#37)
1 parent 97c28b8 commit b06d9ff

File tree

15 files changed

+295
-12
lines changed

15 files changed

+295
-12
lines changed

.github/pull_request_template.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,30 @@
66
-->
77

88
**Pull request description**
9-
<!-- A clear and concise description of what your pull request implements or fixs. -->
9+
<!--
10+
A clear and concise description of what your pull request implements or fixs.
11+
12+
Please check the following:
13+
14+
> Documentation update
15+
- Check spelling
16+
- Respect current formatting
17+
18+
> New plugin
19+
- Created new plugin in /source/plugins/ with index.mjs as entry point
20+
- Added tests in /tests/metrics.test.js
21+
- Added mocked data if needed (required for all APIs which requires a token or limited in requests)
22+
- Updated action.yml with new plugin options
23+
- Updated /source/app/action/index.mjs to retrieve plugin options with correct typing
24+
- Updated /source/web/statics/* to support new plugin options
25+
- Updated /settings.example.json with new plugin name
26+
- Updated README.md to explain plugin features
27+
28+
> Code editions
29+
- Ensure retro-compatibility with previous versions
30+
- Unless feature is not released yet
31+
- Respect current formatting
32+
-->
1033

1134
**Additional context and screenshots**
1235
<!-- Add any other context or screenshots about your pull request here. -->

README.md

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,32 @@ But there's more with [plugins](https://github.com/lowlighter/metrics/tree/maste
147147
</td>
148148
</tr>
149149
<tr>
150+
<th><a href="https://github.com/lowlighter/metrics#-stargazers">✨ Stargazers evolution</a></th>
150151
<th><a href="https://github.com/lowlighter/metrics#-stars">🌟 Recently starred repositories</a></th>
151-
<th><a href="https://github.com/lowlighter/metrics#%EF%B8%8F-base-content">🗃️ Header special features</a></th>
152152
</tr>
153153
<tr>
154+
<td>
155+
<a href="https://github.com/lowlighter/metrics#-stargazers">
156+
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.stargazers.svg" alt="" width="400">
157+
</a>
158+
</td>
154159
<td>
155160
<a href="https://github.com/lowlighter/metrics#-stars">
156161
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.stars.svg" alt="" width="400">
157162
</a>
158163
</td>
164+
</tr>
165+
<tr>
166+
<th><a href="https://github.com/lowlighter/metrics#%EF%B8%8F-base-content">🗃️ Header special features</a></th>
167+
<th></th>
168+
</tr>
169+
<tr>
159170
<td>
160171
<a href="https://github.com/lowlighter/metrics#%EF%B8%8F-base-content">
161172
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.header.svg" alt="" width="400">
162173
</a>
163174
</td>
175+
<td></td>
164176
</tr>
165177
<tr>
166178
<td colspan="2" align="center">
@@ -516,6 +528,7 @@ Used template defaults to the `classic` one.
516528
<th><span title="Habits">💡</span></th>
517529
<th><span title="Gists">🎫</span></th>
518530
<th><span title="Stars">🌟</span></th>
531+
<th><span title="Stargazers">✨</span></th>
519532
</tr>
520533
<tr>
521534
<th>Classic</th>
@@ -534,6 +547,7 @@ Used template defaults to the `classic` one.
534547
<td>✔️</td>
535548
<td>✔️</td>
536549
<td><span title="Available on master">✔️<sup>M</sup></span></td>
550+
<td><span title="Available on master">✔️<sup>M</sup></span></td>
537551
</tr>
538552
<tr>
539553
<th>Terminal</th>
@@ -552,6 +566,7 @@ Used template defaults to the `classic` one.
552566
<td>❌</td>
553567
<td>✔️</td>
554568
<td>❌</td>
569+
<td>❌</td>
555570
</tr>
556571
<tr>
557572
<th>Repository<sup>R</sup></th>
@@ -570,6 +585,7 @@ Used template defaults to the `classic` one.
570585
<td>❌</td>
571586
<td>❌</td>
572587
<td>❌</td>
588+
<td><span title="Available on master">✔️<sup>M</sup></span></td>
573589
</tr>
574590
</table>
575591

@@ -584,7 +600,7 @@ Used template defaults to the `classic` one.
584600

585601
To use `repository` template, you'll need to provide a repository name in `query` option.
586602

587-
If repository owner is different from `token` owner, use `user` option to specify it.
603+
If repository owner is different from `token` owner, use `user` option to specify it.
588604

589605
Add the following to your workflow :
590606
```yaml
@@ -1281,6 +1297,29 @@ Add the following to your workflow :
12811297

12821298
</details>
12831299

1300+
### ✨ Stargazers
1301+
1302+
🚧 This feature is available on @master
1303+
1304+
The *stargazers* plugin displays your stargazers evolution across all of your repositories over the last two weeks.
1305+
1306+
![Stargazers plugin](https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.stargazers.svg)
1307+
1308+
<details>
1309+
<summary>💬 About</summary>
1310+
1311+
It will consume additional GitHub requests per repository per set of 100 stargazers.
1312+
1313+
Add the following to your workflow :
1314+
```yaml
1315+
- uses: lowlighter/metrics@latest
1316+
with:
1317+
# ... other options
1318+
plugin_stargazers: yes
1319+
```
1320+
1321+
</details>
1322+
12841323
### 🔧 Other options
12851324

12861325
A few additional options are available.

action.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,12 @@ inputs:
357357
description: Number of recently starred repositories to display
358358
default: 4
359359

360+
# Display stargazers evolution over the last two weeks
361+
# It shows total stargazers along with increase rate per day
362+
plugin_stagazers:
363+
description: Display stargazers evolution over the last two weeks
364+
default: no
365+
360366
# ====================================================================================
361367
# Options below are mostly used for testing
362368

settings.example.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@
6060
"token":null, "//":"Twitter token (required when enabled)"
6161
},
6262
"stars":{ "//":"Stars plugin",
63-
"enabled":true, "//":"Enable or disable recently starred repositories display"
63+
"enabled":false, "//":"Enable or disable recently starred repositories display"
64+
},
65+
"stargazers":{ "//":"Stargazers plugin",
66+
"enabled":false, "//":"Enable or disable stargazers charts display"
6467
}
6568
}
6669
}

source/app/action/index.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
projects:{enabled:input.bool("plugin_projects")},
141141
tweets:{enabled:input.bool("plugin_tweets")},
142142
stars:{enabled:input.bool("plugin_stars")},
143+
stargazers:{enabled:input.bool("plugin_stargazers")},
143144
}
144145
let q = Object.fromEntries(Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => [key, true]))
145146
info("Plugins enabled", Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => key))

source/app/mocks.mjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,26 @@
296296
}
297297
})
298298
}
299+
//Stargazers query
300+
if (/^query Stargazers /.test(query)) {
301+
console.debug(`metrics/compute/mocks > mocking graphql api result > Stargazers`)
302+
return /after: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/m.test(query) ? ({
303+
repository:{
304+
stargazers:{
305+
edges:[],
306+
}
307+
}
308+
}) : ({
309+
repository:{
310+
stargazers:{
311+
edges:new Array(Math.ceil(20+80*Math.random())).fill(null).map(() => ({
312+
starredAt:new Date(Date.now()-Math.floor(30*Math.random())*24*60*60*1000).toISOString(),
313+
cursor:"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
314+
}))
315+
}
316+
}
317+
})
318+
}
299319
//Unmocked call
300320
return target(...args)
301321
}

source/app/web/statics/app.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
projects:"🗂️ Projects",
4848
tweets:"🐤 Latest tweets",
4949
stars:"🌟 Recently starred repositories",
50+
stargazers:"✨ Stargazers over last weeks",
5051
"base.header":`
5152
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M0 8a8 8 0 1116 0v5.25a.75.75 0 01-1.5 0V8a6.5 6.5 0 10-13 0v5.25a.75.75 0 01-1.5 0V8zm5.5 4.25a.75.75 0 01.75-.75h3.5a.75.75 0 010 1.5h-3.5a.75.75 0 01-.75-.75zM3 6.75C3 5.784 3.784 5 4.75 5h6.5c.966 0 1.75.784 1.75 1.75v1.5A1.75 1.75 0 0111.25 10h-6.5A1.75 1.75 0 013 8.25v-1.5zm1.47-.53a.75.75 0 011.06 0l.97.97.97-.97a.75.75 0 011.06 0l.97.97.97-.97a.75.75 0 111.06 1.06l-1.5 1.5a.75.75 0 01-1.06 0L8 7.81l-.97.97a.75.75 0 01-1.06 0l-1.5-1.5a.75.75 0 010-1.06z"></path></svg>
5253
Header`,

source/app/web/statics/index.html

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
<!-- Title -->
1919
<h1 class="title">
20-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>
21-
<a href="https://github.com/lowlighter/metrics">Metrics v{{ version }}</a>
20+
<a href="https://github.com/lowlighter/metrics">
21+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>
22+
Metrics v{{ version }}
23+
</a>
2224
</h1>
2325

2426
<!-- Tabs -->
@@ -29,14 +31,14 @@ <h1 class="title">
2931
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M0 1.75A.75.75 0 01.75 1h4.253c1.227 0 2.317.59 3 1.501A3.744 3.744 0 0111.006 1h4.245a.75.75 0 01.75.75v10.5a.75.75 0 01-.75.75h-4.507a2.25 2.25 0 00-1.591.659l-.622.621a.75.75 0 01-1.06 0l-.622-.621A2.25 2.25 0 005.258 13H.75a.75.75 0 01-.75-.75V1.75zm8.755 3a2.25 2.25 0 012.25-2.25H14.5v9h-3.757c-.71 0-1.4.201-1.992.572l.004-7.322zm-1.504 7.324l.004-5.073-.002-2.253A2.25 2.25 0 005.003 2.5H1.5v9h3.757a3.75 3.75 0 011.994.574z"></path></svg>
3032
Overview
3133
</div>
32-
<div @click="tab = 'markdown'" class="tab" :class="{active:tab === 'markdown', disabled:!user}">
33-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4 1.75C4 .784 4.784 0 5.75 0h5.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0114.25 15h-9a.75.75 0 010-1.5h9a.25.25 0 00.25-.25V6h-2.75A1.75 1.75 0 0110 4.25V1.5H5.75a.25.25 0 00-.25.25v2.5a.75.75 0 01-1.5 0v-2.5zm7.5-.188V4.25c0 .138.112.25.25.25h2.688a.252.252 0 00-.011-.013l-2.914-2.914a.272.272 0 00-.013-.011zM5.72 6.72a.75.75 0 000 1.06l1.47 1.47-1.47 1.47a.75.75 0 101.06 1.06l2-2a.75.75 0 000-1.06l-2-2a.75.75 0 00-1.06 0zM3.28 7.78a.75.75 0 00-1.06-1.06l-2 2a.75.75 0 000 1.06l2 2a.75.75 0 001.06-1.06L1.81 9.25l1.47-1.47z"></path></svg>
34-
Markdown
35-
</div>
3634
<div @click="tab = 'action'" class="tab" :class="{active:tab === 'action', disabled:!user}">
3735
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zM6.379 5.227A.25.25 0 006 5.442v5.117a.25.25 0 00.379.214l4.264-2.559a.25.25 0 000-.428L6.379 5.227z"></path></svg>
3836
Action
3937
</div>
38+
<div @click="tab = 'markdown'" class="tab" :class="{active:tab === 'markdown', disabled:!user}">
39+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4 1.75C4 .784 4.784 0 5.75 0h5.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0114.25 15h-9a.75.75 0 010-1.5h9a.25.25 0 00.25-.25V6h-2.75A1.75 1.75 0 0110 4.25V1.5H5.75a.25.25 0 00-.25.25v2.5a.75.75 0 01-1.5 0v-2.5zm7.5-.188V4.25c0 .138.112.25.25.25h2.688a.252.252 0 00-.011-.013l-2.914-2.914a.272.272 0 00-.013-.011zM5.72 6.72a.75.75 0 000 1.06l1.47 1.47-1.47 1.47a.75.75 0 101.06 1.06l2-2a.75.75 0 000-1.06l-2-2a.75.75 0 00-1.06 0zM3.28 7.78a.75.75 0 00-1.06-1.06l-2 2a.75.75 0 000 1.06l2 2a.75.75 0 001.06-1.06L1.81 9.25l1.47-1.47z"></path></svg>
40+
Markdown
41+
</div>
4042
</div>
4143
</nav>
4244

@@ -242,7 +244,7 @@ <h4>{{ plugins.descriptions.projects }}</h4>
242244
</div>
243245
<!-- Overview -->
244246
<section class="preview" v-if="tab == 'overview'">
245-
Once generated, click on tabs above to see the code to embed them on your real readme !
247+
Once generated, click on tabs above to see the code to embed them on your real readme !<br>
246248
<template v-if="generated.content">
247249
<img class="metrics" :src="generated.content" alt="metrics">
248250
</template>

source/app/web/statics/style.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
background-color: var(--color-bg-canvas);
1111
display: flex;
1212
flex-direction: column;
13-
overflow: hidden;
1413
}
1514
/* Title */
1615
.title {
@@ -36,6 +35,7 @@
3635
display: flex;
3736
border-bottom: 1px solid var(--color-border-secondary);
3837
flex-shrink: 0;
38+
overflow-x: auto;
3939
}
4040
nav .tab {
4141
flex-shrink: 0;
@@ -287,6 +287,7 @@
287287
}
288288
nav {
289289
margin: 32px 0 24px;
290+
overflow: hidden;
290291
}
291292
nav .left {
292293
display: block;
@@ -297,6 +298,7 @@
297298
main {
298299
height: 100vh;
299300
width: 100vw;
301+
overflow: hidden;
300302
}
301303
.avatar {
302304
display: flex;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//Setup
2+
export default async function ({login, graphql, data, q, queries, imports}, {enabled = false} = {}) {
3+
//Plugin execution
4+
try {
5+
//Check if plugin is enabled and requirements are met
6+
if ((!enabled)||(!q.stargazers))
7+
return null
8+
//Retrieve stargazers from graphql api
9+
console.debug(`metrics/compute/${login}/plugins > stargazers > querying api`)
10+
const repositories = data.user.repositories.nodes.map(({name}) => name).slice(0, 2)
11+
const dates = []
12+
for (const repository of repositories) {
13+
//Iterate through stargazers
14+
console.debug(`metrics/compute/${login}/plugins > stargazers > retrieving stargazers of ${repository}`)
15+
let cursor = null
16+
let pushed = 0
17+
do {
18+
console.debug(`metrics/compute/${login}/plugins > stargazers > retrieving stargazers of ${repository} after ${cursor}`)
19+
const {repository:{stargazers:{edges}}} = await graphql(queries.stargazers({login, repository, after:cursor ? `after: "${cursor}"` : ""}))
20+
cursor = edges?.[edges?.length-1]?.cursor
21+
console.log(edges)
22+
dates.push(...edges.map(({starredAt}) => new Date(starredAt)))
23+
pushed = edges.length
24+
} while ((pushed)&&(cursor))
25+
//Limit repositories
26+
console.debug(`metrics/compute/${login}/plugins > stargazers > loaded ${dates.length} stargazers for ${repository}`)
27+
}
28+
console.debug(`metrics/compute/${login}/plugins > stargazers > loaded ${dates.length} stargazers in total`)
29+
//Compute stargazers increments
30+
const days = 14
31+
const increments = {dates:Object.fromEntries([...new Array(days).fill(null).map((_, i) => [new Date(Date.now()-i*24*60*60*1000).toISOString().slice(0, 10), 0]).reverse()]), max:NaN, min:NaN}
32+
dates
33+
.map(date => date.toISOString().slice(0, 10))
34+
.filter(date => date in increments.dates)
35+
.map(date => increments.dates[date]++)
36+
increments.min = Math.min(...Object.values(increments.dates))
37+
increments.max = Math.max(...Object.values(increments.dates))
38+
//Compute total stargazers
39+
let stargazers = data.computed.repositories.stargazers
40+
const total = {dates:{...increments.dates}, max:NaN, min:NaN}
41+
{
42+
const dates = Object.keys(total.dates)
43+
for (let i = dates.length-1; i >= 0; i--) {
44+
const date = dates[i], tomorrow = dates[i+1]
45+
stargazers -= (increments.dates[tomorrow] ?? 0)
46+
total.dates[date] = stargazers
47+
}
48+
}
49+
total.min = Math.min(...Object.values(total.dates))
50+
total.max = Math.max(...Object.values(total.dates))
51+
//Format values
52+
for (const date in increments.dates)
53+
increments.dates[date] = `${increments.dates[date] > 0 ? "+" : ""}${imports.format(increments.dates[date])}`
54+
for (const date in total.dates)
55+
total.dates[date] = imports.format(total.dates[date])
56+
//Months name
57+
const months = ["", "Jan.", "Feb.", "Mar.", "Apr.", "May", "June", "July", "Aug.", "Sep.", "Oct.", "Nov.", "Dec."]
58+
//Results
59+
return {total, increments, months}
60+
}
61+
//Handle errors
62+
catch (error) {
63+
throw {error:{message:"An error occured", instance:error}}
64+
}
65+
}

0 commit comments

Comments
 (0)