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:
86
CONTRIBUTING.md
Normal file
86
CONTRIBUTING.md
Normal 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
102
README.md
@@ -3,13 +3,19 @@
|
||||
 
|
||||
|
||||
Generates your own GitHub metrics as an SVG image to put them on your profile page or elsewhere !
|
||||
See what it looks like below :
|
||||
|
||||

|
||||
|
||||
Still not enough data for you ? You can enable additional plugins for even more metrics !
|
||||
|
||||

|
||||
|
||||
### 🦑 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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
2
action/dist/index.js
vendored
2
action/dist/index.js
vendored
File diff suppressed because one or more lines are too long
@@ -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
78
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,66 +13,70 @@
|
||||
|
||||
<h1><a href="https://github.com/lowlighter/metrics">Generate your metrics !</a></h1>
|
||||
|
||||
<div class="step">
|
||||
<h2>1. Enter your GitHub username</h2>
|
||||
<label>
|
||||
<input type="text" v-model="user" maxlength="39" placeholder="GitHub username" :disabled="generated.pending">
|
||||
</label>
|
||||
</div>
|
||||
<template>
|
||||
|
||||
<div class="step">
|
||||
<h2>2. Select a template 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">
|
||||
{{ templates.descriptions[template] || template }}
|
||||
<div class="step">
|
||||
<h2>1. Enter your GitHub username</h2>
|
||||
<label>
|
||||
<input type="text" v-model="user" maxlength="39" placeholder="GitHub username" :disabled="generated.pending">
|
||||
</label>
|
||||
</div>
|
||||
<div class="plugins">
|
||||
<label v-for="plugin in plugins.list" :key="plugin">
|
||||
<input type="checkbox" v-model="plugins.enabled[plugin]" @change="load" :disabled="generated.pending">
|
||||
{{ plugins.descriptions[plugin] || plugin }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="cache-notice">
|
||||
*To reduce server overhead, metrics are cached. Changes may not be reflected until cache expiration.
|
||||
</div>
|
||||
<div class="palette">
|
||||
Generated metrics use transparency and colors which matches both light and dark modes
|
||||
<div class="palettes">
|
||||
<label>
|
||||
<input type="radio" v-model="palette" value="light">
|
||||
☀️ Light mode
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" v-model="palette" value="dark">
|
||||
🌙 Night mode
|
||||
|
||||
<div class="step">
|
||||
<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">
|
||||
{{ templates.descriptions[template] || template }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<h2>3. Generate your metrics</h2>
|
||||
<template v-if="generated.content">
|
||||
<img class="metrics" :src="generated.content" alt="metrics">
|
||||
</template>
|
||||
<template v-else>
|
||||
<img class="metrics" :src="templates.placeholder" alt="metrics">
|
||||
<button @click="generate" :disabled="(!user)||(generated.pending)">{{ generated.pending ? "Generating your metrics..." : user ? "Generate your metrics" : "Enter your GitHub username first" }}</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<h2>4. Embed these metrics on your GitHub profile</h2>
|
||||
<template v-if="user">
|
||||
Add the markdown below in your <i>README.md</i> at <a :href="repo">{{ user }}/{{ user }}</a>
|
||||
<div class="code">
|
||||

|
||||
<div class="plugins">
|
||||
<label v-for="plugin in plugins.list" :key="plugin">
|
||||
<input type="checkbox" v-model="plugins.enabled[plugin]" @change="load" :disabled="generated.pending">
|
||||
{{ plugins.descriptions[plugin] || plugin }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
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>
|
||||
<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">
|
||||
Generated metrics use transparency and colors which matches both light and dark modes
|
||||
<div class="palettes">
|
||||
<label>
|
||||
<input type="radio" v-model="palette" value="light">
|
||||
☀️ Light mode
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" v-model="palette" value="dark">
|
||||
🌙 Night mode
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<h2>3. Generate your metrics</h2>
|
||||
<template v-if="generated.content">
|
||||
<img class="metrics" :src="generated.content" alt="metrics">
|
||||
</template>
|
||||
<template v-else>
|
||||
<img class="metrics" :src="templates.placeholder" alt="metrics">
|
||||
<button @click="generate" :disabled="(!user)||(generated.pending)">{{ generated.pending ? "Generating your metrics..." : user ? "Generate your metrics" : "Enter your GitHub username first" }}</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<h2>4. Embed these metrics on your GitHub profile</h2>
|
||||
<template v-if="user">
|
||||
Add the markdown below in your <i>README.md</i> at <a :href="repo">{{ user }}/{{ user }}</a>
|
||||
<div class="code">
|
||||

|
||||
</div>
|
||||
</template>
|
||||
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>
|
||||
|
||||
@@ -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}` : ""}`
|
||||
|
||||
@@ -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()}"`)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -20,25 +20,68 @@
|
||||
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
|
||||
await test()
|
||||
console.log("Test success !")
|
||||
}
|
||||
|
||||
//Main
|
||||
if (/metrics.mjs/.test(process.argv[1])) {
|
||||
//Test
|
||||
await test()
|
||||
console.log("Test success !")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user