From 2493e29ebc278003bf7b6ad1f38ae0cd5bb1e722 Mon Sep 17 00:00:00 2001 From: lowlighter <22963968+lowlighter@users.noreply.github.com> Date: Thu, 17 Sep 2020 21:17:04 +0200 Subject: [PATCH] Add support for lines and traffic plugins --- README.md | 38 +++++++++- action.yml | 5 +- action/dist/index.js | 122 +++++++++++++++++++++++++++++--- action/index.mjs | 8 ++- package-lock.json | 35 +++++++++ package.json | 1 + settings.example.json | 9 ++- src/app.mjs | 21 ++++-- src/metrics.mjs | 6 +- src/plugins/index.mjs | 6 +- src/plugins/lines/index.mjs | 44 +++++++++++- src/plugins/pagespeed/index.mjs | 6 +- src/plugins/traffic/index.mjs | 34 ++++++++- src/query.graphql | 1 + src/template.svg | 12 ++++ 15 files changed, 322 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 7efe0a3d..b2fbe9b8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A GitHub Action which is run periodically at your convenience which generates an Assuming your username is `my-github-user`, you can embed your metrics in your personal repository's readme like below : ```markdown ![GitHub metrics](https://github.com/my-github-user/my-github-user/blob/master/github-metrics.svg) -# Or with a redirection : +# Or with a redirection : [![GitHub metrics](https://github.com/my-github-user/my-github-user/blob/master/github-metrics.svg)](https://github.com/my-github-user/my-github-user) ``` ```html @@ -64,6 +64,10 @@ jobs: token: ${{ secrets.METRICS_TOKEN }} # Your GitHub user name user: my-github-user + # If you own a website and you added it to your GitHub profile, + # You can provide a PageSpeed token to add your site's performance results on the metrics SVG image + # See https://developers.google.com/speed/docs/insights/v5/get-started to obtain a key + # pagespeed_token: ${{ secrets.PAGESPEED_TOKEN }} ``` On each run, a new SVG image will be generated and committed to your repository. @@ -88,7 +92,7 @@ For conveniency, you can use the shared instance available at [metrics.lecoq.io] Assuming your username is `my-github-user`, you can embed your metrics in your personal repository's readme like below : ```markdown ![GitHub metrics](https://metrics.lecoq.io/my-github-user) -# Or with a redirection : +# Or with a redirection : [![GitHub metrics](https://metrics.lecoq.io/my-github-user)](https://github.com/my-github-user/my-github-user) ``` @@ -99,6 +103,7 @@ Since GitHub API has rate limitations and to avoid abuse, the shared instance ha * Images are cached for 1 day (meaning that your metrics won't be updated until the next day) * A maximum of 1000 users can use this service * You're limited to 3 requests per hour (cached metrics are not counted) + * Plugins are not available You should consider deploying your own instance or use GitHub Action if you're planning using this service. @@ -197,6 +202,34 @@ Open and edit `settings.json` to configure your instance. //This is intendend for easier development which allows to see your changes quickly //Defaults to false "debug":false, + + //Plugins configuration + //Most of plugins are disabled by default + //Enabling them can add additional informations and metrics about you, but increases response time + "plugins":{ + //Pagespeed plugin + "pagespeed":{ + //Enable or disable this plugin + //When enabled, pass "?pagespeed=1" in url to generate website's performances + "enabled":false, + //Pagespeed token + //See https://developers.google.com/speed/docs/insights/v5/get-started to obtain a key + "token":"****************************************" + }, + //Lines plugin + "lines":{ + //Enable or disable this plugin + //When enabled, pass "?lines=1" in url to compute total lines added/removed in your repositories by you + "enabled":true + }, + //Traffic plugin + "traffic":{ + //Enable or disable this plugin + //When enabled, pass "?traffic=1" in url to compute total page views in your repositories in last two weeks + //Note that this requires that the passed GitHub API token requires a push access + "enabled":true + } + } } ``` @@ -289,6 +322,7 @@ Below is a list of useful documentations links : * [GitHub GraphQL API](https://docs.github.com/en/graphql) * [GitHub GraphQL Explorer](https://developer.github.com/v4/explorer/) +* [GitHub Rest API](https://docs.github.com/en/rest) ## 📦 Used packages diff --git a/action.yml b/action.yml index fb14394c..b6bd5089 100644 --- a/action.yml +++ b/action.yml @@ -15,7 +15,10 @@ inputs: description: Name of SVG image output default: github-metrics.svg pagespeed_token: - description: Pagespeed Personal Token (optional, will generate user's website performances if provided). See https://developers.google.com/speed/docs/insights/v5/get-started for more information. + description: Pagespeed Personal Token (optional, see https://developers.google.com/speed/docs/insights/v5/get-started for more information) + plugins: + description: List of additional plugins to enabled. Supported values are "lines", "pagespeed" (requires "pagespeed_token") and "traffic" (require "token" with "repository" permissions) + default: [] runs: using: node12 main: action/dist/index.js \ No newline at end of file diff --git a/action/dist/index.js b/action/dist/index.js index fc5ff776..2ca958bd 100644 --- a/action/dist/index.js +++ b/action/dist/index.js @@ -142,6 +142,12 @@ __webpack_require__.r(__webpack_exports__); \${data.user.packages.totalCount} Package\${data.user.packages.totalCount > 1 ? "s" : ""} + \${computed.plugins.lines ? \` +
+ + \${computed.plugins.lines.added} added, \${computed.plugins.lines.deleted} removed +
\` : "" + }
@@ -153,6 +159,12 @@ __webpack_require__.r(__webpack_exports__); \${data.computed.repositories.watchers} Watcher\${data.computed.repositories.watchers > 1 ? "s" : ""} + \${computed.plugins.traffic ? \` +
+ + \${computed.plugins.traffic.views.count} views in last two weeks +
\` : "" + }
@@ -440,6 +452,7 @@ __webpack_require__.r(__webpack_exports__); repositories(last: 100, isFork: false, ownerAffiliations: OWNER) { totalCount nodes { + name watchers { totalCount } @@ -528,11 +541,13 @@ __webpack_require__.r(__webpack_exports__); const rest = github.getOctokit(token) //Additional plugins - const plugins = {}, q = {} + const enabled = new Set(core.getInput("plugins", {default:[]})) + const plugins = {lines:{enabled:enabled.has("lines")}, traffic:{enabled:enabled.has("traffic")}, pagespeed:{enabled:enabled.has("pagespeed")}} if (core.getInput("pagespeed_token")) { - plugins.pagespeed = {enabled:true, token:core.getInput("pagespeed_token")} - q.pagespeed = true + console.log(`Pagespeed token | provided`) + plugins.pagespeed.token = core.getInput("pagespeed_token") } + const q = Object.fromEntries(Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => [key, true])) //Render metrics const rendered = await metrics({login:user, q}, {template, style, query, graphql, plugins}) @@ -9656,17 +9671,18 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ }); /* harmony import */ var image_to_base64__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7192); /* harmony import */ var image_to_base64__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(image_to_base64__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var _plugins_index_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9859); +/* harmony import */ var _plugins_index_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5820); //Imports //Setup - async function metrics({login, q}, {template, style, query, graphql, plugins}) { + async function metrics({login, q}, {template, style, query, graphql, rest, plugins}) { //Compute rendering try { //Query data from GitHub API + console.debug(`metrics/metrics/${login} > query`) const data = await graphql(query .replace(/[$]login/, `"${login}"`) .replace(/[$]calendar.to/, `"${(new Date()).toISOString()}"`) @@ -9682,6 +9698,8 @@ __webpack_require__.r(__webpack_exports__); //Plugins if (data.user.websiteUrl) _plugins_index_mjs__WEBPACK_IMPORTED_MODULE_1__/* .default.pagespeed */ .Z.pagespeed({url:data.user.websiteUrl, computed, pending, q}, plugins.pagespeed) + _plugins_index_mjs__WEBPACK_IMPORTED_MODULE_1__/* .default.lines */ .Z.lines({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.lines) + _plugins_index_mjs__WEBPACK_IMPORTED_MODULE_1__/* .default.traffic */ .Z.traffic({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.traffic) //Iterate through user's repositories for (const repository of data.user.repositories.nodes) { @@ -9728,6 +9746,7 @@ __webpack_require__.r(__webpack_exports__); await Promise.all(pending) //Eval rendering and return + console.debug(`metrics/metrics/${login} > computed`) return eval(`\`${template}\``) } //Internal error @@ -9736,7 +9755,7 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/***/ 9859: +/***/ 5820: /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; @@ -9746,6 +9765,51 @@ __webpack_require__.d(__webpack_exports__, { "Z": () => /* default */ E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_index }); +// CONCATENATED MODULE: E:\Users\lecoq\Documents\GitHub\gitstats\src\plugins\lines\index.mjs +//Formatter + function format(n) { + for (const {u, v} of [{u:"b", v:10**9}, {u:"m", v:10**6}, {u:"k", v:10**3}]) + if (n/v >= 1) + return `${(n/v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")}${u}` + return n + } + +//Setup + /* harmony default export */ function E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_lines_index({login, repositories = [], rest, computed, pending, q}, {enabled = false} = {}) { + //Check if plugin is enabled and requirements are met + if (!enabled) + return computed.plugins.lines = null + if (!q.lines) + return computed.plugins.lines = null + console.debug(`metrics/plugins/lines/${login} > started`) + + //Plugin execution + pending.push(new Promise(async solve => { + //Get contributors stats from repositories + const lines = {added:0, deleted:0} + const response = await Promise.all(repositories.map(async repo => await rest.repos.getContributorsStats({owner:login, repo}))) + //Compute changed lines + response.map(({data:repository}) => { + //Check if data are available + if (!repository) + return + //Extract author + const [contributor] = repository.filter(({author}) => author.login === login) + //Compute editions + if (contributor) + contributor.weeks.forEach(({a, d}) => (lines.added += a, lines.deleted += d)) + }) + //Format values + lines.added = format(lines.added) + lines.deleted = format(lines.deleted) + //Save results + computed.plugins = {lines} + console.debug(`metrics/plugins/lines/${login} > ${JSON.stringify(computed.plugins.lines)}`) + solve() + })) + } + + // EXTERNAL MODULE: E:\Users\lecoq\Documents\GitHub\gitstats\node_modules\axios\index.js var E_Users_lecoq_Documents_GitHub_gitstats_node_modules_axios_index = __webpack_require__(2390); var E_Users_lecoq_Documents_GitHub_gitstats_node_modules_axios_index_default = /*#__PURE__*/__webpack_require__.n(E_Users_lecoq_Documents_GitHub_gitstats_node_modules_axios_index); @@ -9755,14 +9819,17 @@ var E_Users_lecoq_Documents_GitHub_gitstats_node_modules_axios_index_default = / //Setup - /* harmony default export */ function E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_pagespeed_index({url, computed, pending, q}, {enabled = false, token = null} = {}) { + /* harmony default export */ function E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_pagespeed_index({login, url, computed, pending, q}, {enabled = false, token = null} = {}) { //Check if plugin is enabled and requirements are met if (!enabled) return computed.plugins.pagespeed = null + if (!token) + return computed.plugins.pagespeed = null if (!url) return computed.plugins.pagespeed = null if (!q.pagespeed) return computed.plugins.pagespeed = null + console.debug(`metrics/plugins/pagespeed/${login} > started`) //Plugin execution pending.push(new Promise(async solve => { @@ -9777,16 +9844,55 @@ var E_Users_lecoq_Documents_GitHub_gitstats_node_modules_axios_index_default = / })) //Save results computed.plugins.pagespeed = {url, scores:[scores.get("performance"), scores.get("accessibility"), scores.get("best-practices"), scores.get("seo")]} + console.debug(`metrics/plugins/pagespeed/${login} > ${JSON.stringify(computed.plugins.pagespeed)}`) + solve() + })) + } +// CONCATENATED MODULE: E:\Users\lecoq\Documents\GitHub\gitstats\src\plugins\traffic\index.mjs +//Formatter + function E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_traffic_index_format(n) { + for (const {u, v} of [{u:"b", v:10**9}, {u:"m", v:10**6}, {u:"k", v:10**3}]) + if (n/v >= 1) + return `${(n/v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")}${u}` + return n + } + +//Setup + /* harmony default export */ function E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_traffic_index({login, repositories = [], rest, computed, pending, q}, {enabled = false} = {}) { + //Check if plugin is enabled and requirements are met + if (!enabled) + return computed.plugins.traffic = null + if (!q.traffic) + return computed.plugins.traffic = null + console.debug(`metrics/plugins/traffic/${login} > started`) + + //Plugin execution + pending.push(new Promise(async solve => { + //Get views stats from repositories + const views = {count:0, uniques:0} + const response = await Promise.all(repositories.map(async repo => await rest.repos.getViews({owner:login, repo}))) + //Compute views + response.filter(({data}) => data).map(({data:{count, uniques}}) => (views.count += count, views.uniques += uniques)) + //Format values + views.count = E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_traffic_index_format(views.count) + views.uniques = E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_traffic_index_format(views.uniques) + //Save results + computed.plugins.traffic = {views} + console.debug(`metrics/plugins/traffic/${login} > ${JSON.stringify(computed.plugins.traffic)}`) solve() })) } // CONCATENATED MODULE: E:\Users\lecoq\Documents\GitHub\gitstats\src\plugins\index.mjs //Imports + + //Exports /* harmony default export */ const E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_index = ({ - pagespeed: E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_pagespeed_index + lines: E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_lines_index, + pagespeed: E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_pagespeed_index, + traffic: E_Users_lecoq_Documents_GitHub_gitstats_src_plugins_traffic_index, }); /***/ }), diff --git a/action/index.mjs b/action/index.mjs index 0260e976..5e85854a 100644 --- a/action/index.mjs +++ b/action/index.mjs @@ -30,11 +30,13 @@ const rest = github.getOctokit(token) //Additional plugins - const plugins = {}, q = {} + const enabled = new Set(core.getInput("plugins", {default:[]})) + const plugins = {lines:{enabled:enabled.has("lines")}, traffic:{enabled:enabled.has("traffic")}, pagespeed:{enabled:enabled.has("pagespeed")}} if (core.getInput("pagespeed_token")) { - plugins.pagespeed = {enabled:true, token:core.getInput("pagespeed_token")} - q.pagespeed = true + console.log(`Pagespeed token | provided`) + plugins.pagespeed.token = core.getInput("pagespeed_token") } + const q = Object.fromEntries(Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => [key, true])) //Render metrics const rendered = await metrics({login:user, q}, {template, style, query, graphql, plugins}) diff --git a/package-lock.json b/package-lock.json index f8076b74..3d286a6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,6 +77,11 @@ "@octokit/types": "^5.3.0" } }, + "@octokit/plugin-request-log": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz", + "integrity": "sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw==" + }, "@octokit/plugin-rest-endpoint-methods": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.1.4.tgz", @@ -111,6 +116,36 @@ "once": "^1.4.0" } }, + "@octokit/rest": { + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.0.6.tgz", + "integrity": "sha512-ES4lZBKPJMX/yUoQjAZiyFjei9pJ4lTTfb9k7OtYoUzKPDLl/M8jiHqt6qeSauyU4eZGLw0sgP1WiQl9FYeM5w==", + "requires": { + "@octokit/core": "^3.0.0", + "@octokit/plugin-paginate-rest": "^2.2.0", + "@octokit/plugin-request-log": "^1.0.0", + "@octokit/plugin-rest-endpoint-methods": "4.2.0" + }, + "dependencies": { + "@octokit/plugin-rest-endpoint-methods": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.2.0.tgz", + "integrity": "sha512-1/qn1q1C1hGz6W/iEDm9DoyNoG/xdFDt78E3eZ5hHeUfJTLJgyAMdj9chL/cNBHjcjd+FH5aO1x0VCqR2RE0mw==", + "requires": { + "@octokit/types": "^5.5.0", + "deprecation": "^2.3.1" + } + }, + "@octokit/types": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz", + "integrity": "sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ==", + "requires": { + "@types/node": ">= 8" + } + } + } + }, "@octokit/types": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-5.4.1.tgz", diff --git a/package.json b/package.json index 8d556f4d..eac1496a 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@actions/core": "^1.2.5", "@actions/github": "^4.0.0", "@octokit/graphql": "^4.5.4", + "@octokit/rest": "^18.0.6", "axios": "^0.20.0", "express": "^4.17.1", "express-rate-limit": "^5.1.3", diff --git a/settings.example.json b/settings.example.json index 3a0259d5..68fc11c6 100644 --- a/settings.example.json +++ b/settings.example.json @@ -6,10 +6,17 @@ "ratelimiter":null, "//":"Rate limiter (see express-rate-limit documentation for options)", "port":3000, "//":"Listening port", "debug":false, "//":"Debug mode", + "plugins":{ "//":"Additional plugins (optional)", - "pagespeed":{ "//":"Pagespeed configuration", + "pagespeed":{ "//":"Pagespeed plugin", "enabled":false, "//":"Enable or disable Pagespeed metrics", "token":"******", "//":"Pagespeed token" + }, + "traffic":{ "//":"Traffic plugin (GitHub API token must be RW for this to work)", + "enabled":true, "//":"Enable or disable repositories total page views is last two weeks" + }, + "lines":{ "//":"Lines plugin", + "enabled":true, "//":"Enable or disabled repositories total lines added/removed" } } } \ No newline at end of file diff --git a/src/app.mjs b/src/app.mjs index a7ff6ca9..0bab1610 100644 --- a/src/app.mjs +++ b/src/app.mjs @@ -3,6 +3,7 @@ import fs from "fs" import path from "path" import octokit from "@octokit/graphql" + import OctokitRest from "@octokit/rest" import cache from "memory-cache" import ratelimit from "express-rate-limit" import metrics from "./metrics.mjs" @@ -19,10 +20,11 @@ const settings = JSON.parse((await fs.promises.readFile(path.join("settings.json"))).toString()) const {token, maxusers = 0, restricted = [], debug = false, cached = 30*60*1000, port = 3000, ratelimiter = null, plugins = null} = settings if (debug) - console.log(settings) + console.debug(settings) //Load svg template, style and query let [template, style, query] = await load() const graphql = octokit.graphql.defaults({headers:{authorization: `token ${token}`}}) + const rest = new OctokitRest.Octokit({auth:token}) //Setup server const app = express() @@ -51,8 +53,10 @@ //Request params const {login} = req.params - if ((restricted.length)&&(!restricted.includes(login))) + if ((restricted.length)&&(!restricted.includes(login))) { + console.debug(`metrics/app/${login} > 403 (not in whitelisted users)`) return res.sendStatus(403) + } //Read cached data if possible if ((!debug)&&(cached)&&(cache.get(login))) { @@ -61,15 +65,17 @@ return } //Maximum simultaneous users - if ((maxusers)&&(cache.size()+1 > maxusers)) + if ((maxusers)&&(cache.size()+1 > maxusers)) { + console.debug(`metrics/app/${login} > 503 (maximum users reached)`) return res.sendStatus(503) + } //Compute rendering try { //Render if (debug) [template, style, query] = await load() - const rendered = await metrics({login, q:req.query}, {template, style, query, graphql, plugins}) + const rendered = await metrics({login, q:req.query}, {template, style, query, graphql, rest, plugins}) //Cache if ((!debug)&&(cached)) cache.put(login, rendered, cached) @@ -80,8 +86,10 @@ //Internal error catch (error) { //Not found user - if ((error instanceof Error)&&(/^user not found$/.test(error.message))) + if ((error instanceof Error)&&(/^user not found$/.test(error.message))) { + console.debug(`metrics/app/${login} > 404 (user not found)`) return res.sendStatus(404) + } //General error console.error(error) res.sendStatus(500) @@ -95,6 +103,7 @@ `Restricted to users | ${restricted.size ? [...restricted].join(", ") : "(unrestricted)"}`, `Cached time | ${cached} seconds`, `Rate limiter | ${ratelimiter ? JSON.stringify(ratelimiter) : "(enabled)"}`, - `Max simultaneous users | ${maxusers ? `${maxusers} users` : "(unrestricted)"}` + `Max simultaneous users | ${maxusers ? `${maxusers} users` : "(unrestricted)"}`, + `Plugins enabled | ${Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => key).join(", ")}` ].join("\n"))) } \ No newline at end of file diff --git a/src/metrics.mjs b/src/metrics.mjs index d9cc4aee..40b4432c 100644 --- a/src/metrics.mjs +++ b/src/metrics.mjs @@ -3,11 +3,12 @@ import Plugins from "./plugins/index.mjs" //Setup - export default async function metrics({login, q}, {template, style, query, graphql, plugins}) { + export default async function metrics({login, q}, {template, style, query, graphql, rest, plugins}) { //Compute rendering try { //Query data from GitHub API + console.debug(`metrics/metrics/${login} > query`) const data = await graphql(query .replace(/[$]login/, `"${login}"`) .replace(/[$]calendar.to/, `"${(new Date()).toISOString()}"`) @@ -23,6 +24,8 @@ //Plugins if (data.user.websiteUrl) Plugins.pagespeed({url:data.user.websiteUrl, computed, pending, q}, plugins.pagespeed) + Plugins.lines({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.lines) + Plugins.traffic({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.traffic) //Iterate through user's repositories for (const repository of data.user.repositories.nodes) { @@ -69,6 +72,7 @@ await Promise.all(pending) //Eval rendering and return + console.debug(`metrics/metrics/${login} > computed`) return eval(`\`${template}\``) } //Internal error diff --git a/src/plugins/index.mjs b/src/plugins/index.mjs index 39e6a4b9..aa3a4f8c 100644 --- a/src/plugins/index.mjs +++ b/src/plugins/index.mjs @@ -1,7 +1,11 @@ //Imports + import lines from "./lines/index.mjs" import pagespeed from "./pagespeed/index.mjs" + import traffic from "./traffic/index.mjs" //Exports export default { - pagespeed + lines, + pagespeed, + traffic, } \ No newline at end of file diff --git a/src/plugins/lines/index.mjs b/src/plugins/lines/index.mjs index dafd505d..df62392a 100644 --- a/src/plugins/lines/index.mjs +++ b/src/plugins/lines/index.mjs @@ -1 +1,43 @@ -//Placeholder for https://docs.github.com/en/rest/reference/repos#get-all-contributor-commit-activity \ No newline at end of file +//Formatter + function format(n) { + for (const {u, v} of [{u:"b", v:10**9}, {u:"m", v:10**6}, {u:"k", v:10**3}]) + if (n/v >= 1) + return `${(n/v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")}${u}` + return n + } + +//Setup + export default function ({login, repositories = [], rest, computed, pending, q}, {enabled = false} = {}) { + //Check if plugin is enabled and requirements are met + if (!enabled) + return computed.plugins.lines = null + if (!q.lines) + return computed.plugins.lines = null + console.debug(`metrics/plugins/lines/${login} > started`) + + //Plugin execution + pending.push(new Promise(async solve => { + //Get contributors stats from repositories + const lines = {added:0, deleted:0} + const response = await Promise.all(repositories.map(async repo => await rest.repos.getContributorsStats({owner:login, repo}))) + //Compute changed lines + response.map(({data:repository}) => { + //Check if data are available + if (!repository) + return + //Extract author + const [contributor] = repository.filter(({author}) => author.login === login) + //Compute editions + if (contributor) + contributor.weeks.forEach(({a, d}) => (lines.added += a, lines.deleted += d)) + }) + //Format values + lines.added = format(lines.added) + lines.deleted = format(lines.deleted) + //Save results + computed.plugins = {lines} + console.debug(`metrics/plugins/lines/${login} > ${JSON.stringify(computed.plugins.lines)}`) + solve() + })) + } + diff --git a/src/plugins/pagespeed/index.mjs b/src/plugins/pagespeed/index.mjs index 147700f9..97b3e235 100644 --- a/src/plugins/pagespeed/index.mjs +++ b/src/plugins/pagespeed/index.mjs @@ -2,14 +2,17 @@ import axios from "axios" //Setup - export default function ({url, computed, pending, q}, {enabled = false, token = null} = {}) { + export default function ({login, url, computed, pending, q}, {enabled = false, token = null} = {}) { //Check if plugin is enabled and requirements are met if (!enabled) return computed.plugins.pagespeed = null + if (!token) + return computed.plugins.pagespeed = null if (!url) return computed.plugins.pagespeed = null if (!q.pagespeed) return computed.plugins.pagespeed = null + console.debug(`metrics/plugins/pagespeed/${login} > started`) //Plugin execution pending.push(new Promise(async solve => { @@ -24,6 +27,7 @@ })) //Save results computed.plugins.pagespeed = {url, scores:[scores.get("performance"), scores.get("accessibility"), scores.get("best-practices"), scores.get("seo")]} + console.debug(`metrics/plugins/pagespeed/${login} > ${JSON.stringify(computed.plugins.pagespeed)}`) solve() })) } \ No newline at end of file diff --git a/src/plugins/traffic/index.mjs b/src/plugins/traffic/index.mjs index 2402fad3..2ca2a436 100644 --- a/src/plugins/traffic/index.mjs +++ b/src/plugins/traffic/index.mjs @@ -1 +1,33 @@ -//Placeholder for https://docs.github.com/en/rest/reference/repos#get-page-views \ No newline at end of file +//Formatter + function format(n) { + for (const {u, v} of [{u:"b", v:10**9}, {u:"m", v:10**6}, {u:"k", v:10**3}]) + if (n/v >= 1) + return `${(n/v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")}${u}` + return n + } + +//Setup + export default function ({login, repositories = [], rest, computed, pending, q}, {enabled = false} = {}) { + //Check if plugin is enabled and requirements are met + if (!enabled) + return computed.plugins.traffic = null + if (!q.traffic) + return computed.plugins.traffic = null + console.debug(`metrics/plugins/traffic/${login} > started`) + + //Plugin execution + pending.push(new Promise(async solve => { + //Get views stats from repositories + const views = {count:0, uniques:0} + const response = await Promise.all(repositories.map(async repo => await rest.repos.getViews({owner:login, repo}))) + //Compute views + response.filter(({data}) => data).map(({data:{count, uniques}}) => (views.count += count, views.uniques += uniques)) + //Format values + views.count = format(views.count) + views.uniques = format(views.uniques) + //Save results + computed.plugins.traffic = {views} + console.debug(`metrics/plugins/traffic/${login} > ${JSON.stringify(computed.plugins.traffic)}`) + solve() + })) + } \ No newline at end of file diff --git a/src/query.graphql b/src/query.graphql index 9dd21e7c..8abf4e43 100644 --- a/src/query.graphql +++ b/src/query.graphql @@ -8,6 +8,7 @@ query Metrics { repositories(last: 100, isFork: false, ownerAffiliations: OWNER) { totalCount nodes { + name watchers { totalCount } diff --git a/src/template.svg b/src/template.svg index 0010fdaf..6765e3a4 100644 --- a/src/template.svg +++ b/src/template.svg @@ -110,6 +110,12 @@ ${data.user.packages.totalCount} Package${data.user.packages.totalCount > 1 ? "s" : ""} + ${computed.plugins.lines ? ` +
+ + ${computed.plugins.lines.added} added, ${computed.plugins.lines.deleted} removed +
` : "" + }
@@ -121,6 +127,12 @@ ${data.computed.repositories.watchers} Watcher${data.computed.repositories.watchers > 1 ? "s" : ""} + ${computed.plugins.traffic ? ` +
+ + ${computed.plugins.traffic.views.count} views in last two weeks +
` : "" + }