Skip to content

Commit 732d2a4

Browse files
authored
Add support for reactions plugin (lowlighter#180)
1 parent a839b0a commit 732d2a4

File tree

11 files changed

+230
-0
lines changed

11 files changed

+230
-0
lines changed

.github/readme/partials/references.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
* [ankurparihar/readme-pagespeed-insights](https://github.com/ankurparihar/readme-pagespeed-insights)
1414
* [jasonlong/isometric-contributions](https://github.com/jasonlong/isometric-contributions)
1515
* [jamesgeorge007/github-activity-readme](https://github.com/jamesgeorge007/github-activity-readme)
16+
* [vvo/sourcekarma](https://github.com/vvo/sourcekarma)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**Mocked data */
2+
export default function({faker, query, login = faker.internet.userName()}) {
3+
console.debug("metrics/compute/mocks > mocking graphql api result > reactions/default")
4+
const type = query.match(/(?<type>issues|issueComments)[(]/)?.groups?.type ?? "(unknown type)"
5+
return /after: "MOCKED_CURSOR"/m.test(query) ? ({
6+
user:{
7+
[type]:{
8+
edges:[],
9+
nodes:[],
10+
},
11+
},
12+
}) : ({
13+
user:{
14+
[type]:{
15+
edges:new Array(100).fill(null).map(_ => ({
16+
cursor:"MOCKED_CURSOR",
17+
node:{
18+
createdAt:faker.date.recent(),
19+
reactions:{
20+
nodes:new Array(50).fill(null).map(_ => ({content:faker.random.arrayElement(["HEART", "THUMBS_UP", "THUMBS_DOWN", "LAUGH", "CONFUSED", "EYES", "ROCKET", "HOORAY"])})),
21+
},
22+
},
23+
})),
24+
},
25+
},
26+
})
27+
}

source/app/web/statics/app.placeholder.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,24 @@
175175
comments:faker.random.number(1000)
176176
}
177177
}) : null),
178+
//Reactions
179+
...(set.plugins.enabled.reactions ? ({
180+
reactions:{
181+
list:{
182+
HEART:{value:faker.random.number(100), score:faker.random.number(100)/100},
183+
THUMBS_UP:{value:faker.random.number(100), score:faker.random.number(100)/100},
184+
THUMBS_DOWN:{value:faker.random.number(100), score:faker.random.number(100)/100},
185+
LAUGH:{value:faker.random.number(100), score:faker.random.number(100)/100},
186+
CONFUSED:{value:faker.random.number(100), score:faker.random.number(100)/100},
187+
EYES:{value:faker.random.number(100), score:faker.random.number(100)/100},
188+
ROCKET:{value:faker.random.number(100), score:faker.random.number(100)/100},
189+
HOORAY:{value:faker.random.number(100), score:faker.random.number(100)/100},
190+
},
191+
comments:options["reactions.limit"],
192+
details:options["reactions.details"],
193+
days:options["reactions.days"]
194+
}
195+
}) : null),
178196
//Introduction
179197
...(set.plugins.enabled.introduction ? ({
180198
introduction:{

source/plugins/reactions/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
### 🎭 Comment reactions
2+
3+
The *reactions* plugin displays overall reactions on your recent issues and issue comments.
4+
5+
<table>
6+
<td align="center">
7+
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.reactions.svg">
8+
<img width="900" height="1" alt="">
9+
</td>
10+
</table>
11+
12+
#### ℹ️ Examples workflows
13+
14+
[➡️ Available options for this plugin](metadata.yml)
15+
16+
```yaml
17+
- uses: lowlighter/metrics@latest
18+
with:
19+
# ... other options
20+
plugin_reactions: yes
21+
plugin_reactions_limit: 200 # Compute reactions over last 200 issue comments
22+
plugin_reactions_days: 14 # Compute reactions on issue comments posted less than 14 days ago
23+
plugin_reactions_details: percentage # Display reactions percentage
24+
```

source/plugins/reactions/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, q, imports, data, graphql, queries, account}, {enabled = false} = {}) {
3+
//Plugin execution
4+
try {
5+
//Check if plugin is enabled and requirements are met
6+
if ((!enabled)||(!q.reactions))
7+
return null
8+
9+
//Load inputs
10+
let {limit, days, details} = imports.metadata.plugins.reactions.inputs({data, account, q})
11+
12+
//Load issue comments
13+
let cursor = null, pushed = 0
14+
const comments = []
15+
for (const type of ["issues", "issueComments"]) {
16+
do {
17+
//Load issue comments
18+
console.debug(`metrics/compute/${login}/plugins > reactions > retrieving ${type} after ${cursor}`)
19+
const {user:{[type]:{edges}}} = await graphql(queries.reactions({login, type, after:cursor ? `after: "${cursor}"` : ""}))
20+
cursor = edges?.[edges?.length-1]?.cursor
21+
//Save issue comments
22+
const filtered = edges.flatMap(({node:{createdAt:created, reactions:{nodes:reactions}}}) => ({created:new Date(created), reactions:reactions.map(({content}) => content)})).filter(comment => Number.isFinite(days) ? comment.created < new Date(Date.now()-days*24*60*60*1000) : true)
23+
pushed = filtered.length
24+
comments.push(...filtered)
25+
console.debug(`metrics/compute/${login}/plugins > reactions > currently at ${comments.length} comments`)
26+
//Early break
27+
if ((comments.length >= limit)||(filtered.length < edges.length))
28+
break
29+
} while ((cursor)&&(pushed)&&(comments.length < limit))
30+
}
31+
32+
//Applying limit
33+
if (limit) {
34+
comments.splice(limit)
35+
console.debug(`metrics/compute/${login}/plugins > reactions > keeping only ${comments.length} comments`)
36+
}
37+
38+
//Format reactions list
39+
const list = {}
40+
const reactions = comments.flatMap(({reactions}) => reactions)
41+
for (const reaction of reactions)
42+
list[reaction] = (list[reaction] ?? 0) + 1
43+
for (const [key, value] of Object.entries(list))
44+
list[key] = {value, score:value/reactions.length}
45+
46+
//Results
47+
return {list, comments:comments.length, details, days}
48+
}
49+
//Handle errors
50+
catch (error) {
51+
throw {error:{message:"An error occured", instance:error}}
52+
}
53+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: "🎭 Comment reactions"
2+
cost: 1 GraphQL request per 100 issues and issues comments fetched
3+
categorie: github
4+
supports:
5+
- user
6+
inputs:
7+
8+
# Enable or disable plugin
9+
plugin_reactions:
10+
description: Display average issue comments reactions
11+
type: boolean
12+
default: no
13+
14+
# Maximum number of issue comments to parse
15+
# Issues will be fetched before issues comments
16+
plugin_reactions_limit:
17+
description: Maximum number of issue comments to parse
18+
type: number
19+
default: 200
20+
min: 1
21+
max: 1000
22+
23+
# Filter reactions by issue comments age
24+
# Set to 0 to disable age filtering
25+
plugin_reactions_days:
26+
description: Maximum issue comments age
27+
type: number
28+
default: 0
29+
min: 0
30+
31+
# Additional details
32+
plugin_reactions_details:
33+
description: Additional details
34+
type: string
35+
default: none
36+
values:
37+
- none
38+
- count
39+
- percentage
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
query ReactionsDefault {
2+
user(login: "$login") {
3+
login
4+
$type($after first: 100, orderBy: {field: UPDATED_AT, direction: DESC}) {
5+
edges {
6+
cursor
7+
node {
8+
createdAt
9+
reactions(last: 100, orderBy: {field: CREATED_AT, direction: DESC}) {
10+
nodes {
11+
content
12+
}
13+
}
14+
}
15+
}
16+
}
17+
}
18+
}

source/plugins/reactions/tests.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- name: Reactions plugin (default)
2+
uses: lowlighter/metrics@latest
3+
with:
4+
token: MOCKED_TOKEN
5+
plugin_reactions: yes

source/templates/classic/partials/_.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"stargazers",
2020
"people",
2121
"activity",
22+
"reactions",
2223
"anilist",
2324
"wakatime",
2425
"skyline",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<% if (plugins.reactions) { %>
2+
<section>
3+
<h2 class="field">
4+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 2.75a.25.25 0 01.25-.25h8.5a.25.25 0 01.25.25v5.5a.25.25 0 01-.25.25h-3.5a.75.75 0 00-.53.22L3.5 11.44V9.25a.75.75 0 00-.75-.75h-1a.25.25 0 01-.25-.25v-5.5zM1.75 1A1.75 1.75 0 000 2.75v5.5C0 9.216.784 10 1.75 10H2v1.543a1.457 1.457 0 002.487 1.03L7.061 10h3.189A1.75 1.75 0 0012 8.25v-5.5A1.75 1.75 0 0010.25 1h-8.5zM14.5 4.75a.25.25 0 00-.25-.25h-.5a.75.75 0 110-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0114.25 12H14v1.543a1.457 1.457 0 01-2.487 1.03L9.22 12.28a.75.75 0 111.06-1.06l2.22 2.22v-2.19a.75.75 0 01.75-.75h1a.25.25 0 00.25-.25v-5.5z"></path></svg>
5+
Overall users reactions from last <%= plugins.reactions?.comments %> comments
6+
</h2>
7+
<div class="row">
8+
<section>
9+
<% if (plugins.reactions.error) { %>
10+
<div class="field error">
11+
<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>
12+
<%= plugins.reactions.error.message %>
13+
</div>
14+
<% } else { %>
15+
<div class="row fill-width">
16+
<section class="categories">
17+
<% for (const [reaction, icon] of Object.entries({HEART:"❤️", THUMBS_UP:"👍", THUMBS_DOWN:"👎", LAUGH:"😄", CONFUSED:"😕", EYES:"👀", ROCKET:"🚀", HOORAY:"🎉"})) { const {score = 0, value:count = 0} = plugins.reactions.list[reaction] ?? {} %>
18+
<div class="categorie column">
19+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="50" height="50" class="gauge info">
20+
<circle class="gauge-base" r="53" cx="60" cy="60"></circle>
21+
<% if (score > 0) { %>
22+
<circle class="gauge-arc" transform="rotate(-90 60 60)" r="53" cx="60" cy="60" stroke-dasharray="<%= score * 329 %> 329"></circle>
23+
<text x="60" y="60" dominant-baseline="central"><%= icon %></text>
24+
<% } else { %>
25+
<text x="60" y="60" dominant-baseline="central"><%= icon %></text>
26+
<% } %>
27+
</svg>
28+
<% if (plugins.reactions.details === "percentage") { %>
29+
<span class="title"><%= Math.round(score*100) %><small>%</small></span>
30+
<% } else if (plugins.reactions.details === "count") { %>
31+
<span class="title"><%= count %></span>
32+
<% } %>
33+
</div>
34+
<% } %>
35+
</section>
36+
</div>
37+
<% } %>
38+
</section>
39+
</div>
40+
</section>
41+
<% } %>

0 commit comments

Comments
 (0)