Version 1.9 (#6)

This commit is contained in:
Simon Lecoq
2020-10-20 00:19:58 +02:00
committed by GitHub
parent 047d8630e6
commit f21273172d
23 changed files with 454 additions and 860 deletions

View File

@@ -1,96 +1,50 @@
//Imports
import imgb64 from "image-to-base64"
import ejs from "ejs"
import SVGO from "svgo"
import imgb64 from "image-to-base64"
import Plugins from "./plugins/index.mjs"
import Templates from "./templates/index.mjs"
//Setup
export default async function metrics({login, q}, {template, style, query, graphql, rest, plugins, selfless = true, optimize = true}) {
export default async function metrics({login, q}, {graphql, rest, plugins, conf}) {
//Compute rendering
try {
//Init
const template = q.template || conf.settings.templates.default
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))))
throw new Error("unsupported template")
const {query, image, style} = conf.templates[template]
//Query data from GitHub API
console.debug(`metrics/metrics/${login} > query`)
console.debug(`metrics/compute/${login} > query`)
const data = await graphql(query
.replace(/[$]login/, `"${login}"`)
.replace(/[$]calendar.to/, `"${(new Date()).toISOString()}"`)
.replace(/[$]calendar.from/, `"${(new Date(Date.now()-14*24*60*60*1000)).toISOString()}"`)
)
console.debug(`metrics/compute/${login} > query > success`)
//Init
const languages = {colors:{}, total:0, stats:{}}
const licenses = {favorite:"", used:{}}
const computed = data.computed = {commits:0, languages, licenses, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0}, plugins:{}}
const avatar = imgb64(data.user.avatarUrl)
const pending = []
//Plugins
if (data.user.websiteUrl)
Plugins.pagespeed({login, url:data.user.websiteUrl, computed, pending, q}, plugins.pagespeed)
Plugins.lines({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.lines)
Plugins.traffic({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.traffic)
Plugins.habits({login, rest, computed, pending, q}, plugins.habits)
Plugins.selfskip({login, rest, computed, pending, q}, plugins.selfskip)
//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"])
computed.repositories[property] += repository[property].totalCount
//Forks
computed.repositories.forks += repository.forkCount
//Languages
for (const {size, node:{color, name}} of Object.values(repository.languages.edges)) {
languages.stats[name] = (languages.stats[name] || 0) + size
languages.colors[name] = color || "#ededed"
languages.total += size
}
//License
if (repository.licenseInfo)
licenses.used[repository.licenseInfo.spdxId] = (licenses.used[repository.licenseInfo.spdxId] || 0) + 1
}
//Compute count for issues and pull requests
for (const property of ["issues", "pr"])
computed.repositories[`${property}_count`] = computed.repositories[`${property}_open`] + computed.repositories[`${property}_${property === "pr" ? "merged" : "closed"}`]
//Compute total commits and sponsorships
computed.commits = data.user.contributionsCollection.totalCommitContributions + data.user.contributionsCollection.restrictedContributionsCount
computed.sponsorships = data.user.sponsorshipsAsSponsor.totalCount + data.user.sponsorshipsAsMaintainer.totalCount
//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.ceil((diff-years)*12)
computed.registration = years ? `${years} year${years > 1 ? "s" : ""} ago` : `${months} month${months > 1 ? "s" : ""} ago`
//Compute languages stats
Object.keys(languages.stats).map(name => languages.stats[name] /= languages.total)
languages.favorites = Object.entries(languages.stats).sort(([an, a], [bn, b]) => b - a).slice(0, 8).map(([name, value]) => ({name, value, color:languages.colors[name], x:0}))
for (let i = 1; i < languages.favorites.length; i++)
languages.favorites[i].x = languages.favorites[i-1].x + languages.favorites[i-1].value
//Compute licenses stats
licenses.favorite = Object.entries(licenses.used).sort(([an, a], [bn, b]) => b - a).slice(0, 1).map(([name, value]) => name) || ""
//Compute calendar
computed.calendar = data.user.calendar.contributionCalendar.weeks.flatMap(({contributionDays}) => contributionDays).slice(0, 14).reverse()
//Avatar (base64)
computed.avatar = await avatar || "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
//Wait for pending promises
//Template
console.debug(`metrics/compute/${login} > compute`)
const computer = Templates[template].default || Templates[template]
await computer({login, q}, {data, rest, graphql, plugins}, {s, pending, imports:{plugins:Plugins, imgb64}})
await Promise.all(pending)
console.debug(`metrics/compute/${login} > compute > success`)
//Eval rendering
console.debug(`metrics/metrics/${login} > computed`)
let rendered = eval(`\`${template}\``)
console.debug(`metrics/metrics/${login} > templated`)
console.debug(`metrics/compute/${login} > render`)
let rendered = await ejs.render(image, {...data, s, style}, {async:true})
console.debug(`metrics/compute/${login} > render > success`)
//Optimize rendering
if (optimize) {
if (conf.optimize) {
console.debug(`metrics/compute/${login} > optimize`)
const svgo = new SVGO({full:true, plugins:[{cleanupAttrs:true}, {inlineStyles:false}]})
const {data:optimized} = await svgo.optimize(rendered)
console.debug(`metrics/metrics/${login} > optimized`)
console.debug(`metrics/compute/${login} > optimize > success`)
rendered = optimized
}
@@ -98,5 +52,11 @@
return rendered
}
//Internal error
catch (error) { throw (((Array.isArray(error.errors))&&(error.errors[0].type === "NOT_FOUND")) ? new Error("user not found") : error) }
catch (error) {
//User not found
if (((Array.isArray(error.errors))&&(error.errors[0].type === "NOT_FOUND")))
throw new Error("user not found")
//Generic error
throw error
}
}