fix(plugins/isocalendar): Improve padding to support years better
This commit is contained in:
3 files changed
+62
-36
No files matched your search
@@ -110,6 +110,17 @@ jobs:
|
||||
plugins_errors_fatal: yes
|
||||
dryrun: yes
|
||||
|
||||
- name: Isocalendar plugin (full year)
|
||||
uses: lowlighter/metrics@master
|
||||
with:
|
||||
token: ${{ secrets.METRICS_TOKEN }}
|
||||
base: ""
|
||||
repositories: 1
|
||||
plugin_isocalendar: yes
|
||||
plugin_isocalendar_duration: full-year
|
||||
plugins_errors_fatal: yes
|
||||
dryrun: yes
|
||||
|
||||
- name: Habits plugin
|
||||
uses: lowlighter/metrics@master
|
||||
with:
|
||||
|
||||
Vendored
+17
-17
@@ -16,23 +16,23 @@ module.exports=(()=>{var _Mathhypot=Math.hypot,_Mathacos=Math.acos,_Mathtan=Math
|
||||
}
|
||||
}
|
||||
}
|
||||
`);let o=0,i=0,s=0;for(const e of a.nodes)e.isFork||(o+=e.stargazerCount,i+=e.forks.totalCount,s+=e.comments.totalCount);return{totalCount:a.totalCount,stargazers:o,forks:i,comments:s}}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},habits:async function({login:e,rest:t,q:r},{enabled:n=!1,from:a=100}={}){try{if(!n||!r.habits)return null;let{"habits.from":o=a.from??100}=r;o=_Mathmax(1,_Mathmin(100,+o));const i={commits:{hour:NaN,hours:{}},indents:{style:"",spaces:0,tabs:0}},s=await t.activity.listEventsForAuthenticatedUser({username:e,per_page:o}),l=s.data.filter(({type:e})=>"PushEvent"===e).filter(({actor:t})=>t.login===e);{const e=l.map(({created_at:e})=>new Date(e).getHours());for(const t of e)i.commits.hours[t]=(i.commits.hours[t]??0)+1;i.commits.hour=e.length?`${Object.entries(i.commits.hours).sort(([e,t],[r,n])=>n-t).map(([e,t])=>e)[0]}`.padStart(2,"0"):NaN}{const e=await Promise.allSettled(l.flatMap(({payload:e})=>e.commits).map(e=>e.url).map(async e=>(await t.request(e)).data.files));e.filter(({status:e})=>"fulfilled"===e).map(({value:e})=>e).flatMap(e=>e.flatMap(e=>(e.patch??"").match(/(?<=^[+])((?:\t)|(?: )) /gm)??[])).forEach(e=>i.indents[/^\t/.test(e)?"tabs":"spaces"]++),i.indents.style=i.indents.spaces>i.indents.tabs?"spaces":i.indents.tabs>i.indents.spaces?"tabs":""}return i}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},isocalendar:async function({login:e,graphql:t,q:r},{enabled:n=!1}={}){try{if(!n||!r.isocalendar)return null;let{"isocalendar.duration":a="half-year"}=r;const o=29===new Date(new Date().getYear(),1,29).getDate(),s={"half-year":180,"full-year":365+o}[a]??180,l=new Date(Date.now()-1e3*(60*(60*(24*s))));l.setHours(-168);const{user:{calendar:{contributionCalendar:d}}}=await t(`
|
||||
query Calendar {
|
||||
user(login: "${e}") {
|
||||
calendar:contributionsCollection(from: "${l.toISOString()}", to: "${new Date().toISOString()}") {
|
||||
contributionCalendar {
|
||||
weeks {
|
||||
contributionDays {
|
||||
contributionCount
|
||||
color
|
||||
date
|
||||
`);let o=0,i=0,s=0;for(const e of a.nodes)e.isFork||(o+=e.stargazerCount,i+=e.forks.totalCount,s+=e.comments.totalCount);return{totalCount:a.totalCount,stargazers:o,forks:i,comments:s}}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},habits:async function({login:e,rest:t,q:r},{enabled:n=!1,from:a=100}={}){try{if(!n||!r.habits)return null;let{"habits.from":o=a.from??100}=r;o=_Mathmax(1,_Mathmin(100,+o));const i={commits:{hour:NaN,hours:{}},indents:{style:"",spaces:0,tabs:0}},s=await t.activity.listEventsForAuthenticatedUser({username:e,per_page:o}),l=s.data.filter(({type:e})=>"PushEvent"===e).filter(({actor:t})=>t.login===e);{const e=l.map(({created_at:e})=>new Date(e).getHours());for(const t of e)i.commits.hours[t]=(i.commits.hours[t]??0)+1;i.commits.hour=e.length?`${Object.entries(i.commits.hours).sort(([e,t],[r,n])=>n-t).map(([e,t])=>e)[0]}`.padStart(2,"0"):NaN}{const e=await Promise.allSettled(l.flatMap(({payload:e})=>e.commits).map(e=>e.url).map(async e=>(await t.request(e)).data.files));e.filter(({status:e})=>"fulfilled"===e).map(({value:e})=>e).flatMap(e=>e.flatMap(e=>(e.patch??"").match(/(?<=^[+])((?:\t)|(?: )) /gm)??[])).forEach(e=>i.indents[/^\t/.test(e)?"tabs":"spaces"]++),i.indents.style=i.indents.spaces>i.indents.tabs?"spaces":i.indents.tabs>i.indents.spaces?"tabs":""}return i}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},isocalendar:async function({login:e,graphql:t,q:r},{enabled:n=!1}={}){try{if(!n||!r.isocalendar)return null;let{"isocalendar.duration":a="half-year"}=r;a=["full-year","half-year"].includes(a)?a:"full-year";const o=new Date,s=new Date(o);"full-year"===a?s.setFullYear(o.getFullYear()-1):s.setHours(-4320);const l=new Date(s);l.setHours(-336);const d={};for(const[r,n,a]of[["padding",l,s],["weeks",s,o]]){console.debug(`metrics/compute/${e}/plugins > isocalendar > loading "${r}" from "${n.toISOString()}" to "${a.toISOString()}"`);const{user:{calendar:{contributionCalendar:{weeks:o}}}}=await t(`
|
||||
query Calendar {
|
||||
user(login: "${e}") {
|
||||
calendar:contributionsCollection(from: "${n.toISOString()}", to: "${a.toISOString()}") {
|
||||
contributionCalendar {
|
||||
weeks {
|
||||
contributionDays {
|
||||
contributionCount
|
||||
color
|
||||
date
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);d.weeks.shift();let p=0,c={max:0,current:0},u=[],m=0;for(const e of d.weeks)for(const t of e.contributionDays)u.push(t.contributionCount),p=_Mathmax(p,t.contributionCount),c.current=t.contributionCount?c.current+1:0,c.max=_Mathmax(c.max,c.current);m=(u.reduce((e,t)=>e+t,0)/u.length).toFixed(2).replace(/[.]0+$/,"");const g=6;let h=0,i=0,f=`
|
||||
`);d[r]=o}const p=d.weeks[0].contributionDays,c=d.padding.flatMap(({contributionDays:e})=>e).filter(({date:e})=>!p.map(({date:e})=>e).includes(e));for(;7>p.length;)p.unshift(c.pop());let u=0,m={max:0,current:0},g=[],h=0;for(const e of d.weeks)for(const t of e.contributionDays)g.push(t.contributionCount),u=_Mathmax(u,t.contributionCount),m.current=t.contributionCount?m.current+1:0,m.max=_Mathmax(m.max,m.current);h=(g.reduce((e,t)=>e+t,0)/g.length).toFixed(2).replace(/[.]0+$/,"");const f=6;let y=0,i=0,b=`
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="margin-top: -52px;" viewBox="0,0 480,${"full-year"===a?270:170}">
|
||||
${[1,2].map(e=>`
|
||||
<filter id="brightness${e}">
|
||||
@@ -40,12 +40,12 @@ module.exports=(()=>{var _Mathhypot=Math.hypot,_Mathacos=Math.acos,_Mathtan=Math
|
||||
${[..."RGB"].map(t=>`<feFunc${t} type="linear" slope="${1-.4*e}" />`).join("")}
|
||||
</feComponentTransfer>
|
||||
</filter>`).join("")}
|
||||
<g transform="scale(4) translate(12, 0)">`;for(const e of d.weeks){f+=`<g transform="translate(${1.7*h}, ${h})">`,i=0;for(const t of e.contributionDays){const e=t.contributionCount/p;f+=`
|
||||
<g transform="translate(${-1.7*i}, ${i+(1-e)*g})">
|
||||
<g transform="scale(4) translate(12, 0)">`;for(const e of d.weeks){b+=`<g transform="translate(${1.7*y}, ${y})">`,i=0;for(const t of e.contributionDays){const e=t.contributionCount/u;b+=`
|
||||
<g transform="translate(${-1.7*i}, ${i+(1-e)*f})">
|
||||
<path fill="${t.color}" d="M1.7,2 0,1 1.7,0 3.4,1 z" />
|
||||
<path fill="${t.color}" filter="url(#brightness1)" d="M0,1 1.7,2 1.7,${2+e*g} 0,${1+e*g} z" />
|
||||
<path fill="${t.color}" filter="url(#brightness2)" d="M1.7,2 3.4,1 3.4,${1+e*g} 1.7,${2+e*g} z" />
|
||||
</g>`,i++}f+=`</g>`,h++}return f+=`</g></svg>`,{streak:c,max:p,average:m,svg:f,duration:a}}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},languages:async function({data:e,q:t},{enabled:r=!1}={}){try{if(!r||!t.languages)return null;const n={colors:{},total:0,stats:{}};for(const t of e.user.repositories.nodes)for(const{size:e,node:{color:r,name:a}}of Object.values(t.languages.edges))n.stats[a]=(n.stats[a]??0)+e,n.colors[a]=r??"#ededed",n.total+=e;Object.keys(n.stats).map(e=>n.stats[e]/=n.total),n.favorites=Object.entries(n.stats).sort(([e,t],[r,n])=>n-t).slice(0,8).map(([e,t])=>({name:e,value:t,color:n.colors[e],x:0}));for(let e=1;e<n.favorites.length;e++)n.favorites[e].x=n.favorites[e-1].x+n.favorites[e-1].value;return n}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},lines:async function({login:e,data:t,imports:r,rest:n,q:a},{enabled:o=!1}={}){try{if(!o||!a.lines)return null;const i=t.user.repositories.nodes.map(({name:e})=>e)??[],s={added:0,deleted:0},l=await Promise.all(i.map(async t=>await n.repos.getContributorsStats({owner:e,repo:t})));return l.map(({data:t})=>{if(Array.isArray(t)){const[r]=t.filter(({author:t})=>t.login===e);r&&r.weeks.forEach(({a:e,d:t})=>(s.added+=e,s.deleted+=t))}}),s.added=r.format(s.added),s.deleted=r.format(s.deleted),s}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},music:a,pagespeed:async function({imports:e,data:t,q:r},{enabled:n=!1,token:a=null}={}){try{if(!n||!r.pagespeed||!t.user.websiteUrl)return null;let o=t.user.websiteUrl;/^https?:[/][/]/.test(o)||(o=`https://${o}`);const i=new Map;return await Promise.all(["performance","accessibility","best-practices","seo"].map(async t=>{const{score:r,title:n}=(await e.axios.get(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?category=${t}&url=${o}&key=${a}`)).data.lighthouseResult.categories[t];i.set(t,{score:r,title:n})})),{url:o,scores:[i.get("performance"),i.get("accessibility"),i.get("best-practices"),i.get("seo")]}}catch(e){if(e.response?.status)throw{error:{message:`PageSpeed token error (code ${e.response.status})`},url};throw{error:{message:`An error occured`}}}},posts:async function({imports:e,data:t,q:r},{enabled:n=!1}={}){try{if(!n||!r.posts)return null;const a=t.user.login;let{"posts.source":o="","posts.limit":i=4}=r;i=_Mathmax(1,_Mathmin(30,+i));let s=null;switch(o){case"dev.to":{s=(await e.axios.get(`https://dev.to/api/articles?username=${a}&state=fresh`)).data.map(({title:e,readable_publish_date:t})=>({title:e,date:t}));break}default:throw{error:{message:`Unsupported source "${o}"`}};}if(Array.isArray(s))return 0<i&&(console.debug(`metrics/compute/${a}/plugins > posts > keeping only ${i} posts`),s=s.slice(0,i)),{source:o,posts:s};throw{error:{message:`An error occured (could not retrieve posts)`}}}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},selfskip:async function({login:e,rest:t,computed:r,q:n},{enabled:a=!1}={}){try{if(!a||!n.selfskip)return null;let o=0;for(let r=0;;r++){const{data:n}=await t.repos.listCommits({owner:e,repo:e,author:e,per_page:100,page:r});if(o+=n.filter(({commit:e})=>/\[Skip GitHub Action\]/.test(e.message)).length,!n.length)break}return r.commits-=o,{commits:o}}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},traffic:async function({login:e,imports:t,data:r,rest:n,q:a},{enabled:o=!1}={}){try{if(!o||!a.traffic)return null;const i=r.user.repositories.nodes.map(({name:e})=>e)??[],s={count:0,uniques:0},l=await Promise.all(i.map(async t=>await n.repos.getViews({owner:e,repo:t})));return l.filter(({data:e})=>e).map(({data:{count:e,uniques:t}})=>(s.count+=e,s.uniques+=t)),s.count=t.format(s.count),s.uniques=t.format(s.uniques),{views:s}}catch(e){if(403===e.status)throw{error:{message:`Insufficient token rights`}};throw console.debug(e),{error:{message:`An error occured`}}}}},C={classic:async function({login:e,q:t},{conf:r,data:n,rest:a,graphql:i,plugins:l},{s:d,pending:s,imports:p}){await o(...arguments)},terminal:async function({login:e,q:t},{conf:r,data:n,rest:a,graphql:i,plugins:l},{s:d,pending:s,imports:p}){await o(...arguments),t.raw=!0}};var w=r(34651),_=r(78835),T=r(3584),E=r.t(T),A=r(32882),O=r.t(A),P=r(29483),L=r.t(P);(async function(){const[e,t,n,a,o]=[O,L,E,u,m].map(e=>e&&e.default?e.default:e),i=(e,t=!1)=>"string"==typeof e?/^(?:[Tt]rue|[Oo]n|[Yy]es)$/.test(e):t;try{console.log(`GitHub metrics as SVG image`),console.log(`========================================================`),console.log(`Version | 2.5.0`),process.on("unhandledRejection",e=>{throw e}),"push"===t.context.eventName&&t.context.payload&&t.context.payload.head_commit&&/\[Skip GitHub Action\]/.test(t.context.payload.head_commit.message)&&(console.log(`Skipped because [Skip GitHub Action] is in commit message`),process.exit(0));const s=await a({log:!1});console.log(`Configuration | loaded`);const l=e.getInput("template")||"classic";console.log(`Template to use Line truncated
|
||||
<path fill="${t.color}" filter="url(#brightness1)" d="M0,1 1.7,2 1.7,${2+e*f} 0,${1+e*f} z" />
|
||||
<path fill="${t.color}" filter="url(#brightness2)" d="M1.7,2 3.4,1 3.4,${1+e*f} 1.7,${2+e*f} z" />
|
||||
</g>`,i++}b+=`</g>`,y++}return b+=`</g></svg>`,{streak:m,max:u,average:h,svg:b,duration:a}}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},languages:async function({data:e,q:t},{enabled:r=!1}={}){try{if(!r||!t.languages)return null;const n={colors:{},total:0,stats:{}};for(const t of e.user.repositories.nodes)for(const{size:e,node:{color:r,name:a}}of Object.values(t.languages.edges))n.stats[a]=(n.stats[a]??0)+e,n.colors[a]=r??"#ededed",n.total+=e;Object.keys(n.stats).map(e=>n.stats[e]/=n.total),n.favorites=Object.entries(n.stats).sort(([e,t],[r,n])=>n-t).slice(0,8).map(([e,t])=>({name:e,value:t,color:n.colors[e],x:0}));for(let e=1;e<n.favorites.length;e++)n.favorites[e].x=n.favorites[e-1].x+n.favorites[e-1].value;return n}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},lines:async function({login:e,data:t,imports:r,rest:n,q:a},{enabled:o=!1}={}){try{if(!o||!a.lines)return null;const i=t.user.repositories.nodes.map(({name:e})=>e)??[],s={added:0,deleted:0},l=await Promise.all(i.map(async t=>await n.repos.getContributorsStats({owner:e,repo:t})));return l.map(({data:t})=>{if(Array.isArray(t)){const[r]=t.filter(({author:t})=>t.login===e);r&&r.weeks.forEach(({a:e,d:t})=>(s.added+=e,s.deleted+=t))}}),s.added=r.format(s.added),s.deleted=r.format(s.deleted),s}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},music:a,pagespeed:async function({imports:e,data:t,q:r},{enabled:n=!1,token:a=null}={}){try{if(!n||!r.pagespeed||!t.user.websiteUrl)return null;let o=t.user.websiteUrl;/^https?:[/][/]/.test(o)||(o=`https://${o}`);const i=new Map;return await Promise.all(["performance","accessibility","best-practices","seo"].map(async t=>{const{score:r,title:n}=(await e.axios.get(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?category=${t}&url=${o}&key=${a}`)).data.lighthouseResult.categories[t];i.set(t,{score:r,title:n})})),{url:o,scores:[i.get("performance"),i.get("accessibility"),i.get("best-practices"),i.get("seo")]}}catch(e){if(e.response?.status)throw{error:{message:`PageSpeed token error (code ${e.response.status})`},url};throw{error:{message:`An error occured`}}}},posts:async function({imports:e,data:t,q:r},{enabled:n=!1}={}){try{if(!n||!r.posts)return null;const a=t.user.login;let{"posts.source":o="","posts.limit":i=4}=r;i=_Mathmax(1,_Mathmin(30,+i));let s=null;switch(o){case"dev.to":{s=(await e.axios.get(`https://dev.to/api/articles?username=${a}&state=fresh`)).data.map(({title:e,readable_publish_date:t})=>({title:e,date:t}));break}default:throw{error:{message:`Unsupported source "${o}"`}};}if(Array.isArray(s))return 0<i&&(console.debug(`metrics/compute/${a}/plugins > posts > keeping only ${i} posts`),s=s.slice(0,i)),{source:o,posts:s};throw{error:{message:`An error occured (could not retrieve posts)`}}}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},selfskip:async function({login:e,rest:t,computed:r,q:n},{enabled:a=!1}={}){try{if(!a||!n.selfskip)return null;let o=0;for(let r=0;;r++){const{data:n}=await t.repos.listCommits({owner:e,repo:e,author:e,per_page:100,page:r});if(o+=n.filter(({commit:e})=>/\[Skip GitHub Action\]/.test(e.message)).length,!n.length)break}return r.commits-=o,{commits:o}}catch(e){throw console.debug(e),{error:{message:`An error occured`}}}},traffic:async function({login:e,imports:t,data:r,rest:n,q:a},{enabled:o=!1}={}){try{if(!o||!a.traffic)return null;const i=r.user.repositories.nodes.map(({name:e})=>e)??[],s={count:0,uniques:0},l=await Promise.all(i.map(async t=>await n.repos.getViews({owner:e,repo:t})));return l.filter(({data:e})=>e).map(({data:{count:e,uniques:t}})=>(s.count+=e,s.uniques+=t)),s.count=t.format(s.count),s.uniques=t.format(s.uniques),{views:s}}catch(e){if(403===e.status)throw{error:{message:`Insufficient token rights`}};throw console.debug(e),{error:{message:`An error occured`}}}}},C={classic:async function({login:e,q:t},{conf:r,data:n,rest:a,graphql:i,plugins:l},{s:d,pending:s,imports:p}){await o(...arguments)},terminal:async function({login:e,q:t},{conf:r,data:n,rest:a,graphql:i,plugins:l},{s:d,pending:s,imports:p}){await o(...arguments),t.raw=!0}};var w=r(34651),_=r(78835),T=r(3584),E=r.t(T),A=r(32882),O=r.t(A),P=r(29483),L=r.t(P);(async function(){const[e,t,n,a,o]=[O,L,E,u,m].map(e=>e&&e.default?e.default:e),i=(e,t=!1)=>"string"==typeof e?/^(?:[Tt]rue|[Oo]n|[Yy]es)$/.test(e):t;try{console.log(`GitHub metrics as SVG image`),console.log(`========================================================`),console.log(`Version | 2.5.0`),process.on("unhandledRejection",e=>{throw e}),"push"===t.context.eventName&&t.context.payload&&t.context.payload.head_commit&&/\[Skip GitHub Action\]/.test(t.context.payload.head_commit.message)&&(console.log(`Skipped because [Skip GitHub Action] is in commit message`),process.exit(0));const s=await a({log:!1});console.log(`Configuration | loaded`);const l=e.getInput("template")||"classic";console.log(`Template to use Line truncated
|
||||
* @file Embedded JavaScript templating engine. {@link http://ejs.co}
|
||||
* @author Matthew Eernisse <mde@fleegix.org>
|
||||
* @author Tiancheng "Timothy" Gu <timothygu99@gmail.com>
|
||||
|
||||
@@ -8,31 +8,46 @@
|
||||
//Parameters override
|
||||
let {"isocalendar.duration":duration = "half-year"} = q
|
||||
//Duration in days
|
||||
const leap = (new Date(new Date().getYear(), 1, 29).getDate() === 29)
|
||||
const days = {"half-year":180, "full-year":365 + leap}[duration] ?? 180
|
||||
//Compute start day (to ensure last row is complete, we'll retrieve one more week that we'll shift later)
|
||||
const from = new Date(Date.now()-days*24*60*60*1000)
|
||||
from.setHours(-24*7)
|
||||
duration = ["full-year", "half-year"].includes(duration) ? duration : "full-year"
|
||||
//Compute start day
|
||||
const now = new Date()
|
||||
const start = new Date(now)
|
||||
if (duration === "full-year")
|
||||
start.setFullYear(now.getFullYear()-1)
|
||||
else
|
||||
start.setHours(-24*180)
|
||||
//Compute padding to ensure last row is complete
|
||||
const padding = new Date(start)
|
||||
padding.setHours(-14*24)
|
||||
//Retrieve contribution calendar from graphql api
|
||||
const {user:{calendar:{contributionCalendar:calendar}}} = await graphql(`
|
||||
query Calendar {
|
||||
user(login: "${login}") {
|
||||
calendar:contributionsCollection(from: "${from.toISOString()}", to: "${(new Date()).toISOString()}") {
|
||||
contributionCalendar {
|
||||
weeks {
|
||||
contributionDays {
|
||||
contributionCount
|
||||
color
|
||||
date
|
||||
const calendar = {}
|
||||
for (const [name, from, to] of [["padding", padding, start], ["weeks", start, now]]) {
|
||||
console.debug(`metrics/compute/${login}/plugins > isocalendar > loading "${name}" from "${from.toISOString()}" to "${to.toISOString()}"`)
|
||||
const {user:{calendar:{contributionCalendar:{weeks}}}} = await graphql(`
|
||||
query Calendar {
|
||||
user(login: "${login}") {
|
||||
calendar:contributionsCollection(from: "${from.toISOString()}", to: "${to.toISOString()}") {
|
||||
contributionCalendar {
|
||||
weeks {
|
||||
contributionDays {
|
||||
contributionCount
|
||||
color
|
||||
date
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
calendar.weeks.shift()
|
||||
`
|
||||
)
|
||||
calendar[name] = weeks
|
||||
}
|
||||
//Apply padding
|
||||
const firstweek = calendar.weeks[0].contributionDays
|
||||
const padded = calendar.padding.flatMap(({contributionDays}) => contributionDays).filter(({date}) => !firstweek.map(({date}) => date).includes(date))
|
||||
while (firstweek.length < 7)
|
||||
firstweek.unshift(padded.pop())
|
||||
//Compute the highest contributions in a day, streaks and average commits per day
|
||||
let max = 0, streak = {max:0, current:0}, values = [], average = 0
|
||||
for (const week of calendar.weeks) {
|
||||
|
||||
Reference in new issue
Block a user