Add support for reactions plugin (#180)
This commit is contained in:
1
.github/readme/partials/references.md
vendored
1
.github/readme/partials/references.md
vendored
@@ -13,3 +13,4 @@
|
|||||||
* [ankurparihar/readme-pagespeed-insights](https://github.com/ankurparihar/readme-pagespeed-insights)
|
* [ankurparihar/readme-pagespeed-insights](https://github.com/ankurparihar/readme-pagespeed-insights)
|
||||||
* [jasonlong/isometric-contributions](https://github.com/jasonlong/isometric-contributions)
|
* [jasonlong/isometric-contributions](https://github.com/jasonlong/isometric-contributions)
|
||||||
* [jamesgeorge007/github-activity-readme](https://github.com/jamesgeorge007/github-activity-readme)
|
* [jamesgeorge007/github-activity-readme](https://github.com/jamesgeorge007/github-activity-readme)
|
||||||
|
* [vvo/sourcekarma](https://github.com/vvo/sourcekarma)
|
||||||
|
|||||||
27
source/app/mocks/api/github/graphql/reactions.default.mjs
Normal file
27
source/app/mocks/api/github/graphql/reactions.default.mjs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**Mocked data */
|
||||||
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
|
console.debug("metrics/compute/mocks > mocking graphql api result > reactions/default")
|
||||||
|
const type = query.match(/(?<type>issues|issueComments)[(]/)?.groups?.type ?? "(unknown type)"
|
||||||
|
return /after: "MOCKED_CURSOR"/m.test(query) ? ({
|
||||||
|
user:{
|
||||||
|
[type]:{
|
||||||
|
edges:[],
|
||||||
|
nodes:[],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) : ({
|
||||||
|
user:{
|
||||||
|
[type]:{
|
||||||
|
edges:new Array(100).fill(null).map(_ => ({
|
||||||
|
cursor:"MOCKED_CURSOR",
|
||||||
|
node:{
|
||||||
|
createdAt:faker.date.recent(),
|
||||||
|
reactions:{
|
||||||
|
nodes:new Array(50).fill(null).map(_ => ({content:faker.random.arrayElement(["HEART", "THUMBS_UP", "THUMBS_DOWN", "LAUGH", "CONFUSED", "EYES", "ROCKET", "HOORAY"])})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -175,6 +175,24 @@
|
|||||||
comments:faker.random.number(1000)
|
comments:faker.random.number(1000)
|
||||||
}
|
}
|
||||||
}) : null),
|
}) : null),
|
||||||
|
//Reactions
|
||||||
|
...(set.plugins.enabled.reactions ? ({
|
||||||
|
reactions:{
|
||||||
|
list:{
|
||||||
|
HEART:{value:faker.random.number(100), score:faker.random.number(100)/100},
|
||||||
|
THUMBS_UP:{value:faker.random.number(100), score:faker.random.number(100)/100},
|
||||||
|
THUMBS_DOWN:{value:faker.random.number(100), score:faker.random.number(100)/100},
|
||||||
|
LAUGH:{value:faker.random.number(100), score:faker.random.number(100)/100},
|
||||||
|
CONFUSED:{value:faker.random.number(100), score:faker.random.number(100)/100},
|
||||||
|
EYES:{value:faker.random.number(100), score:faker.random.number(100)/100},
|
||||||
|
ROCKET:{value:faker.random.number(100), score:faker.random.number(100)/100},
|
||||||
|
HOORAY:{value:faker.random.number(100), score:faker.random.number(100)/100},
|
||||||
|
},
|
||||||
|
comments:options["reactions.limit"],
|
||||||
|
details:options["reactions.details"],
|
||||||
|
days:options["reactions.days"]
|
||||||
|
}
|
||||||
|
}) : null),
|
||||||
//Introduction
|
//Introduction
|
||||||
...(set.plugins.enabled.introduction ? ({
|
...(set.plugins.enabled.introduction ? ({
|
||||||
introduction:{
|
introduction:{
|
||||||
|
|||||||
24
source/plugins/reactions/README.md
Normal file
24
source/plugins/reactions/README.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
### 🎭 Comment reactions
|
||||||
|
|
||||||
|
The *reactions* plugin displays overall reactions on your recent issues and issue comments.
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<td align="center">
|
||||||
|
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.reactions.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_reactions: yes
|
||||||
|
plugin_reactions_limit: 200 # Compute reactions over last 200 issue comments
|
||||||
|
plugin_reactions_days: 14 # Compute reactions on issue comments posted less than 14 days ago
|
||||||
|
plugin_reactions_details: percentage # Display reactions percentage
|
||||||
|
```
|
||||||
53
source/plugins/reactions/index.mjs
Normal file
53
source/plugins/reactions/index.mjs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//Setup
|
||||||
|
export default async function({login, q, imports, data, graphql, queries, account}, {enabled = false} = {}) {
|
||||||
|
//Plugin execution
|
||||||
|
try {
|
||||||
|
//Check if plugin is enabled and requirements are met
|
||||||
|
if ((!enabled)||(!q.reactions))
|
||||||
|
return null
|
||||||
|
|
||||||
|
//Load inputs
|
||||||
|
let {limit, days, details} = imports.metadata.plugins.reactions.inputs({data, account, q})
|
||||||
|
|
||||||
|
//Load issue comments
|
||||||
|
let cursor = null, pushed = 0
|
||||||
|
const comments = []
|
||||||
|
for (const type of ["issues", "issueComments"]) {
|
||||||
|
do {
|
||||||
|
//Load issue comments
|
||||||
|
console.debug(`metrics/compute/${login}/plugins > reactions > retrieving ${type} after ${cursor}`)
|
||||||
|
const {user:{[type]:{edges}}} = await graphql(queries.reactions({login, type, after:cursor ? `after: "${cursor}"` : ""}))
|
||||||
|
cursor = edges?.[edges?.length-1]?.cursor
|
||||||
|
//Save issue comments
|
||||||
|
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)
|
||||||
|
pushed = filtered.length
|
||||||
|
comments.push(...filtered)
|
||||||
|
console.debug(`metrics/compute/${login}/plugins > reactions > currently at ${comments.length} comments`)
|
||||||
|
//Early break
|
||||||
|
if ((comments.length >= limit)||(filtered.length < edges.length))
|
||||||
|
break
|
||||||
|
} while ((cursor)&&(pushed)&&(comments.length < limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
//Applying limit
|
||||||
|
if (limit) {
|
||||||
|
comments.splice(limit)
|
||||||
|
console.debug(`metrics/compute/${login}/plugins > reactions > keeping only ${comments.length} comments`)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Format reactions list
|
||||||
|
const list = {}
|
||||||
|
const reactions = comments.flatMap(({reactions}) => reactions)
|
||||||
|
for (const reaction of reactions)
|
||||||
|
list[reaction] = (list[reaction] ?? 0) + 1
|
||||||
|
for (const [key, value] of Object.entries(list))
|
||||||
|
list[key] = {value, score:value/reactions.length}
|
||||||
|
|
||||||
|
//Results
|
||||||
|
return {list, comments:comments.length, details, days}
|
||||||
|
}
|
||||||
|
//Handle errors
|
||||||
|
catch (error) {
|
||||||
|
throw {error:{message:"An error occured", instance:error}}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
source/plugins/reactions/metadata.yml
Normal file
39
source/plugins/reactions/metadata.yml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: "🎭 Comment reactions"
|
||||||
|
cost: 1 GraphQL request per 100 issues and issues comments fetched
|
||||||
|
categorie: github
|
||||||
|
supports:
|
||||||
|
- user
|
||||||
|
inputs:
|
||||||
|
|
||||||
|
# Enable or disable plugin
|
||||||
|
plugin_reactions:
|
||||||
|
description: Display average issue comments reactions
|
||||||
|
type: boolean
|
||||||
|
default: no
|
||||||
|
|
||||||
|
# Maximum number of issue comments to parse
|
||||||
|
# Issues will be fetched before issues comments
|
||||||
|
plugin_reactions_limit:
|
||||||
|
description: Maximum number of issue comments to parse
|
||||||
|
type: number
|
||||||
|
default: 200
|
||||||
|
min: 1
|
||||||
|
max: 1000
|
||||||
|
|
||||||
|
# Filter reactions by issue comments age
|
||||||
|
# Set to 0 to disable age filtering
|
||||||
|
plugin_reactions_days:
|
||||||
|
description: Maximum issue comments age
|
||||||
|
type: number
|
||||||
|
default: 0
|
||||||
|
min: 0
|
||||||
|
|
||||||
|
# Additional details
|
||||||
|
plugin_reactions_details:
|
||||||
|
description: Additional details
|
||||||
|
type: string
|
||||||
|
default: none
|
||||||
|
values:
|
||||||
|
- none
|
||||||
|
- count
|
||||||
|
- percentage
|
||||||
18
source/plugins/reactions/queries/reactions.graphql
Normal file
18
source/plugins/reactions/queries/reactions.graphql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
query ReactionsDefault {
|
||||||
|
user(login: "$login") {
|
||||||
|
login
|
||||||
|
$type($after first: 100, orderBy: {field: UPDATED_AT, direction: DESC}) {
|
||||||
|
edges {
|
||||||
|
cursor
|
||||||
|
node {
|
||||||
|
createdAt
|
||||||
|
reactions(last: 100, orderBy: {field: CREATED_AT, direction: DESC}) {
|
||||||
|
nodes {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
source/plugins/reactions/tests.yml
Normal file
5
source/plugins/reactions/tests.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
- name: Reactions plugin (default)
|
||||||
|
uses: lowlighter/metrics@latest
|
||||||
|
with:
|
||||||
|
token: MOCKED_TOKEN
|
||||||
|
plugin_reactions: yes
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"stargazers",
|
"stargazers",
|
||||||
"people",
|
"people",
|
||||||
"activity",
|
"activity",
|
||||||
|
"reactions",
|
||||||
"anilist",
|
"anilist",
|
||||||
"wakatime",
|
"wakatime",
|
||||||
"skyline",
|
"skyline",
|
||||||
|
|||||||
41
source/templates/classic/partials/reactions.ejs
Normal file
41
source/templates/classic/partials/reactions.ejs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<% if (plugins.reactions) { %>
|
||||||
|
<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="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>
|
||||||
|
Overall users reactions from last <%= plugins.reactions?.comments %> comments
|
||||||
|
</h2>
|
||||||
|
<div class="row">
|
||||||
|
<section>
|
||||||
|
<% if (plugins.reactions.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.reactions.error.message %>
|
||||||
|
</div>
|
||||||
|
<% } else { %>
|
||||||
|
<div class="row fill-width">
|
||||||
|
<section class="categories">
|
||||||
|
<% 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] ?? {} %>
|
||||||
|
<div class="categorie column">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="50" height="50" class="gauge info">
|
||||||
|
<circle class="gauge-base" r="53" cx="60" cy="60"></circle>
|
||||||
|
<% if (score > 0) { %>
|
||||||
|
<circle class="gauge-arc" transform="rotate(-90 60 60)" r="53" cx="60" cy="60" stroke-dasharray="<%= score * 329 %> 329"></circle>
|
||||||
|
<text x="60" y="60" dominant-baseline="central"><%= icon %></text>
|
||||||
|
<% } else { %>
|
||||||
|
<text x="60" y="60" dominant-baseline="central"><%= icon %></text>
|
||||||
|
<% } %>
|
||||||
|
</svg>
|
||||||
|
<% if (plugins.reactions.details === "percentage") { %>
|
||||||
|
<span class="title"><%= Math.round(score*100) %><small>%</small></span>
|
||||||
|
<% } else if (plugins.reactions.details === "count") { %>
|
||||||
|
<span class="title"><%= count %></span>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<% } %>
|
||||||
@@ -196,6 +196,9 @@
|
|||||||
.gauge.low {
|
.gauge.low {
|
||||||
color: #e53935;
|
color: #e53935;
|
||||||
}
|
}
|
||||||
|
.gauge.info {
|
||||||
|
color: #58A6FF;
|
||||||
|
}
|
||||||
.gauge-base, .gauge-arc {
|
.gauge-base, .gauge-arc {
|
||||||
stroke: currentColor;
|
stroke: currentColor;
|
||||||
stroke-width: 10;
|
stroke-width: 10;
|
||||||
|
|||||||
Reference in New Issue
Block a user