feat(plugins/calendar): add new plugin (#1013) [skip ci]
This commit is contained in:
12
source/plugins/calendar/README.md
Normal file
12
source/plugins/calendar/README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!--header-->
|
||||||
|
<!--/header-->
|
||||||
|
|
||||||
|
## ➡️ Available options
|
||||||
|
|
||||||
|
<!--options-->
|
||||||
|
<!--/options-->
|
||||||
|
|
||||||
|
## ℹ️ Examples workflows
|
||||||
|
|
||||||
|
<!--examples-->
|
||||||
|
<!--/examples-->
|
||||||
16
source/plugins/calendar/examples.yml
Normal file
16
source/plugins/calendar/examples.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
- name: Current year calendar
|
||||||
|
uses: lowlighter/metrics@latest
|
||||||
|
with:
|
||||||
|
filename: metrics.plugin.calendar.svg
|
||||||
|
token: ${{ secrets.METRICS_TOKEN }}
|
||||||
|
base: ""
|
||||||
|
plugin_calendar: yes
|
||||||
|
|
||||||
|
- name: Full history calendar
|
||||||
|
uses: lowlighter/metrics@latest
|
||||||
|
with:
|
||||||
|
filename: metrics.plugin.calendar.full.svg
|
||||||
|
token: ${{ secrets.METRICS_TOKEN }}
|
||||||
|
base: ""
|
||||||
|
plugin_calendar: yes
|
||||||
|
plugin_calendar_limit: 0
|
||||||
56
source/plugins/calendar/index.mjs
Normal file
56
source/plugins/calendar/index.mjs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//Setup
|
||||||
|
export default async function({login, q, data, imports, graphql, queries, account}, {enabled = false} = {}) {
|
||||||
|
//Plugin execution
|
||||||
|
try {
|
||||||
|
//Check if plugin is enabled and requirements are met
|
||||||
|
if ((!enabled)||(!q.calendar))
|
||||||
|
return null
|
||||||
|
|
||||||
|
//Load inputs
|
||||||
|
let {limit} = imports.metadata.plugins.calendar.inputs({data, account, q})
|
||||||
|
|
||||||
|
//Compute boundaries
|
||||||
|
const end = new Date().getFullYear()
|
||||||
|
const start = new Date(limit ? end-limit+1 : data.user.createdAt, 0).getFullYear()
|
||||||
|
|
||||||
|
//Load contribution calendar
|
||||||
|
console.debug(`metrics/compute/${login}/plugins > calendar > processing years ${start} to ${end}`)
|
||||||
|
const calendar = {years:[]}
|
||||||
|
for (let year = start; year <= end; year++) {
|
||||||
|
console.debug(`metrics/compute/${login}/plugins > calendar > processing year ${year}`)
|
||||||
|
const weeks = []
|
||||||
|
const newyear = new Date(year, 0, 1)
|
||||||
|
const endyear = (year === end) ? new Date() : new Date(year, 11, 31)
|
||||||
|
for (let from = new Date(newyear); from < endyear;) {
|
||||||
|
//Set date range and ensure we start on sundays
|
||||||
|
let to = new Date(from)
|
||||||
|
to.setUTCHours(+4 * 7 * 24)
|
||||||
|
if (to.getUTCDay())
|
||||||
|
to.setUTCHours(-to.getUTCDay() * 24)
|
||||||
|
if (to > endyear)
|
||||||
|
to = endyear
|
||||||
|
|
||||||
|
//Ensure that date ranges are not overlapping by setting it to previous day at 23:59:59.999
|
||||||
|
const dto = new Date(to)
|
||||||
|
dto.setUTCHours(-1)
|
||||||
|
dto.setUTCMinutes(59)
|
||||||
|
dto.setUTCSeconds(59)
|
||||||
|
dto.setUTCMilliseconds(999)
|
||||||
|
//Fetch data from api
|
||||||
|
console.debug(`metrics/compute/${login}/plugins > calendar > loading calendar from "${from.toISOString()}" to "${dto.toISOString()}"`)
|
||||||
|
const {user:{calendar:{contributionCalendar}}} = await graphql(queries.isocalendar.calendar({login, from:from.toISOString(), to:dto.toISOString()}))
|
||||||
|
weeks.push(...contributionCalendar.weeks)
|
||||||
|
//Set next date range start
|
||||||
|
from = new Date(to)
|
||||||
|
}
|
||||||
|
calendar.years.unshift({year, weeks})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Results
|
||||||
|
return calendar
|
||||||
|
}
|
||||||
|
//Handle errors
|
||||||
|
catch (error) {
|
||||||
|
throw {error:{message:"An error occured", instance:error}}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
source/plugins/calendar/metadata.yml
Normal file
22
source/plugins/calendar/metadata.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: "📆 Calendar"
|
||||||
|
category: github
|
||||||
|
description: This plugin displays your commit calendar across several years
|
||||||
|
examples:
|
||||||
|
+current year: https://github.com/lowlighter/metrics/blob/examples/metrics.plugin.calendar.svg
|
||||||
|
full history: https://github.com/lowlighter/metrics/blob/examples/metrics.plugin.calendar.full.svg
|
||||||
|
supports:
|
||||||
|
- user
|
||||||
|
scopes:
|
||||||
|
- public_access
|
||||||
|
inputs:
|
||||||
|
|
||||||
|
plugin_calendar:
|
||||||
|
description: Enable calendar plugin
|
||||||
|
type: boolean
|
||||||
|
default: no
|
||||||
|
|
||||||
|
plugin_calendar_limit:
|
||||||
|
description: Years to display
|
||||||
|
type: number
|
||||||
|
default: 1
|
||||||
|
zero: disable
|
||||||
15
source/plugins/calendar/queries/calendar.graphql
Normal file
15
source/plugins/calendar/queries/calendar.graphql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
query CalendarDefault {
|
||||||
|
user(login: "$login") {
|
||||||
|
calendar:contributionsCollection(from: "$from", to: "$to") {
|
||||||
|
contributionCalendar {
|
||||||
|
weeks {
|
||||||
|
contributionDays {
|
||||||
|
contributionCount
|
||||||
|
color
|
||||||
|
date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"rss",
|
"rss",
|
||||||
"tweets",
|
"tweets",
|
||||||
"isocalendar",
|
"isocalendar",
|
||||||
|
"calendar",
|
||||||
"stars",
|
"stars",
|
||||||
"starlists",
|
"starlists",
|
||||||
"stargazers",
|
"stargazers",
|
||||||
|
|||||||
33
source/templates/classic/partials/calendar.ejs
Normal file
33
source/templates/classic/partials/calendar.ejs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<% if (plugins.calendar) { %>
|
||||||
|
<section>
|
||||||
|
<h2 class="field">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.75 0a.75.75 0 01.75.75V2h5V.75a.75.75 0 011.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0113.25 16H2.75A1.75 1.75 0 011 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 014.75 0zm0 3.5h8.5a.25.25 0 01.25.25V6h-11V3.75a.25.25 0 01.25-.25h2zm-2.25 4v6.75c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V7.5h-11z"></path></svg>
|
||||||
|
Contributions calendar
|
||||||
|
</h2>
|
||||||
|
<div class="row">
|
||||||
|
<section>
|
||||||
|
<% if (plugins.calendar.error) { %>
|
||||||
|
<div class="field error">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
|
||||||
|
<%= plugins.calendar.error.message %>
|
||||||
|
</div>
|
||||||
|
<% } else { %>
|
||||||
|
<svg class="calendar" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0,0 795,<%= 130 * plugins.calendar.years.length %>">
|
||||||
|
<% for (const [r, {year, weeks}] of Object.entries(plugins.calendar.years)) { %>
|
||||||
|
<g transform="translate(0, <%= 14 + r * 130 %>)">
|
||||||
|
<text x="0" y="0"><%= year %></text>
|
||||||
|
<% for (const [x, week] of Object.entries(weeks)) { %>
|
||||||
|
<g transform="translate(<%= x*15 %>, 0)">
|
||||||
|
<% for (const [y, {color}] of Object.entries(week.contributionDays)) { %>
|
||||||
|
<rect class="day" x="0" y="<%= 4 + (x == 0)*(7-week.contributionDays.length)*15 + y*15 %>" width="11" height="11" fill="<%= color %>" rx="2" ry="2" />
|
||||||
|
<% } %>
|
||||||
|
</g>
|
||||||
|
<% } %>
|
||||||
|
</g>
|
||||||
|
<% } %>
|
||||||
|
</svg>
|
||||||
|
<% } %>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<% } %>
|
||||||
@@ -838,6 +838,17 @@
|
|||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Calendar */
|
||||||
|
svg.calendar {
|
||||||
|
margin-left: 13px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.calendar text {
|
||||||
|
font-size: 18px;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
/* People */
|
/* People */
|
||||||
.people {
|
.people {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|||||||
32
tests/mocks/api/github/graphql/calendar.default.mjs
Normal file
32
tests/mocks/api/github/graphql/calendar.default.mjs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**Mocked data */
|
||||||
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
|
console.debug("metrics/compute/mocks > mocking graphql api result > calendar/default")
|
||||||
|
//Generate calendar
|
||||||
|
const date = new Date(query.match(/from: "(?<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)"/)?.groups?.date)
|
||||||
|
const to = new Date(query.match(/to: "(?<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)"/)?.groups?.date)
|
||||||
|
const weeks = []
|
||||||
|
let contributionDays = []
|
||||||
|
for (; date <= to; date.setDate(date.getDate() + 1)) {
|
||||||
|
//Create new week on sunday
|
||||||
|
if (date.getDay() === 0) {
|
||||||
|
weeks.push({contributionDays})
|
||||||
|
contributionDays = []
|
||||||
|
}
|
||||||
|
//Random contributions
|
||||||
|
const contributionCount = Math.min(10, Math.max(0, faker.datatype.number(14) - 4))
|
||||||
|
contributionDays.push({
|
||||||
|
contributionCount,
|
||||||
|
color: ["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"][Math.ceil(contributionCount / 10 / 0.25)],
|
||||||
|
date: date.toISOString().substring(0, 10),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return ({
|
||||||
|
user: {
|
||||||
|
calendar: {
|
||||||
|
contributionCalendar: {
|
||||||
|
weeks,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user