Version 2.2

- Add new template "terminal"
- Add feature to flush cache of user on server
- Server app improvement
- Created metrics common
- Package json loaded in setup
This commit is contained in:
lowlighter
2020-10-24 00:32:53 +02:00
parent 26bc499ef7
commit ca0a6d559e
18 changed files with 556 additions and 63 deletions

View File

@@ -1,5 +1,6 @@
query Metrics {
user(login: $login) {
databaseId
name
login
createdAt

View File

@@ -45,7 +45,6 @@
}
.field.error svg {
fill: #cb2431;
}
/* Displays */

View File

@@ -1,52 +1,7 @@
/** Template processort */
export default async function ({login, q}, {data, rest, graphql, plugins}, {s, pending, imports}) {
//Init
const computed = data.computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, svg:{height:355, width:480}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0}, plugins:{}}
const avatar = imports.imgb64(data.user.avatarUrl)
//Plugins
if (data.user.websiteUrl)
imports.plugins.pagespeed({login, url:data.user.websiteUrl, computed, pending, q}, plugins.pagespeed)
imports.plugins.lines({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.lines)
imports.plugins.traffic({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.traffic)
imports.plugins.habits({login, rest, computed, pending, q}, plugins.habits)
imports.plugins.selfskip({login, rest, computed, pending, q}, plugins.selfskip)
imports.plugins.languages({login, data, computed, pending, q}, plugins.languages)
imports.plugins.followup({login, data, computed, pending, q}, plugins.followup)
//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
//License
if (repository.licenseInfo)
computed.licenses.used[repository.licenseInfo.spdxId] = (computed.licenses.used[repository.licenseInfo.spdxId] || 0) + 1
}
//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 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${s(years)} ago` : `${months} month${s(months)} ago`
//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(", ")
//Imports
import common from "./../common.mjs"
/** Template processor */
export default async function ({login, q}, {conf, data, rest, graphql, plugins}, {s, pending, imports}) {
await common(...arguments)
}

52
src/templates/common.mjs Normal file
View File

@@ -0,0 +1,52 @@
/** Template common processor */
export default async function ({login, q}, {conf, data, rest, graphql, plugins}, {s, pending, imports}) {
//Init
const computed = data.computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, svg:{height:355, width:480}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0}, plugins:{}}
const avatar = imports.imgb64(data.user.avatarUrl)
//Plugins
if (data.user.websiteUrl)
imports.plugins.pagespeed({login, url:data.user.websiteUrl, computed, pending, q}, plugins.pagespeed)
imports.plugins.lines({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.lines)
imports.plugins.traffic({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.traffic)
imports.plugins.habits({login, rest, computed, pending, q}, plugins.habits)
imports.plugins.selfskip({login, rest, computed, pending, q}, plugins.selfskip)
imports.plugins.languages({login, data, computed, pending, q}, plugins.languages)
imports.plugins.followup({login, data, computed, pending, q}, plugins.followup)
//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
//License
if (repository.licenseInfo)
computed.licenses.used[repository.licenseInfo.spdxId] = (computed.licenses.used[repository.licenseInfo.spdxId] || 0) + 1
}
//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 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${s(years)} ago` : `${months} month${s(months)} ago`
//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(", ")
}

View File

@@ -1,7 +1,9 @@
//Imports
import classic from "./classic/template.mjs"
import terminal from "./terminal/template.mjs"
//Exports
export default {
classic
classic,
terminal,
}

View File

