openvibers/openvibe.media Commits
App stats: hours archived answer every window (it was an un-awaited function, so the JSON said hours: {}), and each window carries pw, the seven days before the last seven (#197)
Changes
@@@ -324,11 +324,17 @@ async function latestVodThumbsByManagedStreams(appId, managedStreamIds) {/** Aggregate per-app media stats for the owning app's dashboards/heroes. */async function getAppStats(appId) {const c = async (sql, p = []) => { try { return (await get(sql, p))?.n || 0; } catch { return 0; } };-- const win = async (sql, extraParams = []) => ({-- d: await c(sql, [...extraParams, '-1 day']),-- w: await c(sql, [...extraParams, '-7 days']),-- m: await c(sql, [...extraParams, '-30 days']),-- });++ // Rolling day/week/month figures, and `pw`: the seven days before the last seven, so a reader can say whether++ // this week beat the last one.++ const win = async (agg, base, scale = 1) => {++ const q = async (where, params) => Math.round((await c(`SELECT ${agg} n ${base} AND ${where}`, [appId, ...params])) / scale);++ return {++ d: await q("created_at >= datetime('now', ?)", ['-1 day']),++ w: await q("created_at >= datetime('now', ?)", ['-7 days']),++ m: await q("created_at >= datetime('now', ?)", ['-30 days']),++ pw: await q("created_at >= datetime('now', ?) AND created_at < datetime('now', ?)", ['-14 days', '-7 days']),++ };++ };const vodBase = "FROM vods WHERE app_id = ? AND is_public = 1 AND COALESCE(is_recording,0) = 0 AND COALESCE(clips_only,0) = 0";const clipBase = "FROM clips WHERE app_id = ? AND COALESCE(is_public,1) = 1";return {@@@ -336,12 +342,10 @@ async function getAppStats(appId) {clips: await c(`SELECT COUNT(*) n ${clipBase}`, [appId]),durationSeconds: await c(`SELECT COALESCE(SUM(duration_seconds),0) n ${vodBase}`, [appId]),recent: {-- vods: await win(`SELECT COUNT(*) n ${vodBase} AND created_at >= datetime('now', ?)`, [appId]),-- clips: await win(`SELECT COUNT(*) n ${clipBase} AND created_at >= datetime('now', ?)`, [appId]),-- hours: (async () => {-- const w = await win(`SELECT COALESCE(SUM(duration_seconds),0) n ${vodBase} AND created_at >= datetime('now', ?)`, [appId]);-- return { d: Math.round(w.d / 3600), w: Math.round(w.w / 3600), m: Math.round(w.m / 3600) };-- })(),++ vods: await win('COUNT(*)', vodBase),++ clips: await win('COUNT(*)', clipBase),++ // Hours archived. This was an un-awaited async function, so the stats answered `hours: {}`.++ hours: await win('COALESCE(SUM(duration_seconds),0)', vodBase, 3600),},};}nnew file mode 100644@@@ -0,0 +1,33 @@++'use strict';++// An app's stats (GET /api/v1/:app/stats, db.getAppStats): the home page counters and their rolling windows. Hours++// archived over a window used to be an un-awaited async function, so the JSON answered `hours: {}` and Live's++// "Hours archived" never showed a change; each window also carries `pw`, the seven days before the last seven.++const assert = require('assert');++const fs = require('fs');++const os = require('os');++const path = require('path');++++(async () => {++ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-media-app-stats-'));++ const dir = (n) => { const d = path.join(tmp, n); fs.mkdirSync(d, { recursive: true }); return d; };++ Object.assign(process.env, {++ VOD_PATH: dir('vods'), CLIPS_PATH: dir('clips'), FILES_PATH: dir('files'), THUMBNAILS_PATH: dir('thumbnails'),++ PASTES_PATH: dir('pastes'), OBJECTS_PATH: dir('objects'), MEDIA_PUBLIC_URL: 'https://media.test',++ });++ const db = require('../server/db/database');++ const d = db.getDb();++ const V = d.prepare(`INSERT INTO vods (id, app_id, file_path, file_size, duration_seconds, visibility, is_public, storage_provider, created_at) OVERRIDING SYSTEM VALUE++ VALUES (?, 'live', ?, 100, ?, 'public', 1, 'local', datetime('now', ?))`);++ // Two hours today, three hours ten days ago, one an hour long 40 days ago.++ await V.run(9101, '/x/9101.webm', 7200, '-1 hours');++ await V.run(9102, '/x/9102.webm', 10800, '-10 days');++ await V.run(9103, '/x/9103.webm', 3600, '-40 days');++++ const s = JSON.parse(JSON.stringify(await db.getAppStats('live')));++ assert.strictEqual(Number(s.vods), 3);++ assert.deepStrictEqual(s.recent.hours, { d: 2, w: 2, m: 5, pw: 3 }, 'hours archived answer every window, as numbers');++ assert.deepStrictEqual(s.recent.vods, { d: 1, w: 1, m: 2, pw: 1 });++ assert.deepStrictEqual(s.recent.clips, { d: 0, w: 0, m: 0, pw: 0 });++ console.log(' ✓ an app\'s stats answer hours archived per window, and the week before');++ process.exit(0);++})().catch((err) => { console.error(err); process.exit(1); });
@@ -324,11 +324,17 @@ async function latestVodThumbsByManagedStreams(appId, managedStreamIds) {
@@ -324,11 +324,17 @@ async function latestVodThumbsByManagedStreams(appId, managedStreamIds) {
/** Aggregate per-app media stats for the owning app's dashboards/heroes. */
/** Aggregate per-app media stats for the owning app's dashboards/heroes. */
async function getAppStats(appId) {
async function getAppStats(appId) {
const c = async (sql, p = []) => { try { return (await get(sql, p))?.n || 0; } catch { return 0; } };
const c = async (sql, p = []) => { try { return (await get(sql, p))?.n || 0; } catch { return 0; } };
- const win = async (sql, extraParams = []) => ({
- d: await c(sql, [...extraParams, '-1 day']),
- w: await c(sql, [...extraParams, '-7 days']),
- m: await c(sql, [...extraParams, '-30 days']),
- });
+ // Rolling day/week/month figures, and `pw`: the seven days before the last seven, so a reader can say whether
+ // this week beat the last one.
+ const win = async (agg, base, scale = 1) => {
+ const q = async (where, params) => Math.round((await c(`SELECT ${agg} n ${base} AND ${where}`, [appId, ...params])) / scale);
+ return {
+ d: await q("created_at >= datetime('now', ?)", ['-1 day']),
+ w: await q("created_at >= datetime('now', ?)", ['-7 days']),
+ m: await q("created_at >= datetime('now', ?)", ['-30 days']),
+ pw: await q("created_at >= datetime('now', ?) AND created_at < datetime('now', ?)", ['-14 days', '-7 days']),
+ };
+ };
const vodBase = "FROM vods WHERE app_id = ? AND is_public = 1 AND COALESCE(is_recording,0) = 0 AND COALESCE(clips_only,0) = 0";
const vodBase = "FROM vods WHERE app_id = ? AND is_public = 1 AND COALESCE(is_recording,0) = 0 AND COALESCE(clips_only,0) = 0";
const clipBase = "FROM clips WHERE app_id = ? AND COALESCE(is_public,1) = 1";
const clipBase = "FROM clips WHERE app_id = ? AND COALESCE(is_public,1) = 1";
return {
return {
@@ -336,12 +342,10 @@ async function getAppStats(appId) {
@@ -336,12 +342,10 @@ async function getAppStats(appId) {
clips: await c(`SELECT COUNT(*) n ${clipBase}`, [appId]),
clips: await c(`SELECT COUNT(*) n ${clipBase}`, [appId]),
durationSeconds: await c(`SELECT COALESCE(SUM(duration_seconds),0) n ${vodBase}`, [appId]),
durationSeconds: await c(`SELECT COALESCE(SUM(duration_seconds),0) n ${vodBase}`, [appId]),
recent: {
recent: {
- vods: await win(`SELECT COUNT(*) n ${vodBase} AND created_at >= datetime('now', ?)`, [appId]),
- clips: await win(`SELECT COUNT(*) n ${clipBase} AND created_at >= datetime('now', ?)`, [appId]),
- hours: (async () => {
- const w = await win(`SELECT COALESCE(SUM(duration_seconds),0) n ${vodBase} AND created_at >= datetime('now', ?)`, [appId]);
- return { d: Math.round(w.d / 3600), w: Math.round(w.w / 3600), m: Math.round(w.m / 3600) };
- })(),
+ vods: await win('COUNT(*)', vodBase),
+ clips: await win('COUNT(*)', clipBase),
+ // Hours archived. This was an un-awaited async function, so the stats answered `hours: {}`.
+ hours: await win('COALESCE(SUM(duration_seconds),0)', vodBase, 3600),
},
},
};
};
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
+'use strict';
+// An app's stats (GET /api/v1/:app/stats, db.getAppStats): the home page counters and their rolling windows. Hours
+// archived over a window used to be an un-awaited async function, so the JSON answered `hours: {}` and Live's
+// "Hours archived" never showed a change; each window also carries `pw`, the seven days before the last seven.
+const assert = require('assert');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+
+(async () => {
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-media-app-stats-'));
+ const dir = (n) => { const d = path.join(tmp, n); fs.mkdirSync(d, { recursive: true }); return d; };
+ Object.assign(process.env, {
+ VOD_PATH: dir('vods'), CLIPS_PATH: dir('clips'), FILES_PATH: dir('files'), THUMBNAILS_PATH: dir('thumbnails'),
+ PASTES_PATH: dir('pastes'), OBJECTS_PATH: dir('objects'), MEDIA_PUBLIC_URL: 'https://media.test',
+ });
+ const db = require('../server/db/database');
+ const d = db.getDb();
+ const V = d.prepare(`INSERT INTO vods (id, app_id, file_path, file_size, duration_seconds, visibility, is_public, storage_provider, created_at) OVERRIDING SYSTEM VALUE
+ VALUES (?, 'live', ?, 100, ?, 'public', 1, 'local', datetime('now', ?))`);
+ // Two hours today, three hours ten days ago, one an hour long 40 days ago.
+ await V.run(9101, '/x/9101.webm', 7200, '-1 hours');
+ await V.run(9102, '/x/9102.webm', 10800, '-10 days');
+ await V.run(9103, '/x/9103.webm', 3600, '-40 days');
+
+ const s = JSON.parse(JSON.stringify(await db.getAppStats('live')));
+ assert.strictEqual(Number(s.vods), 3);
+ assert.deepStrictEqual(s.recent.hours, { d: 2, w: 2, m: 5, pw: 3 }, 'hours archived answer every window, as numbers');
+ assert.deepStrictEqual(s.recent.vods, { d: 1, w: 1, m: 2, pw: 1 });
+ assert.deepStrictEqual(s.recent.clips, { d: 0, w: 0, m: 0, pw: 0 });
+ console.log(' ✓ an app\'s stats answer hours archived per window, and the week before');
+ process.exit(0);
+})().catch((err) => { console.error(err); process.exit(1); });