From 88f9e1a41f09606f3a6872f60594858a4741ea6b Mon Sep 17 00:00:00 2001 From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com> Date: Fri, 30 Jul 2021 23:05:12 +0200 Subject: [PATCH] Add new Discussions plugin (#430) [skip ci] --- .../github/graphql/discussions.categories.mjs | 27 +++++++++++ .../github/graphql/discussions.statistics.mjs | 11 +++++ source/app/web/statics/app.placeholder.js | 14 ++++++ source/plugins/discussions/README.md | 21 ++++++++ source/plugins/discussions/index.mjs | 46 ++++++++++++++++++ source/plugins/discussions/metadata.yml | 12 +++++ .../discussions/queries/categories.graphql | 15 ++++++ .../discussions/queries/statistics.graphql | 13 +++++ source/plugins/discussions/tests.yml | 5 ++ source/templates/classic/partials/_.json | 1 + .../classic/partials/discussions.ejs | 48 +++++++++++++++++++ 11 files changed, 213 insertions(+) create mode 100644 source/app/mocks/api/github/graphql/discussions.categories.mjs create mode 100644 source/app/mocks/api/github/graphql/discussions.statistics.mjs create mode 100644 source/plugins/discussions/README.md create mode 100644 source/plugins/discussions/index.mjs create mode 100644 source/plugins/discussions/metadata.yml create mode 100644 source/plugins/discussions/queries/categories.graphql create mode 100644 source/plugins/discussions/queries/statistics.graphql create mode 100644 source/plugins/discussions/tests.yml create mode 100644 source/templates/classic/partials/discussions.ejs diff --git a/source/app/mocks/api/github/graphql/discussions.categories.mjs b/source/app/mocks/api/github/graphql/discussions.categories.mjs new file mode 100644 index 00000000..e5fa8fdc --- /dev/null +++ b/source/app/mocks/api/github/graphql/discussions.categories.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 > contributors/commit") + return /after: "MOCKED_CURSOR"/m.test(query) + ? ({ + user:{ + repositoryDiscussions:{ + edges:[], + nodes:[], + } + } + }) + : ({ + user:{ + repositoryDiscussions:{ + edges:new Array(100).fill(null).map(_ => ({cursor:"MOCKED_CURSOR"})), + nodes:new Array(100).fill(null).map(_ => ({ + category:{ + emoji:faker.random.arrayElement([":chart_with_upwards_trend:", ":chart_with_downwards_trend:", ":bar_char:"]), + name:faker.lorem.slug() + } + })) + } + } + }) +} + diff --git a/source/app/mocks/api/github/graphql/discussions.statistics.mjs b/source/app/mocks/api/github/graphql/discussions.statistics.mjs new file mode 100644 index 00000000..b899f7ea --- /dev/null +++ b/source/app/mocks/api/github/graphql/discussions.statistics.mjs @@ -0,0 +1,11 @@ +/**Mocked data */ +export default function({faker, query, login = faker.internet.userName()}) { + console.debug("metrics/compute/mocks > mocking graphql api result > contributors/commit") + return ({ + user:{ + started:{totalCount:faker.datatype.number(1000)}, + comments:{totalCount:faker.datatype.number(1000)}, + answers:{totalCount:faker.datatype.number(1000)} + } + }) +} \ No newline at end of file diff --git a/source/app/web/statics/app.placeholder.js b/source/app/web/statics/app.placeholder.js index 63d4cc51..2c327712 100644 --- a/source/app/web/statics/app.placeholder.js +++ b/source/app/web/statics/app.placeholder.js @@ -586,6 +586,20 @@ }, }) : null), + //Discussions + ...(set.plugins.enabled.discussions + ? ({ + discussions: { + categories: { + stats: { '🙏 Q&A': faker.datatype.number(100), '📣 Announcements': faker.datatype.number(100), '💡 Ideas': faker.datatype.number(100), '💬 General': faker.datatype.number(100) }, + favorite: '📣 Announcements' + }, + started: faker.datatype.number(1000), + comments: faker.datatype.number(1000), + answers: faker.datatype.number(1000), + }, + }) + : null), //Posts ...(set.plugins.enabled.posts ? ({ diff --git a/source/plugins/discussions/README.md b/source/plugins/discussions/README.md new file mode 100644 index 00000000..23bae3c4 --- /dev/null +++ b/source/plugins/discussions/README.md @@ -0,0 +1,21 @@ +### 💬 Discussions + +The *discussions* plugin displays your GitHub discussions metrics. + + + +
+ + +
+ +#### ℹ️ Examples workflows + +[➡️ Available options for this plugin](metadata.yml) + +```yaml +- uses: lowlighter/metrics@latest + with: + # ... other options + plugin_discussions: yes +``` \ No newline at end of file diff --git a/source/plugins/discussions/index.mjs b/source/plugins/discussions/index.mjs new file mode 100644 index 00000000..8327fb68 --- /dev/null +++ b/source/plugins/discussions/index.mjs @@ -0,0 +1,46 @@ +//Setup + export default async function({login, q, imports, graphql, queries, data, account}, {enabled = false} = {}) { + //Plugin execution + try { + //Check if plugin is enabled and requirements are met + if ((!enabled)||(!q.discussions)) + return null + + //Load inputs + imports.metadata.plugins.discussions.inputs({data, account, q}) + const discussions = {categories:{}} + + //Fetch general statistics + const stats = Object.fromEntries(Object.entries((await graphql(queries.discussions.statistics({login}))).user).map(([key, value]) => [key, value.totalCount])) + Object.assign(discussions, stats) + + //Load started discussions + { + const fetched = [] + const categories = {} + let cursor = null + let pushed = 0 + do { + console.debug(`metrics/compute/${login}/discussions > retrieving discussions after ${cursor}`) + const {user:{repositoryDiscussions:{edges = [], nodes = []} = {}}} = await graphql(queries.discussions.categories({login, after:cursor ? `after: "${cursor}"` : ""})) + cursor = edges?.[edges?.length - 1]?.cursor + fetched.push(...nodes) + pushed = nodes.length + console.debug(`metrics/compute/${login}/discussions > retrieved ${pushed} discussions after ${cursor}`) + } while ((pushed) && (cursor)) + + //Compute favorite category + for (const category of [...fetched.map(({category:{emoji, name}}) => `${imports.emoji.get(emoji)} ${name}`)]) + categories[category] = (categories[category] ?? 0) + 1 + discussions.categories.stats = categories + discussions.categories.favorite = Object.entries(categories).sort((a, b) => b[1] - a[1]).map(([name]) => name).shift() ?? null + } + + //Results + return discussions + } + //Handle errors + catch (error) { + throw {error:{message:"An error occured", instance:error}} + } + } diff --git a/source/plugins/discussions/metadata.yml b/source/plugins/discussions/metadata.yml new file mode 100644 index 00000000..84058a96 --- /dev/null +++ b/source/plugins/discussions/metadata.yml @@ -0,0 +1,12 @@ +name: "💬 Discussions" +cost: 1 GraphQL request + 1 GraphQL request per 100 discussions started +category: github +supports: + - user +inputs: + + # Enable or disable plugin + plugin_discussions: + description: GitHub discussions metrics + type: boolean + default: no \ No newline at end of file diff --git a/source/plugins/discussions/queries/categories.graphql b/source/plugins/discussions/queries/categories.graphql new file mode 100644 index 00000000..cc6ba0f6 --- /dev/null +++ b/source/plugins/discussions/queries/categories.graphql @@ -0,0 +1,15 @@ +query DiscussionsCategories { + user(login: "$login") { + repositoryDiscussions($after first: 100, orderBy: {field: CREATED_AT, direction: DESC}) { + edges { + cursor + } + nodes { + category { + emoji + name + } + } + } + } +} \ No newline at end of file diff --git a/source/plugins/discussions/queries/statistics.graphql b/source/plugins/discussions/queries/statistics.graphql new file mode 100644 index 00000000..281bf769 --- /dev/null +++ b/source/plugins/discussions/queries/statistics.graphql @@ -0,0 +1,13 @@ +query DiscussionsStatistics { + user(login: "$login") { + started: repositoryDiscussions { + totalCount + } + comments: repositoryDiscussionComments { + totalCount + } + answers: repositoryDiscussionComments(onlyAnswers: true) { + totalCount + } + } +} \ No newline at end of file diff --git a/source/plugins/discussions/tests.yml b/source/plugins/discussions/tests.yml new file mode 100644 index 00000000..d1a46e9a --- /dev/null +++ b/source/plugins/discussions/tests.yml @@ -0,0 +1,5 @@ +- name: Discussions plugin (default) + uses: lowlighter/metrics@latest + with: + token: MOCKED_TOKEN + plugin_discussions: yes \ No newline at end of file diff --git a/source/templates/classic/partials/_.json b/source/templates/classic/partials/_.json index cc4d8fc5..0834249c 100644 --- a/source/templates/classic/partials/_.json +++ b/source/templates/classic/partials/_.json @@ -25,6 +25,7 @@ "anilist", "wakatime", "skyline", + "discussions", "support", "stackoverflow", "stock", diff --git a/source/templates/classic/partials/discussions.ejs b/source/templates/classic/partials/discussions.ejs new file mode 100644 index 00000000..e8587b2f --- /dev/null +++ b/source/templates/classic/partials/discussions.ejs @@ -0,0 +1,48 @@ +<% if (plugins.discussions) { %> +
+

+ + <% if (!plugins.discussions.error) { %> + <%= plugins.discussions.started %> Discussion<%= s(plugins.discussions.started) %> started + <% } else { %> + Discussions + <% } %> +

+ <% if (plugins.discussions.error) { %> +
+
+
+ + <%= plugins.discussions.error.message %> +
+
+
+ <% } else { %> +
+
+
+ + <%= plugins.discussions.comments %> Comment<%= s(plugins.discussions.comments) %> +
+
+
+
+ + <%= plugins.discussions.answers %> Answer<%= s(plugins.discussions.answers) %> +
+
+
+ <% if (Object.keys(plugins.discussions.categories.stats).length) { %> +
+
+
+ <% for (const [category, posts] of Object.entries(plugins.discussions.categories.stats)) { %> +
<%= category %>
<%= posts %>
+ <% } %> +
+
+
+ <% } %> + <% } %> +
+<% } %> \ No newline at end of file