The great refactor (#82)
This commit is contained in:
87
source/plugins/core/README.md
Normal file
87
source/plugins/core/README.md
Normal file
@@ -0,0 +1,87 @@
|
||||
### 🧱 Core
|
||||
|
||||
Metrics also have general options that impact global metrics rendering.
|
||||
|
||||
[➡️ Available options](metadata.yml)
|
||||
|
||||
### 🌐 Set timezone
|
||||
|
||||
By default, dates are based on Greenwich meridian (GMT/UTC).
|
||||
|
||||
Set your timezone (see [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for a list of supported timezones) using `config_timezone` option.
|
||||
|
||||
#### ℹ️ Examples workflows
|
||||
|
||||
```yaml
|
||||
- uses: lowlighter/metrics@latest
|
||||
with:
|
||||
# ... other options
|
||||
config_timezone: Europe/Paris
|
||||
```
|
||||
|
||||
### 📦 Ordering content
|
||||
|
||||
You can order metrics content by using `config_order` option.
|
||||
|
||||
It is not mandatory to specify all partials of used templates.
|
||||
Omitted one will be appended using default order.
|
||||
|
||||
#### ℹ️ Examples workflows
|
||||
|
||||
```yaml
|
||||
- uses: lowlighter/metrics@latest
|
||||
with:
|
||||
# ... other options
|
||||
base: header
|
||||
plugin_isocalendar: yes
|
||||
plugin_languages: yes
|
||||
plugin_stars: yes
|
||||
config_order: base.header, isocalendar, languages, stars
|
||||
```
|
||||
|
||||
### 🎞️ SVG CSS Animations
|
||||
|
||||
As rendered metrics use HTML and CSS, some templates have animations.
|
||||
You can choose to disable them by using `config_animations` option.
|
||||
|
||||
#### ℹ️ Examples workflows
|
||||
|
||||
```yaml
|
||||
- uses: lowlighter/metrics@latest
|
||||
with:
|
||||
# ... other options
|
||||
committer_branch: my-branch
|
||||
```
|
||||
|
||||
### 🔲 Adjust padding
|
||||
|
||||
Height of rendered metrics is computed after being rendered through an headless browser.
|
||||
As it can depend on fonts and operating system, it is possible that final result is cropped or has blank space at the bottom.
|
||||
|
||||
You can adjust padding by using `config_padding` option.
|
||||
|
||||
Specify a single value to apply it to both height and with, and two values to use the first one for width and the second for height. Both positive and negative values are accepted, but you must specify a percentage.
|
||||
|
||||
#### ℹ️ Examples workflows
|
||||
|
||||
```yaml
|
||||
- uses: lowlighter/metrics@latest
|
||||
with:
|
||||
# ... other options
|
||||
config_padding: 6%, 10% # 6% width padding, 10% height padding
|
||||
```
|
||||
|
||||
### 💱 Convert output to PNG/JPEG
|
||||
|
||||
It is possible to convert output from SVG to PNG or JPEG images by using `config_output` option.
|
||||
|
||||
Note that `png` does not support animations while `jpeg` does not support both animations and transparency.
|
||||
|
||||
#### ℹ️ Examples workflows
|
||||
|
||||
```yaml
|
||||
- uses: lowlighter/metrics@latest
|
||||
with:
|
||||
# ... other options
|
||||
config_output: png
|
||||
```
|
||||
130
source/plugins/core/index.mjs
Normal file
130
source/plugins/core/index.mjs
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Core plugin is a special plugin because of historical reasons.
|
||||
* It is used by templates to setup global configuration.
|
||||
*/
|
||||
|
||||
//Setup
|
||||
export default async function ({login, q, dflags}, {conf, data, rest, graphql, plugins, queries, account}, {pending, imports}) {
|
||||
//Load inputs
|
||||
imports.metadata.plugins.core.inputs({data, account, q})
|
||||
|
||||
//Init
|
||||
const computed = data.computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0, forked:0, releases:0}}
|
||||
const avatar = imports.imgb64(data.user.avatarUrl)
|
||||
console.debug(`metrics/compute/${login} > formatting common metrics`)
|
||||
|
||||
//Timezone config
|
||||
if (q["config.timezone"]) {
|
||||
const timezone = data.config.timezone = {name:q["config.timezone"], offset:0}
|
||||
try {
|
||||
timezone.offset = Number(new Date().toLocaleString("fr", {timeZoneName:"short", timeZone:timezone.name}).match(/UTC[+](?<offset>\d+)/)?.groups?.offset*60*60*1000) || 0
|
||||
console.debug(`metrics/compute/${login} > timezone set to ${timezone.name} (${timezone.offset > 0 ? "+" : ""}${Math.round(timezone.offset/(60*60*1000))} hours)`)
|
||||
} catch {
|
||||
timezone.error = `Failed to use timezone "${timezone.name}"`
|
||||
console.debug(`metrics/compute/${login} > failed to use timezone "${timezone.name}"`)
|
||||
}
|
||||
}
|
||||
|
||||
//Animations
|
||||
if ("config.animations" in q) {
|
||||
data.animated = q["config.animations"]
|
||||
console.debug(`metrics/compute/${login} > animations ${data.animated ? "enabled" : "disabled"}`)
|
||||
}
|
||||
|
||||
//Plugins
|
||||
for (const name of Object.keys(imports.plugins)) {
|
||||
if (!plugins[name]?.enabled)
|
||||
continue
|
||||
pending.push((async () => {
|
||||
try {
|
||||
console.debug(`metrics/compute/${login}/plugins > ${name} > started`)
|
||||
data.plugins[name] = await imports.plugins[name]({login, q, imports, data, computed, rest, graphql, queries, account}, plugins[name])
|
||||
console.debug(`metrics/compute/${login}/plugins > ${name} > completed`)
|
||||
}
|
||||
catch (error) {
|
||||
console.debug(`metrics/compute/${login}/plugins > ${name} > completed (error)`)
|
||||
data.plugins[name] = error
|
||||
}
|
||||
finally {
|
||||
const result = {name, result:data.plugins[name]}
|
||||
console.debug(imports.util.inspect(result, {depth:Infinity, maxStringLength:256}))
|
||||
return result
|
||||
}
|
||||
})())
|
||||
}
|
||||
|
||||
//Iterate through user's repositories
|
||||
for (const repository of data.user.repositories.nodes) {
|
||||
//Simple properties with totalCount
|
||||
for (const property of ["watchers", "stargazers", "issues_open", "issues_closed", "pr_open", "pr_merged", "releases"])
|
||||
computed.repositories[property] += repository[property].totalCount
|
||||
//Forks
|
||||
computed.repositories.forks += repository.forkCount
|
||||
if (repository.isFork)
|
||||
computed.repositories.forked++
|
||||
//License
|
||||
if (repository.licenseInfo)
|
||||
computed.licenses.used[repository.licenseInfo.spdxId] = (computed.licenses.used[repository.licenseInfo.spdxId] ?? 0) + 1
|
||||
}
|
||||
|
||||
//Total disk usage
|
||||
computed.diskUsage = `${imports.bytes(data.user.repositories.totalDiskUsage*1000)}`
|
||||
|
||||
//Compute licenses stats
|
||||
computed.licenses.favorite = Object.entries(computed.licenses.used).sort(([an, a], [bn, b]) => b - a).slice(0, 1).map(([name, value]) => name) ?? ""
|
||||
|
||||
//Compute total commits
|
||||
computed.commits += data.user.contributionsCollection.totalCommitContributions + data.user.contributionsCollection.restrictedContributionsCount
|
||||
|
||||
//Compute registration date
|
||||
const diff = (Date.now()-(new Date(data.user.createdAt)).getTime())/(365*24*60*60*1000)
|
||||
const years = Math.floor(diff)
|
||||
const months = Math.floor((diff-years)*12)
|
||||
computed.registration = years ? `${years} year${imports.s(years)} ago` : months ? `${months} month${imports.s(months)} ago` : `${Math.ceil(diff*365)} day${imports.s(Math.ceil(diff*365))} ago`
|
||||
computed.cakeday = years > 1 ? [new Date(), new Date(data.user.createdAt)].map(date => date.toISOString().match(/(?<mmdd>\d{2}-\d{2})(?=T)/)?.groups?.mmdd).every((v, _, a) => v === a[0]) : false
|
||||
|
||||
//Compute calendar
|
||||
computed.calendar = data.user.calendar.contributionCalendar.weeks.flatMap(({contributionDays}) => contributionDays).slice(0, 14).reverse()
|
||||
|
||||
//Avatar (base64)
|
||||
computed.avatar = await avatar || "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
|
||||
|
||||
//Token scopes
|
||||
computed.token.scopes = (await rest.request("HEAD /")).headers["x-oauth-scopes"].split(", ")
|
||||
|
||||
//Meta
|
||||
data.meta = {version:conf.package.version, author:conf.package.author}
|
||||
|
||||
//Debug flags
|
||||
if ((dflags.includes("--cakeday"))||(q["dflag.cakeday"])) {
|
||||
console.debug(`metrics/compute/${login} > applying dflag --cakeday`)
|
||||
computed.cakeday = true
|
||||
}
|
||||
if ((dflags.includes("--hireable"))||(q["dflag.hireable"])) {
|
||||
console.debug(`metrics/compute/${login} > applying dflag --hireable`)
|
||||
data.user.isHireable = true
|
||||
}
|
||||
if ((dflags.includes("--halloween"))||(q["dflag.halloween"])) {
|
||||
console.debug(`metrics/compute/${login} > applying dflag --halloween`)
|
||||
//Haloween color replacer
|
||||
const halloween = content => content
|
||||
.replace(/--color-calendar-graph/g, "--color-calendar-halloween-graph")
|
||||
.replace(/#9be9a8/gi, "var(--color-calendar-halloween-graph-day-L1-bg)")
|
||||
.replace(/#40c463/gi, "var(--color-calendar-halloween-graph-day-L2-bg)")
|
||||
.replace(/#30a14e/gi, "var(--color-calendar-halloween-graph-day-L3-bg)")
|
||||
.replace(/#216e39/gi, "var(--color-calendar-halloween-graph-day-L4-bg)")
|
||||
//Update contribution calendar colors
|
||||
computed.calendar.map(day => day.color = halloween(day.color))
|
||||
//Update isocalendar colors
|
||||
const waiting = [...pending]
|
||||
pending.push((async () => {
|
||||
await Promise.all(waiting)
|
||||
if (data.plugins.isocalendar?.svg)
|
||||
data.plugins.isocalendar.svg = halloween(data.plugins.isocalendar.svg)
|
||||
return {name:"dflag.halloween", result:true}
|
||||
})())
|
||||
}
|
||||
|
||||
//Results
|
||||
return null
|
||||
}
|
||||
164
source/plugins/core/metadata.yml
Normal file
164
source/plugins/core/metadata.yml
Normal file
@@ -0,0 +1,164 @@
|
||||
name: "🧱 Core"
|
||||
cost: N/A
|
||||
supports:
|
||||
- user
|
||||
- organization
|
||||
- repository
|
||||
inputs:
|
||||
|
||||
# User account personal token
|
||||
# No additional scopes are needed unless you want to include private repositories metrics
|
||||
# Some plugins may also require additional scopes
|
||||
token:
|
||||
description: GitHub Personal Token
|
||||
type: token
|
||||
required: true
|
||||
|
||||
# GitHub username
|
||||
user:
|
||||
description: GitHub username
|
||||
type: string
|
||||
default: "" # Defaults to "token" owner
|
||||
|
||||
# Set to "${{ secrets.GITHUB_TOKEN }}"
|
||||
committer_token:
|
||||
description: GitHub Token used to commit metrics
|
||||
type: token
|
||||
default: "" # Defaults to "token"
|
||||
|
||||
# Branch used to commit rendered metrics
|
||||
committer_branch:
|
||||
description: Branch used to commit rendered metrics
|
||||
type: string
|
||||
default: "" # Defaults to your repository default branch
|
||||
|
||||
# Rendered metrics output path, relative to repository's root
|
||||
filename:
|
||||
description: Rendered metrics output path
|
||||
type: string
|
||||
default: github-metrics.svg
|
||||
|
||||
# Optimize SVG image to reduce its filesize
|
||||
# Some templates may not support this option
|
||||
optimize:
|
||||
description: SVG optimization
|
||||
type: boolean
|
||||
default: yes
|
||||
|
||||
# Setup additional templates from remote repositories
|
||||
setup_community_templates:
|
||||
description: Additional community templates to setup
|
||||
type: array
|
||||
format:
|
||||
- comma-separated
|
||||
- /(?<user>[-a-z0-9]+)[/](?<repo>[-a-z0-9]+)@(?<branch>[-a-z0-9]+):(?<template>[-a-z0-9]+)/
|
||||
default: ""
|
||||
|
||||
# Template to use
|
||||
# To use community template, prefix its name with "@"
|
||||
template:
|
||||
description: Template to use
|
||||
type: string
|
||||
default: classic
|
||||
|
||||
# Additional query parameters (JSON string)
|
||||
# Some templates may require additional parameters which you can specify here
|
||||
# Do not use this option to pass plugins parameters as they'll be overwritten by the other options
|
||||
query:
|
||||
description: Additional query parameters
|
||||
type: json
|
||||
default: "{}"
|
||||
|
||||
# Timezone used by metrics
|
||||
# See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
config_timezone:
|
||||
description: Timezone used
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
# Specify in which order metrics content will be displayed
|
||||
# If you omit some partials, they'll be appended at the end in default order
|
||||
# See "partials/_.json" of each template for a list of supported partials
|
||||
config_order:
|
||||
description: Configure content order
|
||||
type: array
|
||||
format: comma-separated
|
||||
default: ""
|
||||
|
||||
# Enable SVG CSS animations
|
||||
config_animations:
|
||||
description: SVG CSS animations
|
||||
type: boolean
|
||||
default: yes
|
||||
|
||||
# Configure padding for output image (percentage value)
|
||||
# It can be used to add padding to generated metrics if rendering is cropped or has too much empty space
|
||||
# Specify one value (for both width and height) or two values (one for width and one for height)
|
||||
config_padding:
|
||||
description: Image padding
|
||||
type: array
|
||||
format: comma-separated
|
||||
default: 6%
|
||||
|
||||
# Metrics output format
|
||||
config_output:
|
||||
description: Output image format
|
||||
type: string
|
||||
default: svg
|
||||
values:
|
||||
- svg
|
||||
- png # Does not support animations
|
||||
- jpeg # Does not support animations and transparency
|
||||
|
||||
# ====================================================================================
|
||||
# Options below are mostly used for testing
|
||||
|
||||
# Throw on plugins errors
|
||||
# If disabled, metrics will handle errors gracefully with a message in rendered metrics
|
||||
plugins_errors_fatal:
|
||||
description: Die on plugins errors
|
||||
type: boolean
|
||||
default: no
|
||||
|
||||
# Debug mode
|
||||
# Note that this will automatically be enabled if job fails
|
||||
debug:
|
||||
description: Debug logs
|
||||
type: boolean
|
||||
default: no
|
||||
|
||||
# Ensure SVG can be correctly parsed after generation
|
||||
verify:
|
||||
description: Verify SVG
|
||||
type: boolean
|
||||
default: no
|
||||
|
||||
# Debug flags
|
||||
debug_flags:
|
||||
description: Debug flags
|
||||
type: array
|
||||
format: space-separated
|
||||
default: ""
|
||||
values:
|
||||
- --cakeday
|
||||
- --hireable
|
||||
- --halloween
|
||||
|
||||
# Dry-run mode (perform generation without pushing it)
|
||||
dryrun:
|
||||
description: Enable dry-run
|
||||
type: boolean
|
||||
default: no
|
||||
|
||||
# Use mocked data to bypass external APIs
|
||||
use_mocked_data:
|
||||
description: Use mocked data instead of live APIs
|
||||
type: boolean
|
||||
default: no
|
||||
|
||||
# Use a pre-built image from GitHub registry (experimental)
|
||||
# See https://github.com/users/lowlighter/packages/container/package/metrics for more information
|
||||
use_prebuilt_image:
|
||||
description: Use pre-built image from GitHub registry
|
||||
type: string
|
||||
default: ""
|
||||
Reference in New Issue
Block a user