From 0816f8fa9ed04142deccdb4f96380edff732b3ad Mon Sep 17 00:00:00 2001 From: borja Date: Sat, 22 Nov 2025 13:48:37 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20a=C3=B1ade=20m=C3=A9tricas=20de=20grupo?= =?UTF-8?q?s/usuarios=20y=20paneles=20en=20Grafana?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: aider (openrouter/openai/gpt-5.1) --- docs/grafana/taskbot-metrics.json | 384 +++++++++++++++++++++++++++++- src/http/metrics.ts | 64 +++++ 2 files changed, 447 insertions(+), 1 deletion(-) diff --git a/docs/grafana/taskbot-metrics.json b/docs/grafana/taskbot-metrics.json index a527bec..a0b6d48 100644 --- a/docs/grafana/taskbot-metrics.json +++ b/docs/grafana/taskbot-metrics.json @@ -482,6 +482,388 @@ ], "title": "Oldest pending response age (s)", "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 8, + "panels": [], + "title": "Groups & Users", + "type": "row" + }, + { + "datasource": { + "type": "marcusolsson-json-datasource", + "uid": "bf43dd67r9slcd" + }, + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 0, + "y": 6 + }, + "id": 9, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "titleSize": 24 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.2.1", + "targets": [ + { + "cacheDurationSeconds": 300, + "datasource": { + "type": "marcusolsson-json-datasource", + "uid": "bf43dd67r9slcd" + }, + "fields": [ + { + "jsonPath": "$.gauges.groups_total", + "name": "groups_total", + "type": "number" + } + ], + "method": "GET", + "queryParams": "", + "refId": "A", + "urlPath": "" + } + ], + "title": "Groups total (non-community)", + "type": "stat" + }, + { + "datasource": { + "type": "marcusolsson-json-datasource", + "uid": "bf43dd67r9slcd" + }, + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 4, + "y": 6 + }, + "id": 10, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "titleSize": 24 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.2.1", + "targets": [ + { + "cacheDurationSeconds": 300, + "datasource": { + "type": "marcusolsson-json-datasource", + "uid": "bf43dd67r9slcd" + }, + "fields": [ + { + "jsonPath": "$.gauges.groups_active_total", + "name": "groups_active_total", + "type": "number" + } + ], + "method": "GET", + "queryParams": "", + "refId": "A", + "urlPath": "" + } + ], + "title": "Groups active", + "type": "stat" + }, + { + "datasource": { + "type": "marcusolsson-json-datasource", + "uid": "bf43dd67r9slcd" + }, + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "gray", + "value": 0 + }, + { + "color": "orange", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 8, + "y": 6 + }, + "id": 11, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "titleSize": 24 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.2.1", + "targets": [ + { + "cacheDurationSeconds": 300, + "datasource": { + "type": "marcusolsson-json-datasource", + "uid": "bf43dd67r9slcd" + }, + "fields": [ + { + "jsonPath": "$.gauges.groups_archived_total", + "name": "groups_archived_total", + "type": "number" + } + ], + "method": "GET", + "queryParams": "", + "refId": "A", + "urlPath": "" + } + ], + "title": "Groups archived", + "type": "stat" + }, + { + "datasource": { + "type": "marcusolsson-json-datasource", + "uid": "bf43dd67r9slcd" + }, + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 12, + "y": 6 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "titleSize": 24 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.2.1", + "targets": [ + { + "cacheDurationSeconds": 300, + "datasource": { + "type": "marcusolsson-json-datasource", + "uid": "bf43dd67r9slcd" + }, + "fields": [ + { + "jsonPath": "$.gauges.group_members_total", + "name": "group_members_total", + "type": "number" + } + ], + "method": "GET", + "queryParams": "", + "refId": "A", + "urlPath": "" + } + ], + "title": "Group members (active / active groups)", + "type": "stat" + }, + { + "datasource": { + "type": "marcusolsson-json-datasource", + "uid": "bf43dd67r9slcd" + }, + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 16, + "y": 6 + }, + "id": 13, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "titleSize": 24 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.2.1", + "targets": [ + { + "cacheDurationSeconds": 300, + "datasource": { + "type": "marcusolsson-json-datasource", + "uid": "bf43dd67r9slcd" + }, + "fields": [ + { + "jsonPath": "$.gauges.users_total", + "name": "users_total", + "type": "number" + } + ], + "method": "GET", + "queryParams": "", + "refId": "A", + "urlPath": "" + } + ], + "title": "Users total", + "type": "stat" } ], "preload": false, @@ -502,6 +884,6 @@ "timezone": "browser", "title": "Taskbot - Tasks & Queue (JSON API)", "uid": "taskbot-tasks-queue-json", - "version": 1, + "version": 2, "weekStart": "monday" } diff --git a/src/http/metrics.ts b/src/http/metrics.ts index 8a2af1c..c0f9280 100644 --- a/src/http/metrics.ts +++ b/src/http/metrics.ts @@ -28,6 +28,70 @@ export async function handleMetricsRequest(request: Request, db: Database): Prom Metrics.set('allowed_groups_total_blocked', blocked); } catch {} + // Métricas de grupos y usuarios (gauges derivadas desde BD) + try { + // Grupos: totales, activos y archivados (siempre excluyendo comunidades) + const groupRow = db + .prepare(` + SELECT + COUNT(*) AS total, + SUM( + CASE + WHEN COALESCE(active, 0) = 1 + AND COALESCE(archived, 0) = 0 + THEN 1 ELSE 0 + END + ) AS active, + SUM( + CASE + WHEN COALESCE(archived, 0) = 1 + THEN 1 ELSE 0 + END + ) AS archived + FROM groups + WHERE COALESCE(is_community, 0) = 0; + `) + .get() as any; + + if (groupRow) { + const groupsTotal = Number(groupRow.total ?? 0); + const groupsActive = Number(groupRow.active ?? 0); + const groupsArchived = Number(groupRow.archived ?? 0); + + Metrics.set('groups_total', groupsTotal); + Metrics.set('groups_active_total', groupsActive); + Metrics.set('groups_archived_total', groupsArchived); + } + + // Miembros de grupos: solo miembros activos en grupos activos, no comunidad, no archivados + const gmRow = db + .prepare(` + SELECT COUNT(*) AS total + FROM group_members gm + JOIN groups g ON g.id = gm.group_id + WHERE gm.is_active = 1 + AND COALESCE(g.active, 0) = 1 + AND COALESCE(g.is_community, 0) = 0 + AND COALESCE(g.archived, 0) = 0; + `) + .get() as any; + + if (gmRow) { + const gmTotal = Number(gmRow.total ?? 0); + Metrics.set('group_members_total', gmTotal); + } + + // Usuarios totales + const usersRow = db + .prepare(`SELECT COUNT(*) AS total FROM users;`) + .get() as any; + + if (usersRow) { + const usersTotal = Number(usersRow.total ?? 0); + Metrics.set('users_total', usersTotal); + } + } catch {} + // Métricas de tareas (gauges derivadas desde BD) try { const row = db