From f10449349de2df4678207cfe37dc46e5d781a75d Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Wed, 8 Jul 2026 02:00:35 -0700 Subject: [PATCH] feat(observability): add issue-activity stat panels to the maintainer PR dashboard Adds Issues opened/closed/open stat panels to maintainer-reviews.json, using the grafana-github-datasource live-API pattern already proven in github-prs.json's "Open issues" panel rather than the local webhook-observed issues table -- upstream-accurate regardless of local sync gaps, at the cost of a different consistency model from the review_targets-backed PR panels on the same dashboard. Hardcodes org:JSONbored matching the existing precedent in github-prs.json's own $scope variable, since this dashboard has no per-repo selector. --- grafana/dashboards/maintainer-reviews.json | 48 ++++++++++++++++++-- test/unit/selfhost-grafana-dashboard.test.ts | 30 ++++++++++++ 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/grafana/dashboards/maintainer-reviews.json b/grafana/dashboards/maintainer-reviews.json index 68bd54002b..5d0b338a00 100644 --- a/grafana/dashboards/maintainer-reviews.json +++ b/grafana/dashboards/maintainer-reviews.json @@ -4,7 +4,7 @@ "tags": ["gittensory", "maintainer"], "timezone": "browser", "schemaVersion": 39, - "version": 5, + "version": 6, "editable": false, "graphTooltip": 1, "refresh": "1m", @@ -83,12 +83,52 @@ "options": { "colorMode": "background", "graphMode": "none", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "textMode": "value" }, "targets": [{ "datasource": { "type": "frser-sqlite-datasource", "uid": "gittensory-db" }, "refId": "A", "queryType": "table", "queryText": "SELECT count(*) AS ignored FROM review_targets WHERE (status='ignored' OR verdict='ignore') AND unixepoch(updated_at) >= ${__from:date:seconds} AND unixepoch(updated_at) < ${__to:date:seconds}", "rawQueryText": "SELECT count(*) AS ignored FROM review_targets WHERE (status='ignored' OR verdict='ignore') AND unixepoch(updated_at) >= ${__from:date:seconds} AND unixepoch(updated_at) < ${__to:date:seconds}" }] }, + { + "type": "row", + "id": 11, + "title": "Issue activity", + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 5 } + }, + { + "type": "stat", + "id": 12, + "title": "Issues opened", + "description": "Live GitHub-API count via the grafana-github-datasource (org:JSONbored is:issue), not the local webhook-observed issues table -- upstream-accurate regardless of local sync gaps, at the cost of a different consistency model from the review_targets-backed PR panels above. See #3716 for the tradeoff.", + "datasource": { "type": "grafana-github-datasource", "uid": "github" }, + "gridPos": { "h": 4, "w": 8, "x": 0, "y": 6 }, + "fieldConfig": { "defaults": { "color": { "mode": "fixed", "fixedColor": "blue" } } }, + "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "center", "reduceOptions": { "calcs": ["count"], "fields": "/^number$/" } }, + "targets": [{ "refId": "A", "queryType": "Issues", "owner": "", "repository": "", "options": { "query": "org:JSONbored is:issue", "timeField": 1 } }] + }, + { + "type": "stat", + "id": 13, + "title": "Issues closed", + "description": "Live GitHub-API count via the grafana-github-datasource (org:JSONbored is:issue is:closed), not the local webhook-observed issues table. See #3716 for the tradeoff.", + "datasource": { "type": "grafana-github-datasource", "uid": "github" }, + "gridPos": { "h": 4, "w": 8, "x": 8, "y": 6 }, + "fieldConfig": { "defaults": { "color": { "mode": "fixed", "fixedColor": "red" } } }, + "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "center", "reduceOptions": { "calcs": ["count"], "fields": "/^number$/" } }, + "targets": [{ "refId": "A", "queryType": "Issues", "owner": "", "repository": "", "options": { "query": "org:JSONbored is:issue is:closed", "timeField": 0 } }] + }, + { + "type": "stat", + "id": 14, + "title": "Issues open", + "description": "Current open-issue backlog (org:JSONbored is:issue is:open) -- a state snapshot, not filtered by the dashboard time window, mirroring github-prs.json's own 'Open issues' panel.", + "datasource": { "type": "grafana-github-datasource", "uid": "github" }, + "gridPos": { "h": 4, "w": 8, "x": 16, "y": 6 }, + "fieldConfig": { "defaults": { "color": { "mode": "fixed", "fixedColor": "orange" } } }, + "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "center", "reduceOptions": { "calcs": ["count"], "fields": "/^number$/" } }, + "targets": [{ "refId": "A", "queryType": "Issues", "owner": "", "repository": "", "options": { "query": "org:JSONbored is:issue is:open" } }] + }, { "type": "table", "id": 8, "title": "Pull requests (latest 1000)", "datasource": { "type": "frser-sqlite-datasource", "uid": "gittensory-db" }, - "gridPos": { "h": 16, "w": 24, "x": 0, "y": 5 }, + "gridPos": { "h": 16, "w": 24, "x": 0, "y": 10 }, "fieldConfig": { "defaults": { "custom": { "align": "auto", "cellOptions": { "type": "auto" }, "filterable": true, "inspect": false } }, "overrides": [ @@ -122,7 +162,7 @@ "id": 9, "title": "Reviews per day", "datasource": { "type": "frser-sqlite-datasource", "uid": "gittensory-db" }, - "gridPos": { "h": 8, "w": 12, "x": 0, "y": 21 }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 26 }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "drawStyle": "bars", "fillOpacity": 60, "lineWidth": 1, "showPoints": "never", "stacking": { "mode": "none" } }, "unit": "short" }, "overrides": [] }, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": false }, "tooltip": { "mode": "single", "sort": "none" } }, "targets": [{ "datasource": { "type": "frser-sqlite-datasource", "uid": "gittensory-db" }, "refId": "A", "queryType": "time series", "timeColumns": ["time"], "queryText": "SELECT date(updated_at) AS time, count(*) AS reviews FROM review_targets WHERE unixepoch(updated_at) >= ${__from:date:seconds} AND unixepoch(updated_at) < ${__to:date:seconds} GROUP BY date(updated_at) ORDER BY time", "rawQueryText": "SELECT date(updated_at) AS time, count(*) AS reviews FROM review_targets WHERE unixepoch(updated_at) >= ${__from:date:seconds} AND unixepoch(updated_at) < ${__to:date:seconds} GROUP BY date(updated_at) ORDER BY time" }] @@ -132,7 +172,7 @@ "id": 10, "title": "By verdict", "datasource": { "type": "frser-sqlite-datasource", "uid": "gittensory-db" }, - "gridPos": { "h": 8, "w": 12, "x": 12, "y": 21 }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 26 }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "unit": "short" }, "overrides": [] }, "options": { "legend": { "displayMode": "list", "placement": "right", "showLegend": true, "values": ["value", "percent"] }, "pieType": "donut", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": true }, "tooltip": { "mode": "single", "sort": "none" } }, "targets": [{ "datasource": { "type": "frser-sqlite-datasource", "uid": "gittensory-db" }, "refId": "A", "queryType": "table", "queryText": "SELECT verdict, count(*) AS c FROM review_targets WHERE verdict IS NOT NULL AND unixepoch(updated_at) >= ${__from:date:seconds} AND unixepoch(updated_at) < ${__to:date:seconds} GROUP BY verdict ORDER BY c DESC", "rawQueryText": "SELECT verdict, count(*) AS c FROM review_targets WHERE verdict IS NOT NULL AND unixepoch(updated_at) >= ${__from:date:seconds} AND unixepoch(updated_at) < ${__to:date:seconds} GROUP BY verdict ORDER BY c DESC" }] diff --git a/test/unit/selfhost-grafana-dashboard.test.ts b/test/unit/selfhost-grafana-dashboard.test.ts index fe8f5099cf..78cb96a31c 100644 --- a/test/unit/selfhost-grafana-dashboard.test.ts +++ b/test/unit/selfhost-grafana-dashboard.test.ts @@ -11,6 +11,8 @@ type DashboardTarget = { rawQueryText?: string; format?: string; instant?: boolean; + queryType?: string; + options?: { query?: string; timeField?: number }; }; type DashboardPanel = { @@ -18,6 +20,7 @@ type DashboardPanel = { title?: string; description?: string; targets?: DashboardTarget[]; + datasource?: { type?: string; uid?: string }; }; type Dashboard = { @@ -271,6 +274,33 @@ describe("maintainer Reviews & PRs Grafana dashboard", () => { } }); + it("adds GitHub-API-backed issue-activity stat panels alongside the review_targets PR panels (#3716)", () => { + const dashboard = readDashboard(); + const panelsById = new Map(dashboard.panels.map((panel) => [panel.id, panel])); + + for (const [id, title, expectedQuery] of [ + [12, "Issues opened", "org:JSONbored is:issue"], + [13, "Issues closed", "org:JSONbored is:issue is:closed"], + [14, "Issues open", "org:JSONbored is:issue is:open"], + ] as const) { + const panel = panelsById.get(id); + expect(panel?.title).toBe(title); + expect(panel?.datasource?.type).toBe("grafana-github-datasource"); + const target = panel?.targets?.[0]; + expect(target?.queryType).toBe("Issues"); + expect(target?.options?.query).toBe(expectedQuery); + // A live GitHub-API source (not review_targets) — never asserted to be time-window bound the way + // the SQL panels above are; "Issues open" is deliberately a state snapshot with no timeField at all. + expect(panel?.description?.length ?? 0).toBeGreaterThan(0); + } + + // "Issues open" is a current-state snapshot (mirrors github-prs.json's own "Open issues" panel) — + // no timeField, unlike the opened/closed flow panels. + expect(panelsById.get(14)?.targets?.[0]?.options?.timeField).toBeUndefined(); + expect(panelsById.get(12)?.targets?.[0]?.options?.timeField).toBe(1); + expect(panelsById.get(13)?.targets?.[0]?.options?.timeField).toBe(0); + }); + (sqliteCliAvailable ? it : it.skip)("filters the pull request table to the selected time window", () => { const root = tmpRoot(); const db = join(root, "reporting.sqlite");