Version 2.1

- No plugins are enabled by default
- Logs from setup can be hidden
- Number of repositories to inspect can be configured (default to 100)
- Default events for habits plugin is now 100
- Number of events for habits can be overriden in query
- Server app improvments
- Test improvements
- Better test
This commit is contained in:
lowlighter
2020-10-23 13:56:15 +02:00
parent 543c6f8f98
commit 2cf152d7d1
16 changed files with 311 additions and 200 deletions

86
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,86 @@
# 📊 GitHub metrics
## 💪 Interested in contributing ?
Nice ! Read the few sections below to understand how this project is structured.
### 👨‍💻 General informations
#### Adding new metrics through GraphQL API, REST API or Third-Party service
To use [GitHub GraphQL API](https://docs.github.com/en/graphql), update the GraphQL query from `templates/*/query.graphql`.
Raw queried data should be exposed in `data.user` whereas computed data should be in `data.computed`, and code should be updated through `templates/*/template.mjs`.
To use [GitHub Rest API](https://docs.github.com/en/rest) or a third-party service instead, create a new plugin in `src/plugins`.
Plugins should be self-sufficient and re-exported from [src/plugins/index.mjs](https://github.com/lowlighter/metrics/blob/master/src/plugins/index.mjs), to be later included in the `//Plugins` section of `templates/*/template.mjs`.
Data generated should be exposed in `data.computed.plugins[plugin]` where `plugin` is your plugin's name.
#### Updating the SVG template
The SVG template is located in `templates/*/image.svg` and include the CSS from `templates/*/style.css`.
It is rendered with [EJS](https://github.com/mde/ejs) so you can actually include variables (e.g. `<%= user.name %>`) and execute simple code, like control statements.
#### Metrics server and GitHub action
Most of the time, you won't need to edit these, unless you're integrating features directly tied to them.
Remember that SVG image is actually generated from `src/metrics.mjs`, independently from metrics server and GitHub action.
Metrics server code is located in `src/app.mjs` and instantiates an `express` server app, `octokit`s instances, middlewares (like rate-limiter) and routes.
GitHub action code is located in `action/index.mjs` and instantiates `octokit`s instances and retrieves action parameters.
It then use directly `src/metrics.mjs` to generate the SVG image and commit them to user's repository.
You must run `npm run build` to rebuild the GitHub action.
#### Testing new features
To test new features, setup a metrics server with a test token and `debug` mode enabled.
This way you'll be able to rapidly test SVG renders with your browser.
### 🗂️ Project structure
#### Metrics generator
* `src/setup.mjs` contains the configuration setup
* `src/metrics.mjs` contains the metrics renderer
* `src/templates/*` contains templates files
* `src/templates/*/image.svg` contains the template used by the generated SVG image
* `src/templates/*/query.graphql` is the GraphQL query sent to GitHub GraphQL API
* `src/templates/*/style.css` contains the style used by the generated SVG image
* `src/templates/*/template.mjs` contains the code which prepares data for rendering
* `src/plugins/*` contains the source code of metrics plugins
#### Metrics server instance
* `index.mjs` contains the metrics server entry point
* `src/app.mjs` contains the metrics server code which serves, renders, restricts/rate limit, etc.
#### GitHub action
* `action.yml` contains the GitHub action descriptor
* `action/index.mjs` contains the GitHub action code
* `action/dist/index.js` contains compiled the GitHub action code
* `utils/build.mjs` contains the GitHub action builder
### 📦 Used packages
* [express/express.js](https://github.com/expressjs/express) and [expressjs/compression](https://github.com/expressjs/compression)
* To serve, compute and render a GitHub user's metrics
* [nfriedly/express-rate-limit](https://github.com/nfriedly/express-rate-limit)
* To apply rate limiting on server and avoid spams and hitting GitHub API's own rate limit
* [octokit/graphql.js](https://github.com/octokit/graphql.js/) and [octokit/rest.js](https://github.com/octokit/rest.js)
* To perform request to GitHub GraphQL API and GitHub REST API
* [mde/ejs](https://github.com/mde/ejs)
* To render SVG images
* [ptarjan/node-cache](https://github.com/ptarjan/node-cache)
* To cache generated content
* [renanbastos93/image-to-base64](https://github.com/renanbastos93/image-to-base64)
* To generate base64 representation of users' avatars
* [svg/svgo](https://github.com/svg/svgo)
* To optimize generated SVG
* [axios/axios](https://github.com/axios/axios)
* To make HTTP/S requests
* [actions/toolkit](https://github.com/actions/toolkit/tree/master) and [vercel/ncc](https://github.com/vercel/ncc)
* To build the GitHub Action
* [vuejs/vue](https://github.com/vuejs/vue)
* To display server application

102
README.md
View File

@@ -3,13 +3,19 @@
![Build](https://github.com/lowlighter/metrics/workflows/Build/badge.svg) ![Analysis](https://github.com/lowlighter/metrics/workflows/Analysis/badge.svg)
Generates your own GitHub metrics as an SVG image to put them on your profile page or elsewhere !
See what it looks like below :
![GitHub metrics](https://github.com/lowlighter/lowlighter/blob/master/github-metrics-alt.svg)
Still not enough data for you ? You can enable additional plugins for even more metrics !
![GitHub metrics](https://github.com/lowlighter/lowlighter/blob/master/github-metrics.svg)
### 🦑 Interested to get your own ?
Try it now at [metrics.lecoq.io](https://metrics.lecoq.io/) with your GitHub username !
For a fully-featured experience, setup this as a [GitHub Action](https://github.com/marketplace/actions/github-metrics-as-svg-image) !
## 📜 How to use ?
### ⚙️ Using GitHub Action on your profile repo (~5 min setup)
@@ -150,9 +156,10 @@ Since GitHub API has rate limitations, the shared instance has a few limitations
* Your generated metrics won't be updated during this amount of time
* If you enable or disable plugins in url parameters, you'll need to wait for cache expiration before these changes are applied
* The rate limiter is enabled, although it won't affect already cached users metrics
* Plugins which consume additional requests or require elevated token rights are disabled
* PageSpeed plugin can still be enabled by passing `?pagespeed=1`, but metrics generation can take up some time when it has not been cached yet
* Languages and Follow-up plugins are enabled by default by can be disabled respectively with `?languages=0` and `?followup=0`
* Plugins which consume additional requests or require elevated token rights are disabled. The following plugins are available :
* PageSpeed plugin can be enabled by passing `?pagespeed=1`, but metrics generation can take up some time when it has not been cached yet
* Languages plugin can be enabled by passing `?languages=1`
* Follow-up plugin can be enabled by passing `?followup=1`
To ensure maximum availability, consider deploying your own instance or use the GitHub Action.
@@ -267,7 +274,7 @@ Open and edit `settings.json` to configure your instance using a text editor of
//Enable or disable this plugin. Pass "?habits=1" in url to generate coding habits based on your recent activity
"enabled":true,
//Number of events used to compute coding habits (capped at 100 by GitHub API)
"from":50,
"from":100,
}
}
}
@@ -511,8 +518,6 @@ The *follow-up* plugin allows you to compute the ratio of opened/closed issues a
<details>
<summary>💬 About</summary>
This plugin is enabled by default. To disable it, explicitly opt-out.
##### Setup with GitHub actions
Add the following to your workflow :
@@ -545,8 +550,6 @@ The *languages* plugin allows you to compute which languages you use the most in
<details>
<summary>💬 About</summary>
This plugin is enabled by default. To disable it, explicitly opt-out.
##### Setup with GitHub actions
Add the following to your workflow :
@@ -591,69 +594,13 @@ Add the following to your workflow :
</details>
### 🗂️ Project structure
#### Metrics generator
* `src/setup.mjs` contains the configuration setup
* `src/metrics.mjs` contains the metrics renderer
* `src/templates/*` contains templates files
* `src/templates/*/image.svg` contains the template used by the generated SVG image
* `src/templates/*/query.graphql` is the GraphQL query sent to GitHub GraphQL API
* `src/templates/*/style.css` contains the style used by the generated SVG image
* `src/templates/*/template.mjs` contains the code which prepares data for rendering
* `src/plugins/*` contains the source code of metrics plugins
#### Metrics server instance
* `index.mjs` contains the metrics server entry point
* `src/app.mjs` contains the metrics server code which serves, renders, restricts/rate limit, etc.
#### GitHub action
* `action.yml` contains the GitHub action descriptor
* `action/index.mjs` contains the GitHub action code
* `action/dist/index.js` contains compiled the GitHub action code
* `utils/build.mjs` contains the GitHub action builder
### 💪 Contributing and customizing
If you would like to suggest a new feature, find a bug or need help, you can fill an [issue](https://github.com/lowlighter/metrics/issues) describing your problem.
If you're motivated enough, you can submit a [pull request](https://github.com/lowlighter/metrics/pulls) to integrate new features or to solve open issues.
Read the few sections below to get started with project structure.
#### Adding new metrics through GraphQL API, REST API or Third-Party service
To use [GitHub GraphQL API](https://docs.github.com/en/graphql), update the GraphQL query from `templates/*/query.graphql`.
Raw queried data should be exposed in `data.user` whereas computed data should be in `data.computed`, and code should be updated through `templates/*/template.mjs`.
To use [GitHub Rest API](https://docs.github.com/en/rest) or a third-party service instead, create a new plugin in `src/plugins`.
Plugins should be self-sufficient and re-exported from [src/plugins/index.mjs](https://github.com/lowlighter/metrics/blob/master/src/plugins/index.mjs), to be later included in the `//Plugins` section of `templates/*/template.mjs`.
Data generated should be exposed in `data.computed.plugins[plugin]` where `plugin` is your plugin's name.
#### Updating the SVG template
The SVG template is located in `templates/*/image.svg` and include the CSS from `templates/*/style.css`.
It is rendered with [EJS](https://github.com/mde/ejs) so you can actually include variables (e.g. `<%= user.name %>`) and execute simple code, like control statements.
#### Metrics server and GitHub action
Most of the time, you won't need to edit these, unless you're integrating features directly tied to them.
Remember that SVG image is actually generated from `src/metrics.mjs`, independently from metrics server and GitHub action.
Metrics server code is located in `src/app.mjs` and instantiates an `express` server app, `octokit`s instances, middlewares (like rate-limiter) and routes.
GitHub action code is located in `action/index.mjs` and instantiates `octokit`s instances and retrieves action parameters.
It then use directly `src/metrics.mjs` to generate the SVG image and commit them to user's repository.
You must run `npm run build` to rebuild the GitHub action.
#### Testing new features
To test new features, setup a metrics server with a test token and `debug` mode enabled.
This way you'll be able to rapidly test SVG renders with your browser.
Read [contributing.md](https://github.com/lowlighter/metrics/blob/master/CONTRIBUTING.md) for more information about this.
### 📖 Useful references
@@ -661,29 +608,6 @@ This way you'll be able to rapidly test SVG renders with your browser.
* [GitHub GraphQL Explorer](https://developer.github.com/v4/explorer/)
* [GitHub Rest API](https://docs.github.com/en/rest)
### 📦 Used packages
* [express/express.js](https://github.com/expressjs/express) and [expressjs/compression](https://github.com/expressjs/compression)
* To serve, compute and render a GitHub user's metrics
* [nfriedly/express-rate-limit](https://github.com/nfriedly/express-rate-limit)
* To apply rate limiting on server and avoid spams and hitting GitHub API's own rate limit
* [octokit/graphql.js](https://github.com/octokit/graphql.js/) and [octokit/rest.js](https://github.com/octokit/rest.js)
* To perform request to GitHub GraphQL API and GitHub REST API
* [mde/ejs](https://github.com/mde/ejs)
* To render SVG images
* [ptarjan/node-cache](https://github.com/ptarjan/node-cache)
* To cache generated content
* [renanbastos93/image-to-base64](https://github.com/renanbastos93/image-to-base64)
* To generate base64 representation of users' avatars
* [svg/svgo](https://github.com/svg/svgo)
* To optimize generated SVG
* [axios/axios](https://github.com/axios/axios)
* To make HTTP/S requests
* [actions/toolkit](https://github.com/actions/toolkit/tree/master) and [vercel/ncc](https://github.com/vercel/ncc)
* To build the GitHub Action
* [vuejs/vue](https://github.com/vuejs/vue)
* To display server application
All icons were ripped across GitHub's site, but still remains the intellectual property of GitHub.
See [GitHub Logos and Usage](https://github.com/logos) for more information.

View File

@@ -43,10 +43,10 @@ inputs:
default: no
plugin_languages:
description: Enable most used languages metrics
default: yes
default: no
plugin_followup:
description: Enable owned repositories issues and pull requests metrics
default: yes
default: no
debug:
description: Enable debug logs
default: no

File diff suppressed because one or more lines are too long

View File

@@ -28,7 +28,7 @@
}
//Load configuration
const conf = await setup()
const conf = await setup({log:false})
console.log(`Configuration | loaded`)
//Load svg template, style and query

78
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "metrics",
"version": "1.9.0",
"version": "2.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -37,9 +37,9 @@
}
},
"@octokit/core": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.1.2.tgz",
"integrity": "sha512-AInOFULmwOa7+NFi9F8DlDkm5qtZVmDQayi7TUgChE3yeIGPq0Y+6cAEXPexQ3Ea+uZy66hKEazR7DJyU+4wfw==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.1.3.tgz",
"integrity": "sha512-s5UyENGUQBB+ocEOulXq6UH5J16fxuKY2J7ZYrIu9oJYAn0nCwM8hC8o4L23HEzU0SFzNEX86+ffc1T3Vr2ybg==",
"requires": {
"@octokit/auth-token": "^2.4.0",
"@octokit/graphql": "^4.3.1",
@@ -78,9 +78,9 @@
}
},
"@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=="
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.1.tgz",
"integrity": "sha512-d8vmiGAUGswxErdIGfpd0I2UHo2Cs7EaBDpFUZQ9UqYmA0s5/4XoMO4HBld73xGpCj2BvyVyQe2qd9e+/nvKwQ=="
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "4.2.0",
@@ -136,9 +136,9 @@
}
},
"@types/node": {
"version": "14.11.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.10.tgz",
"integrity": "sha512-yV1nWZPlMFpoXyoknm4S56y2nlTAuFYaJuQtYRAOU7xA/FJ9RY0Xm7QOkaYMMmr8ESdHIuUb6oQgR/0+2NqlyA=="
"version": "14.14.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.2.tgz",
"integrity": "sha512-jeYJU2kl7hL9U5xuI/BhKPZ4vqGM/OmK6whiFAXVhlstzZhVamWhDSmHyGLIp+RVyuF9/d0dqr2P85aFj4BvJg=="
},
"@types/q": {
"version": "1.5.4",
@@ -1421,21 +1421,63 @@
}
},
"string.prototype.trimend": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
"integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz",
"integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==",
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5"
"es-abstract": "^1.18.0-next.1"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz",
"integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==",
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.0",
"is-regex": "^1.1.1",
"object-inspect": "^1.8.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.1",
"string.prototype.trimend": "^1.0.1",
"string.prototype.trimstart": "^1.0.1"
}
}
}
},
"string.prototype.trimstart": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
"integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz",
"integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==",
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5"
"es-abstract": "^1.18.0-next.1"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz",
"integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==",
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.0",
"is-regex": "^1.1.1",
"object-inspect": "^1.8.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.1",
"string.prototype.trimend": "^1.0.1",
"string.prototype.trimstart": "^1.0.1"
}
}
}
},
"string_decoder": {

View File

@@ -1,6 +1,6 @@
{
"name": "metrics",
"version": "2.0.0",
"version": "2.1.0",
"description": "Generate an user's GitHub metrics as SVG image format to embed somewhere else",
"main": "index.mjs",
"scripts": {

View File

@@ -7,6 +7,7 @@
"port":3000, "//":"Listening port",
"optimize":true, "//":"Optimize SVG image",
"debug":false, "//":"Debug mode",
"repositories":100, "//":"Number of repositories to use to compute metrics",
"templates":{ "//":"Template configuration",
"default":"classic", "//":"Default template",
@@ -15,24 +16,24 @@
"plugins":{ "//":"Additional plugins (optional)",
"pagespeed":{ "//":"Pagespeed plugin",
"enabled":false, "//":"Enable or disable PageSpeed metrics",
"token":"******", "//":"Pagespeed token"
"enabled":true, "//":"Enable or disable PageSpeed metrics",
"token":null, "//":"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"
"enabled":false, "//":"Enable or disable repositories total page views is last two weeks"
},
"lines":{ "//":"Lines plugin",
"enabled":true, "//":"Enable or disable repositories total lines added/removed"
"enabled":false, "//":"Enable or disable repositories total lines added/removed"
},
"habits":{ "//":"Habits plugin",
"enabled":true, "//":"Enable or disable coding habits metrics",
"from":50, "//":"Number of activity events to base habits on (up to 100)"
"enabled":false, "//":"Enable or disable coding habits metrics",
"from":100, "//":"Number of activity events to base habits on (up to 100)"
},
"languages":{ "//":"Languages plugins",
"enabled":true, "//":"Enable or disable most used languages metrics (*this plugin is enabled by default)"
"enabled":true, "//":"Enable or disable most used languages metrics"
},
"followup":{ "//":"Follow-up plugin",
"enabled":true, "//":"Enable owned repositories issues and pull requests metrics (*this plugin is enabled by default)"
"enabled":true, "//":"Enable owned repositories issues and pull requests metrics"
}
}
}

View File

@@ -13,6 +13,8 @@
<h1><a href="https://github.com/lowlighter/metrics">Generate your metrics !</a></h1>
<template>
<div class="step">
<h2>1. Enter your GitHub username</h2>
<label>
@@ -21,7 +23,7 @@
</div>
<div class="step">
<h2>2. Select a template and enable additional plugins</h2>
<h2>2. Select a template {{ plugins.list.length ? "and enable additional plugins" : "" }}</h2>
<div class="templates">
<label v-for="template in templates.list" :key="template">
<input type="radio" v-model="templates.selected" :value="template" @change="load" :disabled="generated.pending">
@@ -34,7 +36,7 @@
{{ plugins.descriptions[plugin] || plugin }}
</label>
</div>
<div class="cache-notice">
<div class="cache-notice" v-if="plugins.list.length">
*To reduce server overhead, metrics are cached. Changes may not be reflected until cache expiration.
</div>
<div class="palette">
@@ -74,6 +76,8 @@
For even more features, setup <a href="https://github.com/lowlighter/metrics">lowlighter/metrics</a> as a <a href="https://github.com/marketplace/actions/github-metrics-as-svg-image">GitHub action</a> !
</div>
</template>
</main>
<script src="/axios.min.js"></script>
@@ -91,7 +95,7 @@
palette:"light",
plugins:{
list:(await axios.get("/plugins.list")).data,
enabled:{languages:true, followup:true},
enabled:{},
descriptions:{
pagespeed:"Website performances",
languages:"Most used languages",
@@ -122,7 +126,7 @@
},
url() {
const plugins = Object.entries(this.plugins.enabled)
.filter(([key, value]) => /^(?:languages|followup)$/.test(key) ? !value : value)
.filter(([key, value]) => value)
.map(([key, value]) => `${key}=${+value}`)
.join("&")
return `${window.location.href}${this.user}${plugins.length ? `?${plugins}` : ""}`

View File

@@ -14,6 +14,7 @@
console.debug(`metrics/compute/${login} > start`)
console.debug(JSON.stringify(q))
const template = q.template || conf.settings.templates.default
const repositories = Math.max(0, Number(q.repositories)) || conf.settings.repositories || 100
const pending = []
const s = (value, end = "") => value > 1 ? {y:"ies", "":"s"}[end] : end
if ((!(template in Templates))||(!(template in conf.templates))||((conf.settings.templates.enabled.length)&&(!conf.settings.templates.enabled.includes(template))))
@@ -24,6 +25,7 @@
console.debug(`metrics/compute/${login} > query`)
const data = await graphql(query
.replace(/[$]login/, `"${login}"`)
.replace(/[$]repositories/, `${repositories}`)
.replace(/[$]calendar.to/, `"${(new Date()).toISOString()}"`)
.replace(/[$]calendar.from/, `"${(new Date(Date.now()-14*24*60*60*1000)).toISOString()}"`)
)

View File

@@ -1,5 +1,5 @@
//Setup
export default function ({login, data, computed, pending, q}, {enabled = true} = {}) {
export default function ({login, data, computed, pending, q}, {enabled = false} = {}) {
//Check if plugin is enabled and requirements are met
if (!enabled)
return computed.plugins.followup = null

View File

@@ -1,5 +1,5 @@
//Setup
export default function ({login, rest, computed, pending, q}, {enabled = false, from = 50} = {}) {
export default function ({login, rest, computed, pending, q}, {enabled = false, from = 100} = {}) {
//Check if plugin is enabled and requirements are met
if (!enabled)
return computed.plugins.habits = null
@@ -8,6 +8,12 @@
console.debug(`metrics/compute/${login}/plugins > habits`)
computed.svg.height += 70
//Parameter override
if (typeof q["habits.from"] === "number") {
from = Math.max(0, Math.min(from, q["habits.from"]))
console.debug(`metrics/compute/${login}/plugins > habits > events = ${from}`)
}
//Plugin execution
pending.push(new Promise(async solve => {
try {

View File

@@ -1,5 +1,5 @@
//Setup
export default function ({login, data, computed, pending, q}, {enabled = true} = {}) {
export default function ({login, data, computed, pending, q}, {enabled = false} = {}) {
//Check if plugin is enabled and requirements are met
if (!enabled)
return computed.plugins.languages = null

View File

@@ -3,10 +3,11 @@
import path from "path"
/** Setup */
export default async function () {
export default async function ({log = true} = {}) {
//Init
console.debug(`metrics/setup > setup`)
const logger = log ? console.debug : () => null
logger(`metrics/setup > setup`)
const templates = "src/templates"
const conf = {
templates:{},
@@ -16,17 +17,19 @@
}
//Load settings
console.debug(`metrics/setup > load settings.json`)
logger(`metrics/setup > load settings.json`)
if (fs.existsSync(path.resolve("settings.json"))) {
conf.settings = JSON.parse(`${await fs.promises.readFile(path.resolve("settings.json"))}`)
console.debug(`metrics/setup > load settings.json > success`)
logger(`metrics/setup > load settings.json > success`)
}
else
console.debug(`metrics/setup > load settings.json > (missing)`)
logger(`metrics/setup > load settings.json > (missing)`)
if (!conf.settings.templates)
conf.settings.templates = {default:"classic", enabled:[]}
if (!conf.settings.plugins)
conf.settings.plugins = {}
if (conf.settings.debug)
console.debug(conf.settings)
logger(conf.settings)
//Load templates
if (fs.existsSync(path.resolve(templates))) {
@@ -34,7 +37,7 @@
//Cache templates
if (/^index.mjs$/.test(name))
continue
console.debug(`metrics/setup > load template [${name}]`)
logger(`metrics/setup > load template [${name}]`)
const files = [
`${templates}/${name}/query.graphql`,
`${templates}/${name}/image.svg`,
@@ -43,14 +46,14 @@
]
const [query, image, placeholder, style] = await Promise.all(files.map(async file => `${await fs.promises.readFile(path.resolve(file))}`))
conf.templates[name] = {query, image, placeholder, style}
console.debug(`metrics/setup > load template [${name}] > success`)
logger(`metrics/setup > load template [${name}] > success`)
//Debug
if (conf.settings.debug) {
Object.defineProperty(conf.templates, name, {
get() {
console.debug(`metrics/setup > reload template [${name}]`)
logger(`metrics/setup > reload template [${name}]`)
const [query, image, placeholder, style] = files.map(file => `${fs.readFileSync(path.resolve(file))}`)
console.debug(`metrics/setup > reload template [${name}] > success`)
logger(`metrics/setup > reload template [${name}] > success`)
return {query, image, placeholder, style}
}
})
@@ -58,12 +61,12 @@
}
}
else {
console.debug(`metrics/setup > load templates from build`)
logger(`metrics/setup > load templates from build`)
conf.templates = JSON.parse(Buffer.from(`<#assets>`, "base64").toString("utf8"))
}
//Conf
console.debug(`metrics/setup > setup > success`)
logger(`metrics/setup > setup > success`)
return conf
}

View File

@@ -8,7 +8,7 @@ query Metrics {
gists {
totalCount
}
repositories(last: 100, isFork: false, ownerAffiliations: OWNER) {
repositories(last: $repositories, isFork: false, ownerAffiliations: OWNER) {
totalCount
nodes {
name

View File

@@ -20,22 +20,65 @@
const graphql = octokit.graphql.defaults({headers:{authorization: `token ${token}`}})
const rest = new OctokitRest.Octokit({auth:token})
//Perform tests
await test.build()
for (const template of [
"classic"
]) {
for (const q of [
{},
{followup:1},
{languages:1},
{followup:1, languages:1},
{habits:1, "habits.events":1},
{lines:1},
{traffic:1},
{selfskip:1},
{pagespeed:1},
{followup:1, languages:1, habits:1, "habits.events":1, lines:1, traffic:1, selfskip:1, pagespeed:1}
]) {
await test.metrics({graphql, rest, q:{template, repositories:1, ...q}})
}
}
}
/** Metrics tests */
test.metrics = async function ({graphql, rest, q}) {
//Preparation
console.log(`### Checking metrics with plugins [${Object.keys(q).filter(key => /^\w+$/.test(key)).join(", ")}]`)
const plugins = {
lines:{enabled:true},
traffic:{enabled:true},
pagespeed:{enabled:true},
habits:{enabled:true},
selfskip:{enabled:true},
languages:{enabled:true},
followup:{enabled:true},
}
//Compute render
const conf = await setup()
const rendered = await metrics({login:"lowlighter", q:{}}, {graphql, rest, plugins:{}, conf})
console.log("#### Checking that SVG can be generated")
const conf = await setup({log:false})
const rendered = await metrics({login:"lowlighter", q}, {graphql, rest, plugins, conf})
//Ensure it's a well-formed SVG image
console.log("#### Checking that generated SVG can be parsed")
const parsed = libxmljs.parseXml(rendered)
if (parsed.errors.length)
throw new Error(`Malformed SVG : \n${parsed.errors.join("\n")}`)
}
/** Build test */
test.build = async function () {
//Ensure that action has been rebuild
console.log("### Checking that code has been rebuild")
const action = `${await fs.promises.readFile(`${__dirname}/dist/index.js`)}`
const code = await build()
if (action !== code)
throw new Error(`GitHub Action has not been rebuild. Run "npm run build" to solve this issue`)
}
//Main
if (/metrics.mjs/.test(process.argv[1])) {
//Test