Skip to content

Commit 3c4402e

Browse files
authored
refactor(metrics/insights): new features (lowlighter#1098)
1 parent 79b80b1 commit 3c4402e

File tree

10 files changed

+733
-143
lines changed

10 files changed

+733
-143
lines changed

source/app/metrics/index.mjs

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import util from "util"
44
import * as utils from "./utils.mjs"
55

66
//Setup
7-
export default async function metrics({login, q}, {graphql, rest, plugins, conf, die = false, verify = false, convert = null}, {Plugins, Templates}) {
7+
export default async function metrics({login, q}, {graphql, rest, plugins, conf, die = false, verify = false, convert = null, callbacks = null}, {Plugins, Templates}) {
88
//Compute rendering
99
try {
1010
//Debug
@@ -59,8 +59,8 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
5959

6060
//Executing base plugin and compute metrics
6161
console.debug(`metrics/compute/${login} > compute`)
62-
await Plugins.base({login, q, data, rest, graphql, plugins, queries, pending, imports}, conf)
63-
await computer({login, q}, {conf, data, rest, graphql, plugins, queries, account: data.account, convert, template}, {pending, imports})
62+
await Plugins.base({login, q, data, rest, graphql, plugins, queries, pending, imports, callbacks}, conf)
63+
await computer({login, q}, {conf, data, rest, graphql, plugins, queries, account: data.account, convert, template, callbacks}, {pending, imports})
6464
const promised = await Promise.all(pending)
6565

6666
//Check plugins errors
@@ -211,39 +211,53 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
211211
}
212212

213213
//Metrics insights
214-
metrics.insights = async function({login}, {graphql, rest, conf}, {Plugins, Templates}) {
215-
const q = {
216-
template: "classic",
217-
achievements: true,
218-
"achievements.threshold": "X",
219-
isocalendar: true,
220-
"isocalendar.duration": "full-year",
221-
languages: true,
222-
"languages.limit": 0,
223-
activity: true,
224-
"activity.limit": 100,
225-
"activity.days": 0,
226-
notable: true,
227-
followup: true,
228-
"followup.sections": "repositories, user",
229-
habits: true,
230-
"habits.from": 100,
231-
"habits.days": 7,
232-
"habits.facts": false,
233-
"habits.charts": true,
234-
introduction: true,
235-
}
236-
const plugins = {
237-
achievements: {enabled: true},
238-
isocalendar: {enabled: true},
239-
languages: {enabled: true, extras: false},
240-
activity: {enabled: true, markdown: "extended"},
241-
notable: {enabled: true},
242-
followup: {enabled: true},
243-
habits: {enabled: true, extras: false},
244-
introduction: {enabled: true},
245-
}
246-
return metrics({login, q}, {graphql, rest, plugins, conf, convert: "json"}, {Plugins, Templates})
214+
metrics.insights = async function({login}, {graphql, rest, conf, callbacks}, {Plugins, Templates}) {
215+
return metrics({login, q:metrics.insights.q}, {graphql, rest, plugins:metrics.insights.plugins, conf, callbacks, convert: "json"}, {Plugins, Templates})
216+
}
217+
metrics.insights.q = {
218+
template: "classic",
219+
achievements: true,
220+
"achievements.threshold": "X",
221+
isocalendar: true,
222+
"isocalendar.duration": "full-year",
223+
languages: true,
224+
"languages.limit": 0,
225+
activity: true,
226+
"activity.limit": 100,
227+
"activity.days": 0,
228+
"activity.timestamps": true,
229+
notable: true,
230+
"notable.repositories": true,
231+
followup: true,
232+
"followup.sections": "repositories, user",
233+
introduction: true,
234+
topics: true,
235+
"topics.mode": "icons",
236+
"topics.limit": 0,
237+
stars: true,
238+
"stars.limit": 6,
239+
reactions: true,
240+
"reactions.details": "percentage",
241+
repositories: true,
242+
"repositories.pinned": 6,
243+
sponsors: true,
244+
calendar: true,
245+
"calendar.limit": 0,
246+
}
247+
metrics.insights.plugins = {
248+
achievements: {enabled: true},
249+
isocalendar: {enabled: true},
250+
languages: {enabled: true, extras: false},
251+
activity: {enabled: true, markdown: "extended"},
252+
notable: {enabled: true},
253+
followup: {enabled: true},
254+
introduction: {enabled: true},
255+
topics: {enabled: true},
256+
stars: {enabled: true},
257+
reactions: {enabled: true},
258+
repositories: {enabled: true},
259+
sponsors: {enabled: true},
260+
calendar: {enabled: true},
247261
}
248262

249263
//Metrics insights static render
@@ -278,5 +292,5 @@ metrics.insights.output = async function({login, imports, conf}, {graphql, rest,
278292
</body>
279293
</html>`
280294
await browser.close()
281-
return {mime: "text/html", rendered}
295+
return {mime: "text/html", rendered, errors:json.errors}
282296
}

source/app/web/instance.mjs

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,32 @@ export default async function({sandbox = false} = {}) {
172172
}
173173
})
174174

175+
//Pending requests
176+
const pending = new Map()
177+
175178
//About routes
176179
app.use("/about/.statics/", express.static(`${conf.paths.statics}/about`))
177180
app.get("/about/", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/about/index.html`))
178181
app.get("/about/index.html", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/about/index.html`))
179182
app.get("/about/:login", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/about/index.html`))
183+
app.get("/about/query/:login/:plugin/", async (req, res) => {
184+
//Check username
185+
const login = req.params.login?.replace(/[\n\r]/g, "")
186+
if (!/^[-\w]+$/i.test(login)) {
187+
console.debug(`metrics/app/${login}/insights > 400 (invalid username)`)
188+
return res.status(400).send("Bad request: username seems invalid")
189+
}
190+
//Check plugin
191+
const plugin = req.params.plugin?.replace(/[\n\r]/g, "")
192+
if (!/^\w+$/i.test(plugin)) {
193+
console.debug(`metrics/app/${login}/insights > 400 (invalid plugin name)`)
194+
return res.status(400).send("Bad request: plugin name seems invalid")
195+
}
196+
if (cache.get(`about.${login}.${plugin}`)) {
197+
return res.send(cache.get(`about.${login}.${plugin}`))
198+
}
199+
return res.status(204).send("No content: no data fetched yet")
200+
})
180201
app.get("/about/query/:login/", ...middlewares, async (req, res) => {
181202
//Check username
182203
const login = req.params.login?.replace(/[\n\r]/g, "")
@@ -185,21 +206,45 @@ export default async function({sandbox = false} = {}) {
185206
return res.status(400).send("Bad request: username seems invalid")
186207
}
187208
//Compute metrics
209+
let solve = null
188210
try {
211+
//Prevent multiples requests
212+
if ((!debug) && (!mock) && (pending.has(`about.${login}`))) {
213+
console.debug(`metrics/app/${login}/insights > awaiting pending request`)
214+
await pending.get(`about.${login}`)
215+
}
216+
else {
217+
pending.set(`about.${login}`, new Promise(_solve => solve = _solve))
218+
}
189219
//Read cached data if possible
190220
if ((!debug) && (cached) && (cache.get(`about.${login}`))) {
191221
console.debug(`metrics/app/${login}/insights > using cached results`)
192222
return res.send(cache.get(`about.${login}`))
193223
}
194224
//Compute metrics
195225
console.debug(`metrics/app/${login}/insights > compute insights`)
196-
const json = await metrics.insights({login}, {graphql, rest, conf}, {Plugins, Templates})
197-
//Cache
198-
if ((!debug) && (cached)) {
199-
const maxage = Math.round(Number(req.query.cache))
200-
cache.put(`about.${login}`, json, maxage > 0 ? maxage : cached)
226+
const callbacks = {
227+
async plugin(login, plugin, success, result) {
228+
console.debug(`metrics/app/${login}/insights/plugins > ${plugin} > ${success ? "success" : "failure"}`)
229+
cache.put(`about.${login}.${plugin}`, result)
230+
}
201231
}
202-
return res.json(json)
232+
;(async () => {
233+
try {
234+
const json = await metrics.insights({login}, {graphql, rest, conf, callbacks}, {Plugins, Templates})
235+
//Cache
236+
cache.put(`about.${login}`, json)
237+
if ((!debug) && (cached)) {
238+
const maxage = Math.round(Number(req.query.cache))
239+
cache.put(`about.${login}`, json, maxage > 0 ? maxage : cached)
240+
}
241+
}
242+
catch (error) {
243+
console.error(`metrics/app/${login}/insights > error > ${error}`)
244+
}
245+
})()
246+
console.debug(`metrics/app/${login}/insights > accepted request`)
247+
return res.status(202).json({processing:true, plugins:Object.keys(metrics.insights.plugins)})
203248
}
204249
//Internal error
205250
catch (error) {
@@ -219,12 +264,12 @@ export default async function({sandbox = false} = {}) {
219264
return res.status(500).send("Internal Server Error: failed to process metrics correctly")
220265
}
221266
finally {
267+
solve?.()
222268
_requests_refresh = true
223269
}
224270
})
225271

226272
//Metrics
227-
const pending = new Map()
228273
app.get("/:login/:repository?", ...middlewares, async (req, res) => {
229274
//Request params
230275
const login = req.params.login?.replace(/[\n\r]/g, "")

0 commit comments

Comments
 (0)