From cfca5d3892e648b024948434baf7be0618988363 Mon Sep 17 00:00:00 2001 From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com> Date: Sat, 27 Feb 2021 23:24:51 +0100 Subject: [PATCH] Add stackoverflow plugin (#159) --- .../imgs/plugin_stackoverflow_user_id.png | Bin 0 -> 41883 bytes source/app/metrics/utils.mjs | 4 +- .../app/mocks/api/axios/get/stackoverflow.mjs | 99 +++++++++++ source/app/web/statics/app.placeholder.js | 59 +++++++ source/plugins/stackoverflow/README.md | 36 ++++ source/plugins/stackoverflow/index.mjs | 97 +++++++++++ source/plugins/stackoverflow/metadata.yml | 48 ++++++ source/plugins/stackoverflow/tests.yml | 16 ++ source/templates/classic/partials/_.json | 3 +- .../classic/partials/stackoverflow.ejs | 161 ++++++++++++++++++ source/templates/classic/style.css | 43 +++++ 11 files changed, 563 insertions(+), 3 deletions(-) create mode 100644 .github/readme/imgs/plugin_stackoverflow_user_id.png create mode 100644 source/app/mocks/api/axios/get/stackoverflow.mjs create mode 100644 source/plugins/stackoverflow/README.md create mode 100644 source/plugins/stackoverflow/index.mjs create mode 100644 source/plugins/stackoverflow/metadata.yml create mode 100644 source/plugins/stackoverflow/tests.yml create mode 100644 source/templates/classic/partials/stackoverflow.ejs diff --git a/.github/readme/imgs/plugin_stackoverflow_user_id.png b/.github/readme/imgs/plugin_stackoverflow_user_id.png new file mode 100644 index 0000000000000000000000000000000000000000..61da7248350ae1e59304dcd3c42f7f09a8ed4c93 GIT binary patch literal 41883 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"] ? ">" : ">") - .replace(/"/g, u['"'] ? '"' : '"') - .replace(/'/g, u["'"] ? "'" : "'") + .replace(/"/g, u['"'] ? '"' : """) + .replace(/&(?:apos|#39);/g, u["'"] ? "'" : "'") .replace(/&/g, u["&"] ? "&" : "&") } diff --git a/source/app/mocks/api/axios/get/stackoverflow.mjs b/source/app/mocks/api/axios/get/stackoverflow.mjs new file mode 100644 index 00000000..9aa28fed --- /dev/null +++ b/source/app/mocks/api/axios/get/stackoverflow.mjs @@ -0,0 +1,99 @@ +/**Mocked data */ + export default function({faker, url, options, login = faker.internet.userName()}) { + //Stackoverflow api + if (/^https:..api.stackexchange.com.2.2.*$/.test(url)) { + //Extract user id + const user_id = url.match(/[/]users[/](?\d+)/)?.groups?.id ?? NaN + const pagesize = Number(url.match(/pagesize=(?\d+)/)?.groups?.pagesize) || 30 + //User account + if (/users[/]\d+[/][?]site=stackoverflow$/.test(url)) { + console.debug(`metrics/compute/mocks > mocking stackoverflow api result > ${url}`) + return ({ + status:200, + data:{ + items:[ + { + badge_counts:{bronze:faker.random.number(500), silver:faker.random.number(300), gold:faker.random.number(100)}, + accept_rate:faker.random.number(100), + answer_count:faker.random.number(1000), + question_count:faker.random.number(1000), + view_count:faker.random.number(10000), + creation_date:faker.date.past(), + display_name:faker.internet.userName(), + user_id, + reputation:faker.random.number(100000), + }, + ], + has_more:false, + quota_max:300, + quota_remaining:faker.random.number(300), + }, + }) + } + //Total metrics + if (/[?]site=stackoverflow&filter=total$/.test(url)) { + console.debug(`metrics/compute/mocks > mocking stackoverflow api result > ${url}`) + return ({ + status:200, + data:{ + total:faker.random.number(10000), + }, + }) + } + //Questions + if ((/questions[?]site=stackoverflow/.test(url))||(/questions[/][\d;]+[?]site=stackoverflow/.test(url))) { + console.debug(`metrics/compute/mocks > mocking stackoverflow api result > ${url}`) + return ({ + status:200, + data:{ + items:new Array(pagesize).fill(null).map(_ => ({ + tags:new Array(5).fill(null).map(_ => faker.lorem.slug()), + owner:{display_name:faker.internet.userName()}, + is_answered:faker.random.boolean(), + view_count:faker.random.number(10000), + accepted_answer_id:faker.random.number(1000000), + answer_count:faker.random.number(100), + score:faker.random.number(1000), + creation_date:faker.time.recent(), + down_vote_count:faker.random.number(1000), + up_vote_count:faker.random.number(1000), + comment_count:faker.random.number(1000), + favorite_count:faker.random.number(1000), + title:faker.lorem.sentence(), + body_markdown:faker.lorem.paragraphs(), + link:faker.internet.url(), + question_id:faker.random.number(1000000), + })), + has_more:false, + quota_max:300, + quota_remaining:faker.random.number(300), + }, + }) + } + //Answers + if ((/answers[?]site=stackoverflow/.test(url))||(/answers[/][\d;]+[?]site=stackoverflow/.test(url))) { + console.debug(`metrics/compute/mocks > mocking stackoverflow api result > ${url}`) + return ({ + status:200, + data:{ + items:new Array(pagesize).fill(null).map(_ => ({ + owner:{display_name:faker.internet.userName()}, + link:faker.internet.url(), + is_accepted:faker.random.boolean(), + score:faker.random.number(1000), + down_vote_count:faker.random.number(1000), + up_vote_count:faker.random.number(1000), + comment_count:faker.random.number(1000), + creation_date:faker.time.recent(), + question_id:faker.random.number(1000000), + body_markdown:faker.lorem.paragraphs(), + answer_id:faker.random.number(1000000), + })), + has_more:false, + quota_max:300, + quota_remaining:faker.random.number(300), + }, + }) + } + } + } diff --git a/source/app/web/statics/app.placeholder.js b/source/app/web/statics/app.placeholder.js index 6f1d00de..5ee8733b 100644 --- a/source/app/web/statics/app.placeholder.js +++ b/source/app/web/statics/app.placeholder.js @@ -651,6 +651,65 @@ duration:options["isocalendar.duration"] } }) : null), + //Stackoverflow + ...(set.plugins.enabled.stackoverflow ? ({ + stackoverflow:{ + sections:options["stackoverflow.sections"].split(",").map(x => x.trim()).filter(x => x), + lines:options["stackoverflow.lines"], + user:{ + reputation:faker.random.number(100000), + badges:faker.random.number(1000), + questions:faker.random.number(1000), + answers:faker.random.number(1000), + comments:faker.random.number(1000), + views:faker.random.number(1000), + }, + "answers-top":new Array(options["stackoverflow.limit"]).fill(null).map(_ => ({ + type:"answer", + body:faker.lorem.paragraphs(), + score:faker.random.number(1000), + upvotes:faker.random.number(1000), + downvotes:faker.random.number(1000), + accepted:faker.random.boolean(), + comments:faker.random.number(1000), + author:set.user, + created:"01/01/1970", + link:null, + id:faker.random.number(100000), + question_id:faker.random.number(100000), + question:{ + title:faker.lorem.sentence(), + tags:[faker.lorem.slug(), faker.lorem.slug()], + } + })), + get ["answers-recent"]() { + return this["answers-top"] + }, + "questions-top":new Array(options["stackoverflow.limit"]).fill(null).map(_ => ({ + type:"question", + title:faker.lorem.sentence(), + body:faker.lorem.paragraphs(), + score:faker.random.number(1000), + upvotes:faker.random.number(1000), + downvotes:faker.random.number(1000), + favorites:faker.random.number(1000), + tags:[faker.lorem.slug(), faker.lorem.slug()], + answered:faker.random.boolean(), + answers:faker.random.number(1000), + comments:faker.random.number(1000), + views:faker.random.number(1000), + author:set.user, + created:"01/01/1970", + link:null, + id:faker.random.number(100000), + accepted_answer_id:faker.random.number(100000), + answer:null, + })), + get ["questions-recent"]() { + return this["questions-top"] + }, + } + }) : null), }, } //Formatters diff --git a/source/plugins/stackoverflow/README.md b/source/plugins/stackoverflow/README.md new file mode 100644 index 00000000..4614bcba --- /dev/null +++ b/source/plugins/stackoverflow/README.md @@ -0,0 +1,36 @@ +### 🗨️ Stackoverflow plugin + +The *stackoverflow* plugin lets you display your metrics, questions and answer from [stackoverflow](https://stackoverflow.com/). + + + +
+ + +
+ +
+💬 Get your user id + +Go to [stackoverflow.com](https://stackoverflow.com/) and click on your account profile. + +Your user id will be in both url and search bar. + +![User id](/.github/readme/imgs/plugin_stackoverflow_user_id.png) + +
+ +#### ℹ️ Examples workflows + +[➡️ Available options for this plugin](metadata.yml) + +```yaml +- uses: lowlighter/metrics@latest + with: + # ... other options + plugin_stackoverflow: yes + plugin_stackoverflow_user: 8332505 # Stackoverflow user id (required) + plugin_stackoverflow_sections: answers-top, questions-recent # Display top answers and recent questions + plugin_stackoverflow_limit: 2 # Display 2 entries per section + plugin_stackoverflow_lines: 4 # Display 4 lines per entry +``` \ No newline at end of file diff --git a/source/plugins/stackoverflow/index.mjs b/source/plugins/stackoverflow/index.mjs new file mode 100644 index 00000000..16483946 --- /dev/null +++ b/source/plugins/stackoverflow/index.mjs @@ -0,0 +1,97 @@ +//Setup + export default async function({login, q, imports, data, account}, {enabled = false} = {}) { + //Plugin execution + try { + //Check if plugin is enabled and requirements are met + if ((!enabled)||(!q.stackoverflow)) + return null + + //Load inputs + let {sections, user, limit, lines} = imports.metadata.plugins.stackoverflow.inputs({data, account, q}) + if (!user) + throw {error:{message:"You must provide a stackoverflow user id"}} + + //Initialization + //See https://api.stackexchange.com/docs + const api = {base:"https://api.stackexchange.com/2.2", user:`https://api.stackexchange.com/2.2/users/${user}`} + const filters = {user:"!0Z-LvgkLYnTCu1858)*D0lcx2", answer:"!7goY5TLWwCz.BaGpe)tv5C6Bks2q8siMH6", question:"!)EhwvzgX*hrClxjLzqxiZHHbTPRE5Pb3B9vvRaqCx5-ZY.vPr"} + const result = {sections, lines} + + //Stackoverflow user metrics + { + //Account metrics + console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for user ${user}`) + const {data:{items:[{reputation, badge_counts:{bronze, silver, gold}, answer_count:answers, question_count:questions, view_count:views}]}} = await imports.axios.get(`${api.user}?site=stackoverflow&filter=${filters.user}`) + const {data:{total:comments}} = await imports.axios.get(`${api.user}/comments?site=stackoverflow&filter=total`) + //Save result + result.user = {reputation, badges:bronze+silver+gold, questions, answers, comments, views} + } + + //Answers + for (const {key, sort} of [{key:"answers-recent", sort:"sort=activity&order=desc"}, {key:"answers-top", sort:"sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) { + //Load and format answers + console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`) + const {data:{items}} = await imports.axios.get(`${api.user}/answers?site=stackoverflow&pagesize=${limit}&filter=${filters.answer}&${sort}`) + result[key] = items.map(item => format.answer(item, {imports, data})) + console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`) + //Load related questions + const ids = result[key].map(({question_id}) => question_id).filter(id => id) + if (ids) { + console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`) + const {data:{items}} = await imports.axios.get(`${api.base}/questions/${ids.join(";")}?site=stackoverflow&filter=${filters.question}`) + items.map(item => format.question(item, {imports, data})) + } + } + + //Questions + for (const {key, sort} of [{key:"questions-recent", sort:"sort=activity&order=desc"}, {key:"questions-top", sort:"sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) { + //Load and format questions + console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`) + const {data:{items}} = await imports.axios.get(`${api.user}/questions?site=stackoverflow&pagesize=${limit}&filter=${filters.question}&${sort}`) + result[key] = items.map(item => format.question(item, {imports, data})) + console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`) + //Load related answers + const ids = result[key].map(({accepted_answer_id}) => accepted_answer_id).filter(id => id) + if (ids) { + console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`) + const {data:{items}} = await imports.axios.get(`${api.base}/answers/${ids.join(";")}?site=stackoverflow&filter=${filters.answer}`) + items.map(item => format.answer(item, {imports, data})) + } + } + + //Results + return result + } + //Handle errors + catch (error) { + if (error.error?.message) + throw error + throw {error:{message:"An error occured", instance:error}} + } + } + +//Formatters + const format = { + /**Cached */ + cached:new Map(), + /**Format answers */ + answer({body_markdown:body, score, up_vote_count:upvotes, down_vote_count:downvotes, is_accepted:accepted, comment_count:comments = 0, creation_date, owner:{display_name:author}, link, answer_id:id, question_id}, {imports, data}) { + const formatted = {type:"answer", body:imports.htmlunescape(body), score, upvotes, downvotes, accepted, comments, author, created:imports.date(creation_date*1000, {dateStyle:"short", timeZone:data.config.timezone?.name}), link, id, question_id, + get question() { + return format.cached.get(`q${this.question_id}`) ?? null + }, + } + this.cached.set(`a${id}`, formatted) + return formatted + }, + /**Format questions */ + question({title, body_markdown:body, score, up_vote_count:upvotes, down_vote_count:downvotes, favorite_count:favorites, tags, is_answered:answered, answer_count:answers, comment_count:comments, view_count:views, creation_date, owner:{display_name:author}, link, question_id:id, accepted_answer_id = null}, {imports, data}) { + const formatted = {type:"question", title:imports.htmlunescape(title), body:imports.htmlunescape(body), score, upvotes, downvotes, favorites, tags, answered, answers, comments, views, author, created:imports.date(creation_date*1000, {dateStyle:"short", timeZone:data.config.timezone?.name}), link, id, accepted_answer_id, + get answer() { + return format.cached.get(`a${this.accepted_answer_id}`) ?? null + }, + } + this.cached.set(`q${id}`, formatted) + return formatted + }, + } diff --git a/source/plugins/stackoverflow/metadata.yml b/source/plugins/stackoverflow/metadata.yml new file mode 100644 index 00000000..04d35896 --- /dev/null +++ b/source/plugins/stackoverflow/metadata.yml @@ -0,0 +1,48 @@ +name: "🗨️ Stackoverflow plugin" +cost: N/A +categorie: social +supports: + - user + - organization +inputs: + + # Enable or disable plugin + plugin_stackoverflow: + description: Stackoverflow metrics + type: boolean + default: no + + # Stackoverflow user id + # To obtain it, extract the identifier on your account page url + plugin_stackoverflow_user: + description: Stackoverflow user id + type: number + default: 0 + + # Sections to display + plugin_stackoverflow_sections: + description: Sections to display + type: array + format: comma-separated + default: answers-top, questions-recent + values: + - answers-top # Display top answers + - answers-recent # Display recent answers + - questions-top # Display top questions + - questions-recent # Display recent questions + + # Number of entries to display per section + plugin_stackoverflow_limit: + description: Maximum number of entries to display per section + type: number + default: 2 + min: 1 + max: 30 + + # Number of lines to display per question or answer + # Set to 0 to disable limitations + plugin_stackoverflow_lines: + description: Maximum number of lines to display per question or answer + type: number + default: 4 + min: 0 \ No newline at end of file diff --git a/source/plugins/stackoverflow/tests.yml b/source/plugins/stackoverflow/tests.yml new file mode 100644 index 00000000..4688d3d0 --- /dev/null +++ b/source/plugins/stackoverflow/tests.yml @@ -0,0 +1,16 @@ +- name: Stackoverflow plugin (default) + uses: lowlighter/metrics@latest + with: + token: MOCKED_TOKEN + plugin_stackoverflow: yes + plugin_stackoverflow_user: 1 + +- name: Stackoverflow plugin (complete) + uses: lowlighter/metrics@latest + with: + token: MOCKED_TOKEN + plugin_stackoverflow: yes + plugin_stackoverflow_user: 1 + plugin_stackoverflow_sections: answers-top, answers-recent, questions-top, questions-recent + plugin_stackoverflow_limit: 2 + plugin_stackoverflow_lines: 4 \ No newline at end of file diff --git a/source/templates/classic/partials/_.json b/source/templates/classic/partials/_.json index 2c4d8059..2bbc2b72 100644 --- a/source/templates/classic/partials/_.json +++ b/source/templates/classic/partials/_.json @@ -21,5 +21,6 @@ "activity", "anilist", "wakatime", - "skyline" + "skyline", + "stackoverflow" ] \ No newline at end of file diff --git a/source/templates/classic/partials/stackoverflow.ejs b/source/templates/classic/partials/stackoverflow.ejs new file mode 100644 index 00000000..a3fab2ef --- /dev/null +++ b/source/templates/classic/partials/stackoverflow.ejs @@ -0,0 +1,161 @@ +<% if (plugins.stackoverflow) { %> +
+

+ + Stackoverflow metrics +

+ <% if (plugins.stackoverflow.error) { %> +
+
+
+ + <%= plugins.stackoverflow.error.message %> +
+
+
+ <% } else { %> +
+
+
+ + <%= plugins.stackoverflow.user.reputation %> reputation point<%= s(plugins.stackoverflow.user.reputation) %> +
+
+ + <%= plugins.stackoverflow.user.questions %> question<%= s(plugins.stackoverflow.user.questions) %> +
+
+ + <%= plugins.stackoverflow.user.comments %> comment<%= s(plugins.stackoverflow.user.comments) %> +
+
+
+
+ + <%= plugins.stackoverflow.user.badges %> badge<%= s(plugins.stackoverflow.user.badges) %> +
+
+ + <%= plugins.stackoverflow.user.answers %> answer<%= s(plugins.stackoverflow.user.answers) %> +
+
+
+ <% if (plugins.stackoverflow.lines) { %> + + <% } %> + <% for (const section of plugins.stackoverflow.sections) { if (!plugins.stackoverflow[section]?.length) continue %> +
+
+
+

+ + <%= {"questions-recent":"Recent questions", "questions-top":"Top questions", "answers-recent":"Recent answers", "answers-top":"Top answers"}[section] %> +

+ <% for (const {type, ...entry} of plugins.stackoverflow[section]) { %> +
+ <% if (type === "question") { %> +
+ + <%= entry.title %> +
+
+
+ + <%= entry.tags.join(", ") %> +
+ <% if (entry.answered) { %> +
+ + Resolved +
+ <% } %> +
+
+ <%= entry.body %> +
+
+
+ + <%= f(entry.upvotes) %> +
+
+ + <%= f(entry.downvotes) %> +
+
+ + <%= f(entry.views) %> +
+
+ + <%= f(entry.answers) %> +
+
+ + <%= f(entry.comments) %> +
+
+ + <%= entry.created %> +
+
+ <% } else if (type === "answer") { %> +
+ + <%= entry.question?.title %> +
+
+
+ + <%= entry.question?.tags.join(", ") %> +
+ <% if (entry.question?.answered) { %> +
+ + Resolved +
+ <% } %> +
+
+ <%= entry.body %> +
+
+
+ + <%= f(entry.upvotes) %> +
+
+ + <%= f(entry.downvotes) %> +
+
+ + <%= f(entry.comments) %> +
+
+ + <%= entry.created %> +
+ <% if (entry.accepted) { %> +
+ + Accepted +
+ <% } %> +
+ <% } %> +
+ <% } %> +
+
+
+ <% } %> + <% } %> +
+<% } %> diff --git a/source/templates/classic/style.css b/source/templates/classic/style.css index fd1272b5..1e422e99 100644 --- a/source/templates/classic/style.css +++ b/source/templates/classic/style.css @@ -724,6 +724,49 @@ margin: 0 13px 2px; } +/* Stackoverflow */ + .stackoverflow { + margin-left: 38px; + } + .stackoverflow .entry { + margin: 4px 0 12px; + } + .stackoverflow .title { + color: #58a6ff; + white-space: normal; + align-items: flex-start; + } + .stackoverflow .body, .stackoverflow .infos { + color: #666666; + font-size: 13px; + margin-left: 32px; + } + .stackoverflow .infos { + display: flex; + align-items: center; + } + .stackoverflow .infos > div { + display: inline-flex; + align-items: center; + margin-right: 16px; + } + .stackoverflow .infos svg { + fill: currentColor; + height: 12px; + width: 12px; + margin: 0; + margin-right: 4px; + flex-shrink: 0; + } + .stackoverflow .body { + overflow: hidden; + text-overflow: ellipsis; + border-left: 3px solid #777777B2; + padding-left: 6px; + width: 400px; + } + + /* Fade animation */ .af { opacity: 0;