@@ -0,0 +1,124 @@
<svg xmlns="http://www.w3.org/2000/svg" width="<%= computed.svg.width %>" height="<%= computed.svg.height %>">
<style>
<%= style %>
.stdin, .stdout {
animation-duration: .2s;
}
.stdout {
animation-duration: .2s;
}
<% for (let i = 0; i < 12; i++) { %>
.stdin:nth-of-type(<%= i+1 %>) {
animation-delay: <%= i*.2 %>s;
}
.stdout:nth-of-type(<%= i+2 %>) {
animation-delay: <%= (i+1)*.2 %>s;
}
<% } %>
footer {
animation-delay: <%= 12*.2 %>s;
}
</style>
<foreignObject x="0" y="0" width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xlink="http://www.w3.org/1999/xlink">
<div class="header">
<span class="title">GitHub metrics</span>
<div class="buttons">
<div class="button"></div>
<div class="button"></div>
<div class="button exit"></div>
</div>
</div>
<pre><span class="banner">GitHub metrics generator <%= meta.version %>
These generated metrics comes with ABSOLUTELY NO WARRANTY,
to the extent permitted by applicable law.
Last generated: <%= new Date().toGMTString() %>
</span>
<div class="stdin"><%- meta.$ %> whoami</div><!--
--><div class="stdout"><!--
--><b><%= user.name || user.login %></b> registered=<%= computed.registration %>, uid=<%= `${user.databaseId}`.substr(-4) %>, gid=<%= user.organizations.totalCount %>, followers=<%= user.followers.totalCount %>
contributed to <%= user.repositoriesContributedTo.totalCount %> repositor<%= s(user.repositoriesContributedTo.totalCount, "y") %> <b><% for (const [x, {color}] of Object.entries(computed.calendar)) { -%><span style="color:<%= color %>">#</span><% } %></b>
</div>
<div class="stdin"><%- meta.$ %> git status</div><!--
--><div class="stdout"><b>Recent activity</b><!--
--><b><%= `${computed.commits}`.padStart(5) %></b> commit<%= s(computed.commits) %>
<b><%= `${user.contributionsCollection.totalPullRequestReviewContributions}`.padStart(5) %></b> pull request<%= s(user.contributionsCollection.totalPullRequestReviewContributions) %> reviewed
<b><%= `${user.contributionsCollection.totalPullRequestContributions}`.padStart(5) %></b> pull request<%= s(user.contributionsCollection.totalPullRequestContributions) %> opened
<b><%= `${user.contributionsCollection.totalIssueContributions}`.padStart(5) %></b> issue<%= s(user.contributionsCollection.totalIssueContributions) %> opened
<b><%= `${user.issueComments.totalCount}`.padStart(5) %></b> issue comment<%= s(user.issueComments.totalCount) %>
<b>Tracked activity</b>
<b><%= `${user.following.totalCount}`.padStart(5) %></b> user<%= s(user.followers.totalCount) %> followed
<b><%= `${computed.sponsorships}`.padStart(5) %></b> repositor<%= s(computed.sponsorships, "y") %> sponsored
<b><%= `${user.starredRepositories.totalCount}`.padStart(5) %></b> repositor<%= s(user.starredRepositories.totalCount, "y") %> starred
<b><%= `${user.watching.totalCount}`.padStart(5) %></b> repositor<%= s(user.watching.totalCount, "y") %> watched
<% if (computed.plugins.lines) { -%><% if (computed.plugins.lines.error) { -%>
<span class="diff error">@@ <%= computed.plugins.lines.error %> @@</span>
<% } else { -%>
<span class="diff">@@ -<%= computed.plugins.lines.deleted %> +<%= computed.plugins.lines.added %> @@</span>
<% }} -%></div>
<div class="stdin"><%- meta.$ %> ls -lh github/repositories</div><!--
--><div class="stdout"><!--
-->Total <%= user.repositories.totalCount %> repositor<%= s(user.repositories.totalCount, "y") %>
<% if (computed.plugins.traffic) { -%><% if (computed.plugins.traffic.error) { -%>
---- <b> </b> views <span class="error">(<%= computed.plugins.traffic.error %>)</span>
<% } else { -%>
-r-- <b><%= `${computed.plugins.traffic.views.count}`.padStart(5) %></b> views
<% }} -%>
-r-- <b><%= `${computed.repositories.stargazers}`.padStart(5) %></b> stargazer<%= s(computed.repositories.stargazers) %>
-r-- <b><%= `${computed.repositories.forks}`.padStart(5) %></b> fork<%= s(computed.repositories.forks) %>
-r-- <b><%= `${computed.repositories.watchers}`.padStart(5) %></b> watcher<%= s(computed.repositories.watchers) %>
dr-x <b><%= `${user.packages.totalCount}`.padStart(5) %></b> package<%= s(user.packages.totalCount) %>
dr-x <b><%= `${user.gists.totalCount}`.padStart(5) %></b> gist<%= s(user.gists.totalCount) %>
<% if (computed.plugins.followup) { -%><% if (computed.plugins.followup.error) { -%>
d--- <b> </b> ISSUES <span class="error">(<%= computed.plugins.followup.error %>)</span>
d--- <b> </b> PULL_REQUESTS <span class="error">(<%= computed.plugins.followup.error %>)</span>
<% } else { -%>
dr-x <b><%= `${computed.plugins.followup.issues.count}`.padStart(5) %></b> ISSUES
-r-- <b><%= `${computed.plugins.followup.issues.open}`.padStart(5) %></b> ├── open
-r-- <b><%= `${computed.plugins.followup.issues.closed}`.padStart(5) %></b> └── closed
dr-x <b><%= `${computed.plugins.followup.issues.count}`.padStart(5) %></b> PULL_REQUESTS
-r-- <b><%= `${computed.plugins.followup.pr.open}`.padStart(5) %></b> ├── open
-r-- <b><%= `${computed.plugins.followup.pr.merged}`.padStart(5) %></b> └── merged
<% }} -%>
<% if (computed.licenses.favorite.length) { -%>
dr-x LICENSE
-r-- └── <%= computed.licenses.favorite %>
<% } -%>
</div><% if (computed.plugins.languages) { -%><% if (computed.plugins.languages.error) { -%>
<div class="stdin"><%- meta.$ %> locale</div><!--
--><div class="stdout"><!--
--><span class="error"><%= computed.plugins.languages.error %></span>
<% } else { -%>
<div class="stdin"><%- meta.$ %> locale</div><!--
--><div class="stdout"><!--
--><% for (const {name, value} of computed.plugins.languages.favorites) { -%>
<b><%= name.toLocaleUpperCase().padEnd(12) %></b> [<%= "#".repeat(Math.ceil(100*value/5)).padEnd(20) %>] <%= (100*value).toFixed(2).padEnd(5) %>%
<% }} -%></div><% } -%><% if (computed.plugins.pagespeed) { -%><% if (computed.plugins.pagespeed.error) { -%>
<div class="stdin"><%- meta.$ %> curl -I <%= user.websiteUrl %></div><!--
--><div class="stdout"><!--
--><span class="error"><%= computed.plugins.pagespeed.error %></span>
<% } else { -%>
<div class="stdin"><%- meta.$ %> curl -I <%= user.websiteUrl %></div><!--
--><div class="stdout"><!--
--><b>User-Agent</b>: Google PageSpeed API
<b>Location</b>: <%= user.websiteUrl %>
<% for (const {score, title} of computed.plugins.pagespeed.scores) { -%>
<b><%= `X-${title.replace(/ /g, "-")}` %></b>: <%= Math.round(score*100) %>%
<% }} -%></div><% } -%>
<footer>Connection reset by 127.0.0.1</footer></pre>
</div>
</foreignObject>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -0,0 +1,88 @@
<svg xmlns="http://www.w3.org/2000/svg" width="480" height="<%= 640 + 80 + (!!plugins.followup)*(100+32) + (!!plugins.languages)*(170+32) + (!!plugins.lines)*(30+32) + (!!plugins.traffic)*(16+32) + (!!plugins.pagespeed)*(136+32) %>">
<style>
<%= style %>
</style>
<foreignObject x="0" y="0" width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xlink="http://www.w3.org/1999/xlink">
<div class="header">
<span class="title">GitHub metrics</span>
<div class="buttons">
<div class="button"></div>
<div class="button"></div>
<div class="button exit"></div>
</div>
</div>
<pre><span class="banner">GitHub metrics generator X.Y.Z
These generated metrics comes with ABSOLUTELY NO WARRANTY,
to the extent permitted by applicable law.
Last generated: <%= new Date().toGMTString() %>
</span>
<div class="stdin"><span class="ps1-path">▇▇▇▇@metrics</span>:<span class="ps1-location">~</span># whoami</div><!--
--><div class="stdout"><!--
--><b>▇▇▇▇</b> registered=▇▇, uid=▇▇, gid=▇▇, followers=▇▇
contributed to ▇▇ repositories <b style="color:#ebedf0">##############</b>
</div>
<div class="stdin"><span class="ps1-path">▇▇▇▇@metrics</span>:<span class="ps1-location">~</span># git status</div><!--
--><div class="stdout"><b>Recent activity</b><!--
--><b><%= `▇▇`.padStart(5) %></b> commits
<b><%= `▇▇`.padStart(5) %></b> pull requests reviewed
<b><%= `▇▇`.padStart(5) %></b> pull requests opened
<b><%= `▇▇`.padStart(5) %></b> issues opened
<b><%= `▇▇`.padStart(5) %></b> issue comments
<b>Tracked activity</b>
<b><%= `▇▇`.padStart(5) %></b> users followed
<b><%= `▇▇`.padStart(5) %></b> repositories sponsored
<b><%= `▇▇`.padStart(5) %></b> repositories starred
<b><%= `▇▇`.padStart(5) %></b> repositories watched
<% if (plugins.lines) { -%>
<span class="diff">@@ -▇▇ +▇▇ @@</span>
<% } -%></div>
<div class="stdin"><span class="ps1-path">▇▇▇▇@metrics</span>:<span class="ps1-location">~</span># ls -lh github/repositories</div><!--
--><div class="stdout"><!--
-->Total ▇▇ repositories
<% if (plugins.traffic) { -%>
-r-- <b><%= `▇▇`.padStart(5) %></b> views
<% } -%>
-r-- <b><%= `▇▇`.padStart(5) %></b> stargazers
-r-- <b><%= `▇▇`.padStart(5) %></b> forks
-r-- <b><%= `▇▇`.padStart(5) %></b> watchers
dr-x <b><%= `▇▇`.padStart(5) %></b> packages
dr-x <b><%= `▇▇`.padStart(5) %></b> gists
<% if (plugins.followup) { -%>
dr-x <b><%= `▇▇`.padStart(5) %></b> ISSUES
-r-- <b><%= `▇▇`.padStart(5) %></b> ├── open
-r-- <b><%= `▇▇`.padStart(5) %></b> └── closed
dr-x <b><%= `▇▇`.padStart(5) %></b> PULL_REQUESTS
-r-- <b><%= `▇▇`.padStart(5) %></b> ├── open
-r-- <b><%= `▇▇`.padStart(5) %></b> └── merged
<% } -%>
dr-x LICENSE
-r-- └── ▇▇▇▇
</div><% if (plugins.languages) { -%>
<div class="stdin"><span class="ps1-path">▇▇▇▇@metrics</span>:<span class="ps1-location">~</span># locale</div><!--
--><div class="stdout"><!--
--><% for (const value of [0.6, 0.2, 0.1, 0.05, 0.05, 0, 0, 0]) { -%>
<b><%= `▇▇▇▇▇▇`.padEnd(12) %></b> [<%= "#".repeat(Math.ceil(100*value/5)).padEnd(20) %>] ▇%
<% } -%></div><% } -%><% if (plugins.pagespeed) { -%>
<div class="stdin"><span class="ps1-path">▇▇▇▇@metrics</span>:<span class="ps1-location">~</span># curl -I ▇▇▇▇▇▇</div><!--
--><div class="stdout"><!--
--><b>User-Agent</b>: Google PageSpeed API
<b>Location</b>: ▇▇▇▇▇▇
<b>X-Performance</b>: ▇%
<b>X-Accessibility</b>: ▇%
<b>X-Best-Practices</b>: ▇%
<b>X-SEO</b>: ▇%</div><% } -%>
<footer>Connection reset by 127.0.0.1</footer></pre>
</div>
</foreignObject>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,97 @@
query Metrics {
user(login: $login) {
databaseId
name
login
createdAt
avatarUrl
websiteUrl
gists {
totalCount
}
repositories(last: $repositories, isFork: false, ownerAffiliations: OWNER) {
totalCount
nodes {
name
watchers {
totalCount
}
stargazers {
totalCount
}
languages(first: 4) {
edges {
size
node {
color
name
}
}
}
issues_open: issues(states: OPEN) {
totalCount
}
issues_closed: issues(states: CLOSED) {
totalCount
}
pr_open: pullRequests(states: OPEN) {
totalCount
}
pr_merged: pullRequests(states: MERGED) {
totalCount
}
forkCount
licenseInfo {
spdxId
}
}
}
packages {
totalCount
}
starredRepositories {
totalCount
}
watching {
totalCount
}
sponsorshipsAsSponsor {
totalCount
}
sponsorshipsAsMaintainer {
totalCount
}
contributionsCollection {
totalRepositoriesWithContributedCommits
totalCommitContributions
restrictedContributionsCount
totalIssueContributions
totalPullRequestContributions
totalPullRequestReviewContributions
}
calendar:contributionsCollection(from: $calendar.from, to: $calendar.to) {
contributionCalendar {
weeks {
contributionDays {
color
}
}
}
}
repositoriesContributedTo {
totalCount
}
followers {
totalCount
}
following {
totalCount
}
issueComments {
totalCount
}
organizations {
totalCount
}
}
}

View File

@@ -0,0 +1,107 @@
/* SVG global context */
svg {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
font-size: 14px;
color: #777777;
}
/* Title bar */
.header {
display: flex;
justify-content: space-between;
width: 100%;
height: 20px;
align-items: center;
padding: 0 8px;
box-sizing: border-box;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background: linear-gradient(#504b45 0%,#3c3b37 100%);
}
.title {
color: #d5d0ce;
line-height: 12px;
}
.buttons {
display: flex;
align-items: center;
}
.button {
color: black;
display: flex;
justify-content: center;
margin-right: 5px;
font-size: 8px;
height: 12px;
width: 12px;
border-radius: 100%;
background: linear-gradient(#7d7871 0%, #595953 100%);
text-shadow: 0px 1px 0px rgba(255,255,255,0.2);
}
.button.exit {
background: linear-gradient(#f37458 0%, #de4c12 100%);
}
/* Terminal */
pre {
margin: 0;
background: #42092B;
padding: 12px;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
font-family: monospace;
color: #DDDDDD;
}
.banner, footer {
opacity: .7;
}
/* Prompt */
.ps1-path {
color: #7EDA29;
}
.ps1-location {
color: #4878c0;
}
/* Diff */
.diff {
color: #3A96DD;
}
/* Error */
.error {
color: #cb2431;
}
/* Animations */
.stdin, footer {
width: 0%;
white-space: nowrap;
overflow: hidden;
animation-name: stdin-animation;
animation-fill-mode: both;
}
.stdout {
max-height: 0%;
overflow: hidden;
animation-name: stdout-animation;
animation-fill-mode: both;
}
@keyframes stdin-animation {
0% { width: 0%; }
100% { width: 100%; }
}
@keyframes stdout-animation {
0% { max-height: 0; }
100% { max-height: 360px; }
}

View File

@@ -0,0 +1,34 @@
//Imports
import common from "./../common.mjs"
/** Template processor */
export default async function ({login, q}, {conf, data, rest, graphql, plugins}, {s, pending, imports}) {
//Common
await common(...arguments)
const computed = data.computed
//Compute image size
computed.svg = {height:640, width:480}
if (computed.plugins.followup)
computed.svg.height += 100 + 32
if (computed.plugins.lines)
computed.svg.height += 30 + 32
if (computed.plugins.traffic)
computed.svg.height += 16 + 32
if (computed.plugins.pagespeed)
computed.svg.height += 136 + 32
if (computed.plugins.languages)
computed.svg.height += 170 + 32
//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}y` : `${months}m`
//Meta
data.meta = {
version:conf.package.version,
$:`<span class="ps1-path">${data.user.login}@metrics</span>:<span class="ps1-location">~</span>${computed.token.scopes.includes("repo") ? "#" : "$"}`,
}
}