Add tests and optimization/compression for rendered metrics

This commit is contained in:
lowlighter
2020-10-13 14:00:48 +02:00
parent 1b738d11d2
commit 39404a9acf
10 changed files with 1055 additions and 32 deletions

View File

@@ -6,7 +6,7 @@ See what it looks like below :
![GitHub metrics](https://github.com/lowlighter/lowlighter/blob/master/github-metrics.svg) ![GitHub metrics](https://github.com/lowlighter/lowlighter/blob/master/github-metrics.svg)
##### 🦑 Interested to get your own ? ##### 🦑 Interested to get your own ?
Try it now at [metrics.lecoq.io](https://metrics.lecoq.io/) with your GitHub username ! Try it now at [metrics.lecoq.io](https://metrics.lecoq.io/) with your GitHub username !
## 📜 How to use ? ## 📜 How to use ?
@@ -482,17 +482,17 @@ And pass `?traffic=1` in url when generating metrics.
* `action/index.mjs` contains the GitHub action code * `action/index.mjs` contains the GitHub action code
* `action/dist/index.js` contains compiled the GitHub action code * `action/dist/index.js` contains compiled the GitHub action code
* `utils/*` contains various utilitaries for build * `utils/*` contains various utilitaries for build
### 💪 Contributing ### 💪 Contributing
If you would like to suggest a new feature or find a bug, you can fill an [issue](https://github.com/lowlighter/metrics/issues) describing your problem. If you would like to suggest a new feature or find a bug, 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. 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. Read the few sections below to get started with project structure.
#### Adding new metrics through GraphQL API, REST API or Third-Party service #### Adding new metrics through GraphQL API, REST API or Third-Party service
If you want to gather additional metrics, update the GraphQL query from `src/query.graphql` to get additional data from [GitHub GraphQL API](https://docs.github.com/en/graphql). If you want to gather additional metrics, update the GraphQL query from `src/query.graphql` to get additional data from [GitHub GraphQL API](https://docs.github.com/en/graphql).
Add additional computations and formatting in `src/metrics.mjs`. Add additional computations and formatting in `src/metrics.mjs`.
Raw queried data should be exposed in `data.user` whereas computed data should be in `data.computed`. Raw queried data should be exposed in `data.user` whereas computed data should be in `data.computed`.
@@ -517,7 +517,7 @@ It then use directly `src/metrics.mjs` to generate the SVG image and commit them
#### Testing new features #### Testing new features
To test new features, you'll need to follow the first steps of the `Deploying your own instance` tutorial. To test new features, you'll need to follow the first steps of the `Deploying your own instance` tutorial.
Basically you create a `settings.json` containing a test token and `debug` mode enabled. Basically you create a `settings.json` containing a test token and `debug` mode enabled.
You can then start the node with `npm start` and you'll be able to test how the SVG renders with your editions by opening the server url in your browser. You can then start the node with `npm start` and you'll be able to test how the SVG renders with your editions by opening the server url in your browser.
@@ -544,16 +544,22 @@ Below is a list of useful links :
Below is a list of primary dependencies : Below is a list of primary dependencies :
* [express/express.js](https://github.com/expressjs/express) * [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 * To serve, compute and render a GitHub user's metrics
* [nfriedly/express-rate-limit](https://github.com/nfriedly/express-rate-limit) * [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 * 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/) * [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 * To perform request to GitHub GraphQL API and GitHub REST API
* [ptarjan/node-cache](https://github.com/ptarjan/node-cache) * [ptarjan/node-cache](https://github.com/ptarjan/node-cache)
* To cache generated content * To cache generated content
* [renanbastos93/image-to-base64](https://github.com/renanbastos93/image-to-base64) * [renanbastos93/image-to-base64](https://github.com/renanbastos93/image-to-base64)
* To generate base64 representation of users' avatars * 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
All icons were ripped across GitHub's site, but still remains the intellectual property of GitHub. 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. See [GitHub Logos and Usage](https://github.com/logos) for more information.

10
action/dist/index.js vendored

File diff suppressed because one or more lines are too long

980
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
{ {
"name": "metrics", "name": "metrics",
"version": "1.5.0", "version": "1.6.0",
"description": "Generate an user's GitHub metrics as SVG image format to embed somewhere else", "description": "Generate an user's GitHub metrics as SVG image format to embed somewhere else",
"main": "index.mjs", "main": "index.mjs",
"scripts": { "scripts": {
"start": "node index.mjs", "start": "node index.mjs",
"build": "node utils/build.mjs", "build": "node utils/build.mjs",
"test": "echo \"Error: no test specified\" && exit 1", "test": "node tests/metrics.mjs",
"upgrade": "npm install @actions/core@latest @actions/github@latest @octokit/graphql@latest @octokit/rest@latest axios@latest express@latest express-rate-limit@latest image-to-base64@latest memory-cache@latest @vercel/ncc@latest" "upgrade": "npm install @actions/core@latest @actions/github@latest @octokit/graphql@latest @octokit/rest@latest axios@latest compression@latest express@latest express-rate-limit@latest image-to-base64@latest memory-cache@latest svgo@latest @vercel/ncc@latest libxmljs@latest"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -25,12 +25,15 @@
"@octokit/graphql": "^4.5.6", "@octokit/graphql": "^4.5.6",
"@octokit/rest": "^18.0.6", "@octokit/rest": "^18.0.6",
"axios": "^0.20.0", "axios": "^0.20.0",
"compression": "^1.7.4",
"express": "^4.17.1", "express": "^4.17.1",
"express-rate-limit": "^5.1.3", "express-rate-limit": "^5.1.3",
"image-to-base64": "^2.1.1", "image-to-base64": "^2.1.1",
"memory-cache": "^0.2.0" "memory-cache": "^0.2.0",
"svgo": "^1.3.2"
}, },
"devDependencies": { "devDependencies": {
"@vercel/ncc": "^0.24.1" "@vercel/ncc": "^0.24.1",
"libxmljs": "^0.19.7"
} }
} }

View File

@@ -7,6 +7,7 @@
import cache from "memory-cache" import cache from "memory-cache"
import ratelimit from "express-rate-limit" import ratelimit from "express-rate-limit"
import metrics from "./metrics.mjs" import metrics from "./metrics.mjs"
import compression from "compression"
//Load svg template, style and query //Load svg template, style and query
async function load() { async function load() {
@@ -28,6 +29,7 @@
//Setup server //Setup server
const app = express() const app = express()
app.use(compression())
const middlewares = [] const middlewares = []
//Rate limiter middleware //Rate limiter middleware
if (ratelimiter) { if (ratelimiter) {

View File

@@ -5,9 +5,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="A SVG image generator which includes activity, community and repositories metrics about your GitHub account that you can includes on your profile"> <meta name="description" content="A SVG image generator which includes activity, community and repositories metrics about your GitHub account that you can includes on your profile">
<meta name="author" content="lowlighter"> <meta name="author" content="lowlighter">
<link rel="icon" href="data:,">
</head> </head>
<body> <body>
<h1><a href="https://github.com/lowlighter/metrics">GitHub metrics</a></h1> <h1><a href="https://github.com/lowlighter/metrics">GitHub metrics</a></h1>
<label> <label>
@@ -24,9 +25,11 @@
<br> <br>
<div class="code"> <div class="code">
![<span class="md-alt">GitHub metrics</span>](https://metrics.lecoq.io/my-github-user) ![<span class="md-alt">GitHub metrics</span>](https://metrics.lecoq.io/my-github-user)
</div> </div>
<br>
And for even more metrics, setup this <a href="https://github.com/marketplace/actions/github-metrics-as-svg-image">GitHub action</a> on your repository !
</aside> </aside>
<script> <script>
window.onload = function () { window.onload = function () {
//User updater //User updater
@@ -40,11 +43,11 @@
document.querySelector("#metrics .generated").style.opacity = 0 document.querySelector("#metrics .generated").style.opacity = 0
document.querySelector("aside").style.opacity = 0 document.querySelector("aside").style.opacity = 0
//Update github user //Update github user
document.querySelector(".code").innerText = `![GitHub metrics](https://metrics.lecoq.io/${user})` document.querySelector(".code").innerHTML = `![<span class="md-alt">GitHub metrics</span>](https://metrics.lecoq.io/${user})`
document.querySelector("#user-repo").href = `https://github.com/${user}/${user}` document.querySelector("#user-repo").href = `https://github.com/${user}/${user}`
document.querySelectorAll(".user").forEach(node => node.innerText = user) document.querySelectorAll(".user").forEach(node => node.innerText = user)
//Update metrics //Update metrics
if (event.key === "Enter") if (event.key === "Enter")
metrics(user) metrics(user)
else else
timeout = setTimeout(() => metrics(user), 2000) timeout = setTimeout(() => metrics(user), 2000)

View File

@@ -1,5 +1,6 @@
//Imports //Imports
import imgb64 from "image-to-base64" import imgb64 from "image-to-base64"
import SVGO from "svgo"
import Plugins from "./plugins/index.mjs" import Plugins from "./plugins/index.mjs"
//Setup //Setup
@@ -78,9 +79,19 @@
//Wait for pending promises //Wait for pending promises
await Promise.all(pending) await Promise.all(pending)
//Eval rendering and return //Eval rendering
console.debug(`metrics/metrics/${login} > computed`) console.debug(`metrics/metrics/${login} > computed`)
return eval(`\`${template}\``) const templated = eval(`\`${template}\``)
console.debug(`metrics/metrics/${login} > templated`)
//Optimize rendering
const svgo = new SVGO({plugins:[{cleanupAttrs:true}, {inlineStyles:false}]})
const {data:optimized} = await svgo.optimize(templated)
console.debug(`metrics/metrics/${login} > optimized`)
//Result
const rendered = optimized
return rendered
} }
//Internal error //Internal error
catch (error) { throw (((Array.isArray(error.errors))&&(error.errors[0].type === "NOT_FOUND")) ? new Error("user not found") : error) } catch (error) { throw (((Array.isArray(error.errors))&&(error.errors[0].type === "NOT_FOUND")) ? new Error("user not found") : error) }

View File

@@ -30,7 +30,7 @@
computed.plugins.traffic = {views} computed.plugins.traffic = {views}
console.debug(`metrics/plugins/traffic/${login} > ${JSON.stringify(computed.plugins.traffic)}`) console.debug(`metrics/plugins/traffic/${login} > ${JSON.stringify(computed.plugins.traffic)}`)
solve() solve()
} }
catch (error) { catch (error) {
//Thrown when token has unsufficient permissions //Thrown when token has unsufficient permissions
if (error.status === 403) { if (error.status === 403) {

View File

@@ -255,7 +255,7 @@
</div> </div>
</section> </section>
</div> </div>
` : ` ` : `
<section> <section>
<div class="row fill-width"> <div class="row fill-width">
<section class="categories"> <section class="categories">

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

26
tests/metrics.mjs Normal file
View File

@@ -0,0 +1,26 @@
//Imports
import path from "path"
import fs from "fs"
import metrics from "../src/metrics.mjs"
import octokit from "@octokit/graphql"
import OctokitRest from "@octokit/rest"
import libxmljs from "libxmljs"
//Die on unhandled rejections
process.on("unhandledRejection", error => { throw error })
//Load GitHub handlers
const token = process.argv.slice(2)[0] ?? ""
const graphql = octokit.graphql.defaults({headers:{authorization: `token ${token}`}})
const rest = new OctokitRest.Octokit({auth:token})
//Load svg template, style and query
const [template, style, query] = await Promise.all(["template.svg", "style.css", "query.graphql"].map(async file => `${await fs.promises.readFile(path.join("src", file))}`))
//Compute render
const rendered = await metrics({login:"lowlighter", q:{}}, {template, style, query, graphql, rest, plugins:{}})
//Ensure it's a well-formed SVG image
const parsed = libxmljs.parseXml(rendered)
if (parsed.errors.length)
throw new Error(`Malformed SVG : \n${parsed.errors.join("\n")}`)