From 732d2a44b0d870671f338008c582d73274729887 Mon Sep 17 00:00:00 2001 From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com> Date: Tue, 9 Mar 2021 19:58:53 +0100 Subject: [PATCH] Add support for reactions plugin (#180) --- .github/readme/partials/references.md | 1 + .../api/github/graphql/reactions.default.mjs | 27 ++++++++++ source/app/web/statics/app.placeholder.js | 18 +++++++ source/plugins/reactions/README.md | 24 +++++++++ source/plugins/reactions/index.mjs | 53 +++++++++++++++++++ source/plugins/reactions/metadata.yml | 39 ++++++++++++++ .../reactions/queries/reactions.graphql | 18 +++++++ source/plugins/reactions/tests.yml | 5 ++ source/templates/classic/partials/_.json | 1 + .../templates/classic/partials/reactions.ejs | 41 ++++++++++++++ source/templates/classic/style.css | 3 ++ 11 files changed, 230 insertions(+) create mode 100644 source/app/mocks/api/github/graphql/reactions.default.mjs create mode 100644 source/plugins/reactions/README.md create mode 100644 source/plugins/reactions/index.mjs create mode 100644 source/plugins/reactions/metadata.yml create mode 100644 source/plugins/reactions/queries/reactions.graphql create mode 100644 source/plugins/reactions/tests.yml create mode 100644 source/templates/classic/partials/reactions.ejs diff --git a/.github/readme/partials/references.md b/.github/readme/partials/references.md index 6753180a..649638ee 100644 --- a/.github/readme/partials/references.md +++ b/.github/readme/partials/references.md @@ -13,3 +13,4 @@ * [ankurparihar/readme-pagespeed-insights](https://github.com/ankurparihar/readme-pagespeed-insights) * [jasonlong/isometric-contributions](https://github.com/jasonlong/isometric-contributions) * [jamesgeorge007/github-activity-readme](https://github.com/jamesgeorge007/github-activity-readme) +* [vvo/sourcekarma](https://github.com/vvo/sourcekarma) diff --git a/source/app/mocks/api/github/graphql/reactions.default.mjs b/source/app/mocks/api/github/graphql/reactions.default.mjs new file mode 100644 index 00000000..3528abfc --- /dev/null +++ b/source/app/mocks/api/github/graphql/reactions.default.mjs @@ -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(/(?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"])})), + }, + }, + })), + }, + }, + }) + } diff --git a/source/app/web/statics/app.placeholder.js b/source/app/web/statics/app.placeholder.js index 6130cb50..d9515c24 100644 --- a/source/app/web/statics/app.placeholder.js +++ b/source/app/web/statics/app.placeholder.js @@ -175,6 +175,24 @@ comments:faker.random.number(1000) } }) : 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 ...(set.plugins.enabled.introduction ? ({ introduction:{ diff --git a/source/plugins/reactions/README.md b/source/plugins/reactions/README.md new file mode 100644 index 00000000..2d28b9ed --- /dev/null +++ b/source/plugins/reactions/README.md @@ -0,0 +1,24 @@ +### 🎭 Comment reactions + +The *reactions* plugin displays overall reactions on your recent issues and issue comments. + + + +
+ + +
+ +#### â„šī¸ 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 +``` \ No newline at end of file diff --git a/source/plugins/reactions/index.mjs b/source/plugins/reactions/index.mjs new file mode 100644 index 00000000..56d7dec5 --- /dev/null +++ b/source/plugins/reactions/index.mjs @@ -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}} + } + } \ No newline at end of file diff --git a/source/plugins/reactions/metadata.yml b/source/plugins/reactions/metadata.yml new file mode 100644 index 00000000..144a1986 --- /dev/null +++ b/source/plugins/reactions/metadata.yml @@ -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 \ No newline at end of file diff --git a/source/plugins/reactions/queries/reactions.graphql b/source/plugins/reactions/queries/reactions.graphql new file mode 100644 index 00000000..f2abddef --- /dev/null +++ b/source/plugins/reactions/queries/reactions.graphql @@ -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 + } + } + } + } + } + } +} diff --git a/source/plugins/reactions/tests.yml b/source/plugins/reactions/tests.yml new file mode 100644 index 00000000..16004690 --- /dev/null +++ b/source/plugins/reactions/tests.yml @@ -0,0 +1,5 @@ +- name: Reactions plugin (default) + uses: lowlighter/metrics@latest + with: + token: MOCKED_TOKEN + plugin_reactions: yes \ No newline at end of file diff --git a/source/templates/classic/partials/_.json b/source/templates/classic/partials/_.json index 2bbc2b72..543f7812 100644 --- a/source/templates/classic/partials/_.json +++ b/source/templates/classic/partials/_.json @@ -19,6 +19,7 @@ "stargazers", "people", "activity", + "reactions", "anilist", "wakatime", "skyline", diff --git a/source/templates/classic/partials/reactions.ejs b/source/templates/classic/partials/reactions.ejs new file mode 100644 index 00000000..984d54ab --- /dev/null +++ b/source/templates/classic/partials/reactions.ejs @@ -0,0 +1,41 @@ +<% if (plugins.reactions) { %> +
+

+ + Overall users reactions from last <%= plugins.reactions?.comments %> comments +

+
+
+ <% if (plugins.reactions.error) { %> +
+ + <%= plugins.reactions.error.message %> +
+ <% } else { %> +
+
+ <% 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] ?? {} %> +
+ + + <% if (score > 0) { %> + + <%= icon %> + <% } else { %> + <%= icon %> + <% } %> + + <% if (plugins.reactions.details === "percentage") { %> + <%= Math.round(score*100) %>% + <% } else if (plugins.reactions.details === "count") { %> + <%= count %> + <% } %> +
+ <% } %> +
+
+ <% } %> +
+
+
+<% } %> \ No newline at end of file diff --git a/source/templates/classic/style.css b/source/templates/classic/style.css index b5a45f55..54899970 100644 --- a/source/templates/classic/style.css +++ b/source/templates/classic/style.css @@ -196,6 +196,9 @@ .gauge.low { color: #e53935; } + .gauge.info { + color: #58A6FF; + } .gauge-base, .gauge-arc { stroke: currentColor; stroke-width: 10;