From 80e9b5883da23186246aaded2d2bd0e364a13808 Mon Sep 17 00:00:00 2001 From: Thomas Schmelzer Date: Sun, 30 Aug 2026 10:56:52 +0400 Subject: [PATCH 1/4] Demote template drift to the bottom of the board Template drift is a rhiza concern, and most people looking at this board do not use the template at all. It was sitting a third of the way down, directly under the trend chart, with its headline count in the top row of tiles between "CI red on main" and "PRs with red checks" - the most prominent spot on the page. That ordering asserts the number matters to the reader, and for most readers it does not. The section now comes last on both boards, and "Behind template" moves out of the header to sit above the bargauge and the pinned-ref table as that section's own headline. The five remaining tiles widen to 5/5/5/5/4 so the header still fills the grid rather than trailing a gap where the sixth used to be. make-public-dashboard.py needed the same edit: its LAYOUT dict positions the header tiles by title, so leaving "Behind template" listed there would have pinned it back into the header on the public copy no matter where fleet.json put it. Dropping it from LAYOUT lets it keep its order from the source instead. Co-Authored-By: Claude Opus 5 (1M context) --- grafana/dashboards/fleet-public.json | 706 +++++++++++++-------------- grafana/dashboards/fleet.json | 698 +++++++++++++------------- scripts/make-public-dashboard.py | 14 +- 3 files changed, 711 insertions(+), 707 deletions(-) diff --git a/grafana/dashboards/fleet-public.json b/grafana/dashboards/fleet-public.json index 6661d6c..4a67720 100644 --- a/grafana/dashboards/fleet-public.json +++ b/grafana/dashboards/fleet-public.json @@ -40,7 +40,7 @@ "gridPos": { "x": 0, "y": 1, - "w": 4, + "w": 5, "h": 5 }, "datasource": { @@ -84,9 +84,9 @@ "type": "stat", "title": "Open PRs", "gridPos": { - "x": 4, + "x": 5, "y": 1, - "w": 4, + "w": 5, "h": 5 }, "datasource": { @@ -131,9 +131,9 @@ "type": "stat", "title": "Open issues", "gridPos": { - "x": 8, + "x": 10, "y": 1, - "w": 4, + "w": 5, "h": 5 }, "datasource": { @@ -179,9 +179,9 @@ "title": "CI red on main", "description": "Repos whose last completed run on the default branch did not pass.", "gridPos": { - "x": 12, + "x": 15, "y": 1, - "w": 4, + "w": 5, "h": 5 }, "datasource": { @@ -234,70 +234,6 @@ }, "id": 3 }, - { - "type": "stat", - "title": "Behind template", - "description": "Repos pinned to a template release older than the newest one.", - "gridPos": { - "x": 16, - "y": 1, - "w": 4, - "h": 5 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "sum(jq_rhiza_releases_behind > bool 0)", - "instant": true - } - ], - "options": { - "colorMode": "value", - "graphMode": "area", - "textMode": "value", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "justifyMode": "auto", - "text": { - "valueSize": 48, - "titleSize": 13 - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "decimals": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#0ca30c", - "value": null - }, - { - "color": "#fab219", - "value": 1 - }, - { - "color": "#ec835a", - "value": 5 - } - ] - } - }, - "overrides": [] - }, - "id": 4 - }, { "type": "stat", "title": "PRs with red checks", @@ -525,7 +461,7 @@ }, { "type": "row", - "title": "Template drift", + "title": "Detail", "gridPos": { "h": 1, "w": 24, @@ -533,16 +469,15 @@ "y": 18 }, "collapsed": false, - "id": 8, + "id": 14, "panels": [] }, { - "type": "bargauge", - "title": "Template releases behind", - "description": "How many releases were published after each repo's pinned ref. Repos pinned to a branch or a sha are absent - the pin is not comparable to a release.", + "type": "table", + "title": "Last completed run on the default branch", "gridPos": { - "h": 13, - "w": 10, + "h": 14, + "w": 24, "x": 0, "y": 19 }, @@ -553,88 +488,19 @@ "targets": [ { "refId": "A", - "expr": "sort_desc(jq_rhiza_releases_behind)", + "expr": "jq_ci_last_run_info", "instant": true, - "legendFormat": "{{repo}}" - } - ], - "options": { - "orientation": "horizontal", - "displayMode": "basic", - "valueMode": "text", - "showUnfilled": true, - "minVizWidth": 8, - "minVizHeight": 14, - "namePlacement": "left", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "decimals": 0, - "min": 0, - "color": { - "mode": "thresholds" - }, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#184f95", - "value": null - }, - { - "color": "#256abf", - "value": 1 - }, - { - "color": "#3987e5", - "value": 2 - }, - { - "color": "#6da7ec", - "value": 3 - }, - { - "color": "#9ec5f4", - "value": 5 - } - ] - } + "format": "table" }, - "overrides": [] - }, - "id": 9 - }, - { - "type": "table", - "title": "Pinned ref per repo", - "gridPos": { - "h": 13, - "w": 14, - "x": 10, - "y": 19 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ { - "refId": "A", - "expr": "jq_rhiza_template_ref_info", + "refId": "B", + "expr": "time() - jq_ci_last_run_timestamp_seconds", "instant": true, "format": "table" }, { - "refId": "B", - "expr": "jq_rhiza_releases_behind", + "refId": "C", + "expr": "jq_ci_last_run_duration_seconds", "instant": true, "format": "table" } @@ -653,20 +519,27 @@ "excludeByName": { "Time 1": true, "Time 2": true, + "Time 3": true, "Value #A": true, "__name__ 1": true, "__name__ 2": true, + "__name__ 3": true, "instance 1": true, "instance 2": true, + "instance 3": true, "job 1": true, "job 2": true, + "job 3": true, "fleet 1": true, - "fleet 2": true + "fleet 2": true, + "fleet 3": true }, "renameByName": { "repo": "Repo", - "ref": "Pinned ref", - "Value #B": "Releases behind" + "conclusion": "Conclusion", + "workflow": "Workflow", + "Value #B": "Finished", + "Value #C": "Took" } } }, @@ -675,8 +548,8 @@ "options": { "sort": [ { - "field": "Releases behind", - "desc": true + "field": "Conclusion", + "desc": false } ] } @@ -701,209 +574,55 @@ { "matcher": { "id": "byName", - "options": "Releases behind" + "options": "Finished" }, "properties": [ + { + "id": "unit", + "value": "s" + }, { "id": "custom.align", "value": "right" }, { "id": "custom.width", - "value": 140 + "value": 130 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Took" + }, + "properties": [ + { + "id": "unit", + "value": "s" }, { - "id": "noValue", - "value": "not a release" + "id": "custom.align", + "value": "right" + }, + { + "id": "custom.width", + "value": 110 } ] }, { "matcher": { "id": "byName", - "options": "Repo" + "options": "Conclusion" }, "properties": [ { - "id": "links", - "value": [ - { - "title": "Open on GitHub", - "url": "https://github.com/${__value.text}", - "targetBlank": true - } - ] - } - ] - } - ] - }, - "id": 10 - }, - { - "type": "row", - "title": "Detail", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 32 - }, - "collapsed": false, - "id": 14, - "panels": [] - }, - { - "type": "table", - "title": "Last completed run on the default branch", - "gridPos": { - "h": 14, - "w": 24, - "x": 0, - "y": 33 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "jq_ci_last_run_info", - "instant": true, - "format": "table" - }, - { - "refId": "B", - "expr": "time() - jq_ci_last_run_timestamp_seconds", - "instant": true, - "format": "table" - }, - { - "refId": "C", - "expr": "jq_ci_last_run_duration_seconds", - "instant": true, - "format": "table" - } - ], - "transformations": [ - { - "id": "joinByField", - "options": { - "byField": "repo", - "mode": "outer" - } - }, - { - "id": "organize", - "options": { - "excludeByName": { - "Time 1": true, - "Time 2": true, - "Time 3": true, - "Value #A": true, - "__name__ 1": true, - "__name__ 2": true, - "__name__ 3": true, - "instance 1": true, - "instance 2": true, - "instance 3": true, - "job 1": true, - "job 2": true, - "job 3": true, - "fleet 1": true, - "fleet 2": true, - "fleet 3": true - }, - "renameByName": { - "repo": "Repo", - "conclusion": "Conclusion", - "workflow": "Workflow", - "Value #B": "Finished", - "Value #C": "Took" - } - } - }, - { - "id": "sortBy", - "options": { - "sort": [ - { - "field": "Conclusion", - "desc": false - } - ] - } - } - ], - "options": { - "showHeader": true, - "footer": { - "show": false - } - }, - "fieldConfig": { - "defaults": { - "custom": { - "align": "left", - "cellOptions": { - "type": "auto" - } - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Finished" - }, - "properties": [ - { - "id": "unit", - "value": "s" - }, - { - "id": "custom.align", - "value": "right" - }, - { - "id": "custom.width", - "value": 130 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Took" - }, - "properties": [ - { - "id": "unit", - "value": "s" - }, - { - "id": "custom.align", - "value": "right" - }, - { - "id": "custom.width", - "value": 110 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Conclusion" - }, - "properties": [ - { - "id": "custom.width", - "value": 150 - }, - { - "id": "mappings", + "id": "custom.width", + "value": 150 + }, + { + "id": "mappings", "value": [ { "type": "value", @@ -1004,7 +723,7 @@ "h": 12, "w": 24, "x": 0, - "y": 47 + "y": 33 }, "datasource": { "type": "prometheus", @@ -1259,7 +978,7 @@ "h": 11, "w": 24, "x": 0, - "y": 59 + "y": 45 }, "datasource": { "type": "prometheus", @@ -1403,7 +1122,7 @@ "h": 1, "w": 24, "x": 0, - "y": 70 + "y": 56 }, "collapsed": false, "panels": [] @@ -1417,7 +1136,7 @@ "h": 12, "w": 24, "x": 0, - "y": 71 + "y": 57 }, "datasource": { "type": "prometheus", @@ -1536,7 +1255,7 @@ "h": 12, "w": 24, "x": 0, - "y": 83 + "y": 69 }, "datasource": { "type": "prometheus", @@ -1652,7 +1371,7 @@ "h": 12, "w": 24, "x": 0, - "y": 95 + "y": 81 }, "datasource": { "type": "prometheus", @@ -1818,7 +1537,7 @@ "h": 12, "w": 24, "x": 0, - "y": 107 + "y": 93 }, "datasource": { "type": "prometheus", @@ -1929,7 +1648,7 @@ "h": 1, "w": 24, "x": 0, - "y": 119 + "y": 105 }, "collapsed": false, "id": 526, @@ -1942,7 +1661,7 @@ "h": 5, "w": 6, "x": 0, - "y": 120 + "y": 106 }, "datasource": { "type": "prometheus", @@ -1999,7 +1718,7 @@ "h": 5, "w": 6, "x": 6, - "y": 120 + "y": 106 }, "datasource": { "type": "prometheus", @@ -2055,7 +1774,7 @@ "h": 5, "w": 12, "x": 12, - "y": 120 + "y": 106 }, "datasource": { "type": "prometheus", @@ -2125,6 +1844,287 @@ ] }, "id": 529 + }, + { + "type": "row", + "title": "Template drift", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 111 + }, + "collapsed": false, + "id": 8, + "panels": [] + }, + { + "type": "stat", + "title": "Behind template", + "description": "Repos pinned to a template release older than the newest one.", + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 112 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "sum(jq_rhiza_releases_behind > bool 0)", + "instant": true + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "textMode": "value", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "justifyMode": "auto", + "text": { + "valueSize": 48, + "titleSize": 13 + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "decimals": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#0ca30c", + "value": null + }, + { + "color": "#fab219", + "value": 1 + }, + { + "color": "#ec835a", + "value": 5 + } + ] + } + }, + "overrides": [] + }, + "id": 4 + }, + { + "type": "bargauge", + "title": "Template releases behind", + "description": "How many releases were published after each repo's pinned ref. Repos pinned to a branch or a sha are absent - the pin is not comparable to a release.", + "gridPos": { + "h": 13, + "w": 10, + "x": 0, + "y": 116 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "sort_desc(jq_rhiza_releases_behind)", + "instant": true, + "legendFormat": "{{repo}}" + } + ], + "options": { + "orientation": "horizontal", + "displayMode": "basic", + "valueMode": "text", + "showUnfilled": true, + "minVizWidth": 8, + "minVizHeight": 14, + "namePlacement": "left", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "decimals": 0, + "min": 0, + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#184f95", + "value": null + }, + { + "color": "#256abf", + "value": 1 + }, + { + "color": "#3987e5", + "value": 2 + }, + { + "color": "#6da7ec", + "value": 3 + }, + { + "color": "#9ec5f4", + "value": 5 + } + ] + } + }, + "overrides": [] + }, + "id": 9 + }, + { + "type": "table", + "title": "Pinned ref per repo", + "gridPos": { + "h": 13, + "w": 14, + "x": 10, + "y": 116 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "jq_rhiza_template_ref_info", + "instant": true, + "format": "table" + }, + { + "refId": "B", + "expr": "jq_rhiza_releases_behind", + "instant": true, + "format": "table" + } + ], + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "repo", + "mode": "outer" + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time 1": true, + "Time 2": true, + "Value #A": true, + "__name__ 1": true, + "__name__ 2": true, + "instance 1": true, + "instance 2": true, + "job 1": true, + "job 2": true, + "fleet 1": true, + "fleet 2": true + }, + "renameByName": { + "repo": "Repo", + "ref": "Pinned ref", + "Value #B": "Releases behind" + } + } + }, + { + "id": "sortBy", + "options": { + "sort": [ + { + "field": "Releases behind", + "desc": true + } + ] + } + } + ], + "options": { + "showHeader": true, + "footer": { + "show": false + } + }, + "fieldConfig": { + "defaults": { + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Releases behind" + }, + "properties": [ + { + "id": "custom.align", + "value": "right" + }, + { + "id": "custom.width", + "value": 140 + }, + { + "id": "noValue", + "value": "not a release" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Repo" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Open on GitHub", + "url": "https://github.com/${__value.text}", + "targetBlank": true + } + ] + } + ] + } + ] + }, + "id": 10 } ] } diff --git a/grafana/dashboards/fleet.json b/grafana/dashboards/fleet.json index 69db647..d7fa00d 100644 --- a/grafana/dashboards/fleet.json +++ b/grafana/dashboards/fleet.json @@ -57,7 +57,7 @@ "title": "Repos monitored", "gridPos": { "h": 5, - "w": 4, + "w": 5, "x": 0, "y": 1 }, @@ -98,8 +98,8 @@ "title": "Open PRs", "gridPos": { "h": 5, - "w": 4, - "x": 4, + "w": 5, + "x": 5, "y": 1 }, "datasource": { @@ -147,8 +147,8 @@ "title": "Open issues", "gridPos": { "h": 5, - "w": 4, - "x": 8, + "w": 5, + "x": 10, "y": 1 }, "datasource": { @@ -197,8 +197,8 @@ "description": "Repos whose last completed run on the default branch did not pass.", "gridPos": { "h": 5, - "w": 4, - "x": 12, + "w": 5, + "x": 15, "y": 1 }, "datasource": { @@ -253,72 +253,6 @@ }, "id": 3 }, - { - "type": "stat", - "title": "Behind template", - "description": "Repos pinned to a template release older than the newest one.", - "gridPos": { - "h": 5, - "w": 4, - "x": 16, - "y": 1 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "sum(jq_rhiza_releases_behind{repo=~\"$repo\"} > bool 0)", - "instant": true - } - ], - "options": { - "colorMode": "value", - "graphMode": "area", - "textMode": "value", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "decimals": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#0ca30c", - "value": null - }, - { - "color": "#fab219", - "value": 1 - }, - { - "color": "#ec835a", - "value": 5 - } - ] - }, - "links": [ - { - "title": "Show the repos behind this number", - "url": "/d/jq-fleet/jebel-quant-fleet?viewPanel=102&${__url_time_range}", - "targetBlank": false - } - ] - }, - "overrides": [] - }, - "id": 4 - }, { "type": "stat", "title": "PRs with red checks", @@ -749,7 +683,7 @@ }, { "type": "row", - "title": "Template drift", + "title": "Detail", "gridPos": { "h": 1, "w": 24, @@ -758,15 +692,14 @@ }, "collapsed": false, "panels": [], - "id": 8 + "id": 14 }, { - "type": "bargauge", - "title": "Template releases behind", - "description": "How many releases were published after each repo's pinned ref. Repos pinned to a branch or a sha are absent - the pin is not comparable to a release.", + "type": "table", + "title": "Last completed run on the default branch", "gridPos": { - "h": 13, - "w": 10, + "h": 14, + "w": 24, "x": 0, "y": 21 }, @@ -777,88 +710,19 @@ "targets": [ { "refId": "A", - "expr": "sort_desc(jq_rhiza_releases_behind{repo=~\"$repo\"})", + "expr": "jq_ci_last_run_info{repo=~\"$repo\"}", "instant": true, - "legendFormat": "{{repo}}" - } - ], - "options": { - "orientation": "horizontal", - "displayMode": "basic", - "valueMode": "text", - "showUnfilled": true, - "minVizWidth": 8, - "minVizHeight": 14, - "namePlacement": "left", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "decimals": 0, - "min": 0, - "color": { - "mode": "thresholds" - }, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#184f95", - "value": null - }, - { - "color": "#256abf", - "value": 1 - }, - { - "color": "#3987e5", - "value": 2 - }, - { - "color": "#6da7ec", - "value": 3 - }, - { - "color": "#9ec5f4", - "value": 5 - } - ] - } + "format": "table" }, - "overrides": [] - }, - "id": 9 - }, - { - "type": "table", - "title": "Pinned ref per repo", - "gridPos": { - "h": 13, - "w": 14, - "x": 10, - "y": 21 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ { - "refId": "A", - "expr": "jq_rhiza_template_ref_info{repo=~\"$repo\"}", + "refId": "B", + "expr": "time() - jq_ci_last_run_timestamp_seconds{repo=~\"$repo\"}", "instant": true, "format": "table" }, { - "refId": "B", - "expr": "jq_rhiza_releases_behind{repo=~\"$repo\"}", + "refId": "C", + "expr": "jq_ci_last_run_duration_seconds{repo=~\"$repo\"}", "instant": true, "format": "table" } @@ -877,20 +741,27 @@ "excludeByName": { "Time 1": true, "Time 2": true, + "Time 3": true, "Value #A": true, "__name__ 1": true, "__name__ 2": true, + "__name__ 3": true, "instance 1": true, "instance 2": true, + "instance 3": true, "job 1": true, "job 2": true, + "job 3": true, "fleet 1": true, - "fleet 2": true + "fleet 2": true, + "fleet 3": true }, "renameByName": { "repo": "Repo", - "ref": "Pinned ref", - "Value #B": "Releases behind" + "conclusion": "Conclusion", + "workflow": "Workflow", + "Value #B": "Finished", + "Value #C": "Took" } } }, @@ -899,8 +770,8 @@ "options": { "sort": [ { - "field": "Releases behind", - "desc": true + "field": "Conclusion", + "desc": false } ] } @@ -925,209 +796,55 @@ { "matcher": { "id": "byName", - "options": "Releases behind" + "options": "Finished" }, "properties": [ + { + "id": "unit", + "value": "s" + }, { "id": "custom.align", "value": "right" }, { "id": "custom.width", - "value": 140 + "value": 130 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Took" + }, + "properties": [ + { + "id": "unit", + "value": "s" }, { - "id": "noValue", - "value": "not a release" + "id": "custom.align", + "value": "right" + }, + { + "id": "custom.width", + "value": 110 } ] }, { "matcher": { "id": "byName", - "options": "Repo" + "options": "Conclusion" }, "properties": [ { - "id": "links", - "value": [ - { - "title": "Open on GitHub", - "url": "https://github.com/${__value.text}", - "targetBlank": true - } - ] - } - ] - } - ] - }, - "id": 10 - }, - { - "type": "row", - "title": "Detail", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 34 - }, - "collapsed": false, - "panels": [], - "id": 14 - }, - { - "type": "table", - "title": "Last completed run on the default branch", - "gridPos": { - "h": 14, - "w": 24, - "x": 0, - "y": 35 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "jq_ci_last_run_info{repo=~\"$repo\"}", - "instant": true, - "format": "table" - }, - { - "refId": "B", - "expr": "time() - jq_ci_last_run_timestamp_seconds{repo=~\"$repo\"}", - "instant": true, - "format": "table" - }, - { - "refId": "C", - "expr": "jq_ci_last_run_duration_seconds{repo=~\"$repo\"}", - "instant": true, - "format": "table" - } - ], - "transformations": [ - { - "id": "joinByField", - "options": { - "byField": "repo", - "mode": "outer" - } - }, - { - "id": "organize", - "options": { - "excludeByName": { - "Time 1": true, - "Time 2": true, - "Time 3": true, - "Value #A": true, - "__name__ 1": true, - "__name__ 2": true, - "__name__ 3": true, - "instance 1": true, - "instance 2": true, - "instance 3": true, - "job 1": true, - "job 2": true, - "job 3": true, - "fleet 1": true, - "fleet 2": true, - "fleet 3": true - }, - "renameByName": { - "repo": "Repo", - "conclusion": "Conclusion", - "workflow": "Workflow", - "Value #B": "Finished", - "Value #C": "Took" - } - } - }, - { - "id": "sortBy", - "options": { - "sort": [ - { - "field": "Conclusion", - "desc": false - } - ] - } - } - ], - "options": { - "showHeader": true, - "footer": { - "show": false - } - }, - "fieldConfig": { - "defaults": { - "custom": { - "align": "left", - "cellOptions": { - "type": "auto" - } - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Finished" - }, - "properties": [ - { - "id": "unit", - "value": "s" - }, - { - "id": "custom.align", - "value": "right" - }, - { - "id": "custom.width", - "value": 130 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Took" - }, - "properties": [ - { - "id": "unit", - "value": "s" - }, - { - "id": "custom.align", - "value": "right" - }, - { - "id": "custom.width", - "value": 110 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Conclusion" - }, - "properties": [ - { - "id": "custom.width", - "value": 150 - }, - { - "id": "mappings", + "id": "custom.width", + "value": 150 + }, + { + "id": "mappings", "value": [ { "type": "value", @@ -1228,7 +945,7 @@ "h": 12, "w": 24, "x": 0, - "y": 49 + "y": 35 }, "datasource": { "type": "prometheus", @@ -1483,7 +1200,7 @@ "h": 11, "w": 24, "x": 0, - "y": 61 + "y": 47 }, "datasource": { "type": "prometheus", @@ -1627,7 +1344,7 @@ "h": 16, "w": 24, "x": 0, - "y": 72 + "y": 58 }, "datasource": { "type": "prometheus", @@ -1925,7 +1642,7 @@ "h": 1, "w": 24, "x": 0, - "y": 88 + "y": 74 }, "collapsed": true, "panels": [ @@ -2785,7 +2502,7 @@ "h": 1, "w": 24, "x": 0, - "y": 89 + "y": 75 }, "collapsed": true, "panels": [ @@ -2982,6 +2699,289 @@ } ], "id": 526 + }, + { + "type": "row", + "title": "Template drift", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 76 + }, + "collapsed": false, + "panels": [], + "id": 8 + }, + { + "type": "stat", + "title": "Behind template", + "description": "Repos pinned to a template release older than the newest one.", + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 77 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "sum(jq_rhiza_releases_behind{repo=~\"$repo\"} > bool 0)", + "instant": true + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "textMode": "value", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "decimals": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#0ca30c", + "value": null + }, + { + "color": "#fab219", + "value": 1 + }, + { + "color": "#ec835a", + "value": 5 + } + ] + }, + "links": [ + { + "title": "Show the repos behind this number", + "url": "/d/jq-fleet/jebel-quant-fleet?viewPanel=102&${__url_time_range}", + "targetBlank": false + } + ] + }, + "overrides": [] + }, + "id": 4 + }, + { + "type": "bargauge", + "title": "Template releases behind", + "description": "How many releases were published after each repo's pinned ref. Repos pinned to a branch or a sha are absent - the pin is not comparable to a release.", + "gridPos": { + "h": 13, + "w": 10, + "x": 0, + "y": 81 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "sort_desc(jq_rhiza_releases_behind{repo=~\"$repo\"})", + "instant": true, + "legendFormat": "{{repo}}" + } + ], + "options": { + "orientation": "horizontal", + "displayMode": "basic", + "valueMode": "text", + "showUnfilled": true, + "minVizWidth": 8, + "minVizHeight": 14, + "namePlacement": "left", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "decimals": 0, + "min": 0, + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#184f95", + "value": null + }, + { + "color": "#256abf", + "value": 1 + }, + { + "color": "#3987e5", + "value": 2 + }, + { + "color": "#6da7ec", + "value": 3 + }, + { + "color": "#9ec5f4", + "value": 5 + } + ] + } + }, + "overrides": [] + }, + "id": 9 + }, + { + "type": "table", + "title": "Pinned ref per repo", + "gridPos": { + "h": 13, + "w": 14, + "x": 10, + "y": 81 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "jq_rhiza_template_ref_info{repo=~\"$repo\"}", + "instant": true, + "format": "table" + }, + { + "refId": "B", + "expr": "jq_rhiza_releases_behind{repo=~\"$repo\"}", + "instant": true, + "format": "table" + } + ], + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "repo", + "mode": "outer" + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time 1": true, + "Time 2": true, + "Value #A": true, + "__name__ 1": true, + "__name__ 2": true, + "instance 1": true, + "instance 2": true, + "job 1": true, + "job 2": true, + "fleet 1": true, + "fleet 2": true + }, + "renameByName": { + "repo": "Repo", + "ref": "Pinned ref", + "Value #B": "Releases behind" + } + } + }, + { + "id": "sortBy", + "options": { + "sort": [ + { + "field": "Releases behind", + "desc": true + } + ] + } + } + ], + "options": { + "showHeader": true, + "footer": { + "show": false + } + }, + "fieldConfig": { + "defaults": { + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Releases behind" + }, + "properties": [ + { + "id": "custom.align", + "value": "right" + }, + { + "id": "custom.width", + "value": 140 + }, + { + "id": "noValue", + "value": "not a release" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Repo" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Open on GitHub", + "url": "https://github.com/${__value.text}", + "targetBlank": true + } + ] + } + ] + } + ] + }, + "id": 10 } ] } diff --git a/scripts/make-public-dashboard.py b/scripts/make-public-dashboard.py index 3639332..fc7a8b8 100755 --- a/scripts/make-public-dashboard.py +++ b/scripts/make-public-dashboard.py @@ -75,12 +75,16 @@ def restyle(title, *, lit, unit=None): # Data age is a verdict: stale data invalidates everything above it. restyle("Data age", lit=True, unit="s") + # "Behind template" is deliberately absent: it lives in the Template drift + # section at the bottom, not in the header. Most people reading this board + # do not use the template at all, and a headline tile implies otherwise. + # Listing it here would pin it back into the header - `placed` titles are + # positioned absolutely, everything else keeps its order from fleet.json. LAYOUT = { - "Repos monitored": (0, 1, 4, 5), - "Open PRs": (4, 1, 4, 5), - "Open issues": (8, 1, 4, 5), - "CI red on main": (12, 1, 4, 5), - "Behind template": (16, 1, 4, 5), + "Repos monitored": (0, 1, 5, 5), + "Open PRs": (5, 1, 5, 5), + "Open issues": (10, 1, 5, 5), + "CI red on main": (15, 1, 5, 5), "PRs with red checks": (20, 1, 4, 5), "Data age": (0, 6, 24, 3), } From 022121e4e5371ec3658f60d1928b9c75360dca03 Mon Sep 17 00:00:00 2001 From: Thomas Schmelzer Date: Sun, 30 Aug 2026 11:00:05 +0400 Subject: [PATCH 2/4] Put Collector health directly under the trend chart It was second from the bottom, below every detail table, which is where you put something nobody needs to look at. But it answers the question that qualifies everything else on the page: is this data current? A red CI count is meaningless if the last GitHub refresh failed an hour ago, and the reader has no way to know that without scrolling past four tables to find out. It now sits directly under "Trend and CI history" - after the summary tiles and the trend, before the detail. On the laptop board it is a collapsed row, so the cost is one line of vertical space; on the public copy the generator expands it, so rate limit, time since last refresh and refresh duration are visible without scrolling to the end. Section order on both boards is now: Fleet at a glance, Trend and CI history, Collector health, Detail, the per-repo drill-down, Template drift. Co-Authored-By: Claude Opus 5 (1M context) --- grafana/dashboards/fleet-public.json | 426 +++++++++++++-------------- grafana/dashboards/fleet.json | 422 +++++++++++++------------- 2 files changed, 424 insertions(+), 424 deletions(-) diff --git a/grafana/dashboards/fleet-public.json b/grafana/dashboards/fleet-public.json index 4a67720..79b82b6 100644 --- a/grafana/dashboards/fleet-public.json +++ b/grafana/dashboards/fleet-public.json @@ -461,7 +461,7 @@ }, { "type": "row", - "title": "Detail", + "title": "Collector health", "gridPos": { "h": 1, "w": 24, @@ -469,6 +469,210 @@ "y": 18 }, "collapsed": false, + "id": 526, + "panels": [] + }, + { + "type": "stat", + "title": "GitHub rate limit remaining", + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 19 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "jq_github_rate_limit_remaining", + "instant": true + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "decimals": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#d03b3b", + "value": null + }, + { + "color": "#fab219", + "value": 500 + }, + { + "color": "#0ca30c", + "value": 1500 + } + ] + } + }, + "overrides": [] + }, + "id": 527 + }, + { + "type": "stat", + "title": "Since last GitHub refresh", + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 19 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "time() - jq_collector_last_success_timestamp_seconds{source=\"github\"}", + "instant": true + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + } + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#0ca30c", + "value": null + }, + { + "color": "#fab219", + "value": 900 + }, + { + "color": "#d03b3b", + "value": 3600 + } + ] + } + }, + "overrides": [] + }, + "id": 528 + }, + { + "type": "timeseries", + "title": "Refresh duration", + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 19 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "jq_collector_refresh_duration_seconds", + "legendFormat": "{{source}}" + } + ], + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true, + "calcs": [] + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "min": 0, + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 0, + "showPoints": "never" + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "github" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "#3987e5" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "local" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "#d95926" + } + } + ] + } + ] + }, + "id": 529 + }, + { + "type": "row", + "title": "Detail", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 24 + }, + "collapsed": false, "id": 14, "panels": [] }, @@ -479,7 +683,7 @@ "h": 14, "w": 24, "x": 0, - "y": 19 + "y": 25 }, "datasource": { "type": "prometheus", @@ -723,7 +927,7 @@ "h": 12, "w": 24, "x": 0, - "y": 33 + "y": 39 }, "datasource": { "type": "prometheus", @@ -978,7 +1182,7 @@ "h": 11, "w": 24, "x": 0, - "y": 45 + "y": 51 }, "datasource": { "type": "prometheus", @@ -1122,7 +1326,7 @@ "h": 1, "w": 24, "x": 0, - "y": 56 + "y": 62 }, "collapsed": false, "panels": [] @@ -1136,7 +1340,7 @@ "h": 12, "w": 24, "x": 0, - "y": 57 + "y": 63 }, "datasource": { "type": "prometheus", @@ -1255,7 +1459,7 @@ "h": 12, "w": 24, "x": 0, - "y": 69 + "y": 75 }, "datasource": { "type": "prometheus", @@ -1371,7 +1575,7 @@ "h": 12, "w": 24, "x": 0, - "y": 81 + "y": 87 }, "datasource": { "type": "prometheus", @@ -1537,7 +1741,7 @@ "h": 12, "w": 24, "x": 0, - "y": 93 + "y": 99 }, "datasource": { "type": "prometheus", @@ -1641,210 +1845,6 @@ ] } }, - { - "type": "row", - "title": "Collector health", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 105 - }, - "collapsed": false, - "id": 526, - "panels": [] - }, - { - "type": "stat", - "title": "GitHub rate limit remaining", - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 106 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "jq_github_rate_limit_remaining", - "instant": true - } - ], - "options": { - "colorMode": "value", - "graphMode": "area", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "decimals": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#d03b3b", - "value": null - }, - { - "color": "#fab219", - "value": 500 - }, - { - "color": "#0ca30c", - "value": 1500 - } - ] - } - }, - "overrides": [] - }, - "id": 527 - }, - { - "type": "stat", - "title": "Since last GitHub refresh", - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 106 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "time() - jq_collector_last_success_timestamp_seconds{source=\"github\"}", - "instant": true - } - ], - "options": { - "colorMode": "value", - "graphMode": "area", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - } - }, - "fieldConfig": { - "defaults": { - "unit": "s", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#0ca30c", - "value": null - }, - { - "color": "#fab219", - "value": 900 - }, - { - "color": "#d03b3b", - "value": 3600 - } - ] - } - }, - "overrides": [] - }, - "id": 528 - }, - { - "type": "timeseries", - "title": "Refresh duration", - "gridPos": { - "h": 5, - "w": 12, - "x": 12, - "y": 106 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "jq_collector_refresh_duration_seconds", - "legendFormat": "{{source}}" - } - ], - "options": { - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": true, - "calcs": [] - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "s", - "min": 0, - "custom": { - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 0, - "showPoints": "never" - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "github" - }, - "properties": [ - { - "id": "color", - "value": { - "mode": "fixed", - "fixedColor": "#3987e5" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "local" - }, - "properties": [ - { - "id": "color", - "value": { - "mode": "fixed", - "fixedColor": "#d95926" - } - } - ] - } - ] - }, - "id": 529 - }, { "type": "row", "title": "Template drift", diff --git a/grafana/dashboards/fleet.json b/grafana/dashboards/fleet.json index d7fa00d..45e2729 100644 --- a/grafana/dashboards/fleet.json +++ b/grafana/dashboards/fleet.json @@ -683,13 +683,218 @@ }, { "type": "row", - "title": "Detail", + "title": "Collector health", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 20 }, + "collapsed": true, + "panels": [ + { + "type": "stat", + "title": "GitHub rate limit remaining", + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 99 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "jq_github_rate_limit_remaining", + "instant": true + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + } + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "decimals": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#d03b3b", + "value": null + }, + { + "color": "#fab219", + "value": 500 + }, + { + "color": "#0ca30c", + "value": 1500 + } + ] + } + }, + "overrides": [] + }, + "id": 527 + }, + { + "type": "stat", + "title": "Since last GitHub refresh", + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 99 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "time() - jq_collector_last_success_timestamp_seconds{source=\"github\"}", + "instant": true + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + } + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#0ca30c", + "value": null + }, + { + "color": "#fab219", + "value": 900 + }, + { + "color": "#d03b3b", + "value": 3600 + } + ] + } + }, + "overrides": [] + }, + "id": 528 + }, + { + "type": "timeseries", + "title": "Refresh duration", + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 99 + }, + "datasource": { + "type": "prometheus", + "uid": "jq-prometheus" + }, + "targets": [ + { + "refId": "A", + "expr": "jq_collector_refresh_duration_seconds", + "legendFormat": "{{source}}" + } + ], + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true, + "calcs": [] + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "s", + "min": 0, + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 0, + "showPoints": "never" + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "github" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "#3987e5" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "local" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "#d95926" + } + } + ] + } + ] + }, + "id": 529 + } + ], + "id": 526 + }, + { + "type": "row", + "title": "Detail", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 21 + }, "collapsed": false, "panels": [], "id": 14 @@ -701,7 +906,7 @@ "h": 14, "w": 24, "x": 0, - "y": 21 + "y": 22 }, "datasource": { "type": "prometheus", @@ -945,7 +1150,7 @@ "h": 12, "w": 24, "x": 0, - "y": 35 + "y": 36 }, "datasource": { "type": "prometheus", @@ -1200,7 +1405,7 @@ "h": 11, "w": 24, "x": 0, - "y": 47 + "y": 48 }, "datasource": { "type": "prometheus", @@ -1344,7 +1549,7 @@ "h": 16, "w": 24, "x": 0, - "y": 58 + "y": 59 }, "datasource": { "type": "prometheus", @@ -1642,7 +1847,7 @@ "h": 1, "w": 24, "x": 0, - "y": 74 + "y": 75 }, "collapsed": true, "panels": [ @@ -2495,211 +2700,6 @@ } ] }, - { - "type": "row", - "title": "Collector health", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 75 - }, - "collapsed": true, - "panels": [ - { - "type": "stat", - "title": "GitHub rate limit remaining", - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 99 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "jq_github_rate_limit_remaining", - "instant": true - } - ], - "options": { - "colorMode": "value", - "graphMode": "area", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "decimals": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#d03b3b", - "value": null - }, - { - "color": "#fab219", - "value": 500 - }, - { - "color": "#0ca30c", - "value": 1500 - } - ] - } - }, - "overrides": [] - }, - "id": 527 - }, - { - "type": "stat", - "title": "Since last GitHub refresh", - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 99 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "time() - jq_collector_last_success_timestamp_seconds{source=\"github\"}", - "instant": true - } - ], - "options": { - "colorMode": "value", - "graphMode": "area", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - } - }, - "fieldConfig": { - "defaults": { - "unit": "s", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#0ca30c", - "value": null - }, - { - "color": "#fab219", - "value": 900 - }, - { - "color": "#d03b3b", - "value": 3600 - } - ] - } - }, - "overrides": [] - }, - "id": 528 - }, - { - "type": "timeseries", - "title": "Refresh duration", - "gridPos": { - "h": 5, - "w": 12, - "x": 12, - "y": 99 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "jq_collector_refresh_duration_seconds", - "legendFormat": "{{source}}" - } - ], - "options": { - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": true, - "calcs": [] - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "s", - "min": 0, - "custom": { - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 0, - "showPoints": "never" - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "github" - }, - "properties": [ - { - "id": "color", - "value": { - "mode": "fixed", - "fixedColor": "#3987e5" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "local" - }, - "properties": [ - { - "id": "color", - "value": { - "mode": "fixed", - "fixedColor": "#d95926" - } - } - ] - } - ] - }, - "id": 529 - } - ], - "id": 526 - }, { "type": "row", "title": "Template drift", From edebe00f18b2dd658c40ae97a3e4374453cc5333 Mon Sep 17 00:00:00 2001 From: Thomas Schmelzer Date: Sun, 30 Aug 2026 11:06:58 +0400 Subject: [PATCH 3/4] Generate the server's JQ_REPOS line instead of retyping it A server has no checkouts, so it names its fleet directly in .env - which left the one list that decides the board's contents existing twice, in two formats, kept in step by hand. Nothing reports the drift: add a repo to repos.yml and the laptop board grows a row while the public board silently does not. `gen-repos.py --env` resolves repos.yml exactly as the normal run does and prints the JQ_REPOS= line, writing nothing. Run it where the checkouts are and copy the one line over, or pipe it straight at the host. The server still keeps its fleet in .env rather than reading repos.yml, because every path in that file describes a machine the server is not - but the line is now derived rather than transcribed. Two tests: that --env prints the list and leaves the compose override untouched (so it is safe to run where no stack exists), and that an unknown flag is refused rather than silently treated as a normal run. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 20 ++++++++++++++++++++ collector/tests/test_fleet.py | 28 ++++++++++++++++++++++++++++ scripts/gen-repos.py | 17 +++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/README.md b/README.md index 9545c26..09b130f 100644 --- a/README.md +++ b/README.md @@ -386,6 +386,26 @@ chmod 600 .env A `.env` rather than `export`, so the settings survive a reboot - compose reads it automatically, and it is gitignored. +**Do not retype that `JQ_REPOS` line.** A fleet transcribed by hand is a fleet +that quietly diverges from the one in `repos.yml` - a repo added on the laptop +never reaches the board, and nothing reports the difference. Generate it on a +machine that has the checkouts and copy the single line across: + +```bash +python3 scripts/gen-repos.py --env +# JQ_REPOS=Jebel-Quant/monitoring,cvxgrp/cvxrisk,tschm/pyhrp +``` + +`--env` writes nothing and prints only that line, so it pipes: + +```bash +python3 scripts/gen-repos.py --env | ssh fleet-host 'cat >> monitoring/.env' +``` + +Re-run it after editing `repos.yml` and replace the line on the server. The +server keeps naming its fleet in `.env` rather than reading `repos.yml`, because +every path in that file describes a machine the server is not. + Note the token has to be able to read every repo named in `JQ_REPOS`. A fine-grained token scoped to one org cannot see another's, and the collector logs `listed repo ... is not readable` when that happens — one unreadable entry diff --git a/collector/tests/test_fleet.py b/collector/tests/test_fleet.py index 6d2bedb..af3ba5c 100644 --- a/collector/tests/test_fleet.py +++ b/collector/tests/test_fleet.py @@ -15,6 +15,7 @@ import importlib.util import pathlib import subprocess +import sys import pytest @@ -192,3 +193,30 @@ def test_a_directory_that_is_not_a_checkout_fails(gen_repos, tmp_path): (tmp_path / "empty").mkdir() with pytest.raises(SystemExit): gen_repos.resolve({"path": str(tmp_path / "empty")}, 1) + + +def test_env_mode_prints_the_list_and_writes_nothing(gen_repos, tmp_path, monkeypatch, capsys): + """A server names its fleet in .env, and that line must come from repos.yml. + + Retyping it is how the two drift: a repo added here never reaches the board + and nothing reports the difference. --env must also leave the compose + override alone, so it is safe to run on a machine that has no stack. + """ + path = make_checkout(tmp_path, "Jebel-Quant", "rhiza") + source = tmp_path / "repos.yml" + source.write_text(f"repos:\n - path: {path}\n - repo: cvxgrp/cvxsimulator\n") + target = tmp_path / "docker-compose.repos.yml" + monkeypatch.setattr(gen_repos, "SOURCE", source) + monkeypatch.setattr(gen_repos, "TARGET", target) + monkeypatch.setattr(sys, "argv", ["gen-repos.py", "--env"]) + + gen_repos.main() + + assert capsys.readouterr().out.strip() == "JQ_REPOS=Jebel-Quant/rhiza,cvxgrp/cvxsimulator" + assert not target.exists() + + +def test_an_unknown_flag_is_refused(gen_repos, monkeypatch): + monkeypatch.setattr(sys, "argv", ["gen-repos.py", "--all"]) + with pytest.raises(SystemExit): + gen_repos.main() diff --git a/scripts/gen-repos.py b/scripts/gen-repos.py index 414f3cf..1928e56 100755 --- a/scripts/gen-repos.py +++ b/scripts/gen-repos.py @@ -8,6 +8,13 @@ and the board's contents are decided by repos.yml alone. Run it after editing repos.yml; scripts/up.sh does that for you. + +With ``--env`` it writes nothing and prints the ``JQ_REPOS=`` line instead. A +server has no checkouts, so it names its fleet in .env by hand - and a list +retyped by hand is a list that drifts from the one here. Generate it on a +machine that has the checkouts and paste the one line over: + + python3 scripts/gen-repos.py --env """ from __future__ import annotations @@ -105,6 +112,11 @@ def resolve(entry: object, index: int) -> tuple[str, Path | None]: def main() -> None: + env_only = "--env" in sys.argv[1:] + unknown = [a for a in sys.argv[1:] if a != "--env"] + if unknown: + fail(f"unknown argument {unknown[0]} - the only flag is --env") + resolved: dict[str, Path | None] = {} for index, entry in enumerate(load_entries(), start=1): full_name, path = resolve(entry, index) @@ -112,6 +124,11 @@ def main() -> None: fail(f"{full_name} is listed twice") resolved[full_name] = path + # Nothing but the line itself on stdout, so it can be piped or appended. + if env_only: + print(f'JQ_REPOS={",".join(resolved)}') + return + lines = [ "# Generated by scripts/gen-repos.py from repos.yml - do not edit.", "#", From 8c2d9fb43d7fb06cd80a1a38a9f645e3e72007c5 Mon Sep 17 00:00:00 2001 From: Thomas Schmelzer Date: Sun, 30 Aug 2026 11:23:21 +0400 Subject: [PATCH 4/4] Keep template drift off the public copy entirely Moving the section to the bottom was not enough: template drift is a rhiza concern, and to a visitor who does not use the template "three releases behind" reads as a defect rather than as something that does not apply to them. Dropping the section alone would not have removed it. The same metrics were on the board in two other places - a "Repos behind the template" table in the drill-down, and a "Behind template" series on the trend chart - so a headline tile could disappear while a chart carried on plotting the number. All five panels are handled together. The mechanism already existed for jq_local_*: a panel whose every query reads a dropped family goes, a panel that also reads something else keeps its other series. That is now a list, DROPPED_METRICS, with jq_rhiza_* alongside, plus drop_empty_rows so the Template drift heading does not survive its own contents. The final guard checks every family rather than just the one, so a future panel reading either cannot reach the public copy unnoticed. The private board is untouched: the section, the tile, the drill-down table and the trend series are all still there. Co-Authored-By: Claude Opus 5 (1M context) --- grafana/dashboards/fleet-public.json | 421 +-------------------------- scripts/make-public-dashboard.py | 69 ++++- 2 files changed, 58 insertions(+), 432 deletions(-) diff --git a/grafana/dashboards/fleet-public.json b/grafana/dashboards/fleet-public.json index 79b82b6..be3dc52 100644 --- a/grafana/dashboards/fleet-public.json +++ b/grafana/dashboards/fleet-public.json @@ -389,11 +389,6 @@ "refId": "A", "expr": "sum(jq_ci_last_run_success == bool 0)", "legendFormat": "CI red on main" - }, - { - "refId": "B", - "expr": "sum(jq_rhiza_releases_behind > bool 0)", - "legendFormat": "Behind template" } ], "options": { @@ -439,21 +434,6 @@ } } ] - }, - { - "matcher": { - "id": "byName", - "options": "Behind template" - }, - "properties": [ - { - "id": "color", - "value": { - "mode": "fixed", - "fixedColor": "#d95926" - } - } - ] } ] }, @@ -1450,122 +1430,6 @@ ] } }, - { - "id": 102, - "type": "table", - "title": "Repos behind the template", - "description": "Every repo pinned to an older template release. Matches the 'Behind template' tile.", - "gridPos": { - "h": 12, - "w": 24, - "x": 0, - "y": 75 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "(jq_rhiza_releases_behind > 0) * on(repo) group_left(ref) topk by (repo) (1, jq_rhiza_template_ref_info)", - "instant": true, - "format": "table" - } - ], - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "__name__": true, - "instance": true, - "job": true, - "fleet": true, - "Value": false - }, - "indexByName": { - "repo": 0, - "ref": 1, - "Value": 2 - }, - "renameByName": { - "repo": "Repo", - "ref": "Pinned ref", - "Value": "Releases behind" - } - } - }, - { - "id": "sortBy", - "options": { - "sort": [ - { - "field": "Releases behind", - "desc": true - } - ] - } - } - ], - "options": { - "showHeader": true, - "footer": { - "show": false - } - }, - "fieldConfig": { - "defaults": { - "custom": { - "align": "left", - "cellOptions": { - "type": "auto" - } - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Repo" - }, - "properties": [ - { - "id": "custom.width", - "value": 260 - }, - { - "id": "links", - "value": [ - { - "title": "Open on GitHub", - "url": "https://github.com/${__value.text}", - "targetBlank": true - } - ] - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Releases behind" - }, - "properties": [ - { - "id": "custom.align", - "value": "right" - }, - { - "id": "custom.width", - "value": 160 - } - ] - } - ] - } - }, { "id": 104, "type": "table", @@ -1575,7 +1439,7 @@ "h": 12, "w": 24, "x": 0, - "y": 87 + "y": 75 }, "datasource": { "type": "prometheus", @@ -1741,7 +1605,7 @@ "h": 12, "w": 24, "x": 0, - "y": 99 + "y": 87 }, "datasource": { "type": "prometheus", @@ -1844,287 +1708,6 @@ } ] } - }, - { - "type": "row", - "title": "Template drift", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 111 - }, - "collapsed": false, - "id": 8, - "panels": [] - }, - { - "type": "stat", - "title": "Behind template", - "description": "Repos pinned to a template release older than the newest one.", - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 112 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "sum(jq_rhiza_releases_behind > bool 0)", - "instant": true - } - ], - "options": { - "colorMode": "value", - "graphMode": "area", - "textMode": "value", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "justifyMode": "auto", - "text": { - "valueSize": 48, - "titleSize": 13 - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "decimals": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#0ca30c", - "value": null - }, - { - "color": "#fab219", - "value": 1 - }, - { - "color": "#ec835a", - "value": 5 - } - ] - } - }, - "overrides": [] - }, - "id": 4 - }, - { - "type": "bargauge", - "title": "Template releases behind", - "description": "How many releases were published after each repo's pinned ref. Repos pinned to a branch or a sha are absent - the pin is not comparable to a release.", - "gridPos": { - "h": 13, - "w": 10, - "x": 0, - "y": 116 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "sort_desc(jq_rhiza_releases_behind)", - "instant": true, - "legendFormat": "{{repo}}" - } - ], - "options": { - "orientation": "horizontal", - "displayMode": "basic", - "valueMode": "text", - "showUnfilled": true, - "minVizWidth": 8, - "minVizHeight": 14, - "namePlacement": "left", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "decimals": 0, - "min": 0, - "color": { - "mode": "thresholds" - }, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#184f95", - "value": null - }, - { - "color": "#256abf", - "value": 1 - }, - { - "color": "#3987e5", - "value": 2 - }, - { - "color": "#6da7ec", - "value": 3 - }, - { - "color": "#9ec5f4", - "value": 5 - } - ] - } - }, - "overrides": [] - }, - "id": 9 - }, - { - "type": "table", - "title": "Pinned ref per repo", - "gridPos": { - "h": 13, - "w": 14, - "x": 10, - "y": 116 - }, - "datasource": { - "type": "prometheus", - "uid": "jq-prometheus" - }, - "targets": [ - { - "refId": "A", - "expr": "jq_rhiza_template_ref_info", - "instant": true, - "format": "table" - }, - { - "refId": "B", - "expr": "jq_rhiza_releases_behind", - "instant": true, - "format": "table" - } - ], - "transformations": [ - { - "id": "joinByField", - "options": { - "byField": "repo", - "mode": "outer" - } - }, - { - "id": "organize", - "options": { - "excludeByName": { - "Time 1": true, - "Time 2": true, - "Value #A": true, - "__name__ 1": true, - "__name__ 2": true, - "instance 1": true, - "instance 2": true, - "job 1": true, - "job 2": true, - "fleet 1": true, - "fleet 2": true - }, - "renameByName": { - "repo": "Repo", - "ref": "Pinned ref", - "Value #B": "Releases behind" - } - } - }, - { - "id": "sortBy", - "options": { - "sort": [ - { - "field": "Releases behind", - "desc": true - } - ] - } - } - ], - "options": { - "showHeader": true, - "footer": { - "show": false - } - }, - "fieldConfig": { - "defaults": { - "custom": { - "align": "left", - "cellOptions": { - "type": "auto" - } - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Releases behind" - }, - "properties": [ - { - "id": "custom.align", - "value": "right" - }, - { - "id": "custom.width", - "value": 140 - }, - { - "id": "noValue", - "value": "not a release" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Repo" - }, - "properties": [ - { - "id": "links", - "value": [ - { - "title": "Open on GitHub", - "url": "https://github.com/${__value.text}", - "targetBlank": true - } - ] - } - ] - } - ] - }, - "id": 10 } ] } diff --git a/scripts/make-public-dashboard.py b/scripts/make-public-dashboard.py index fc7a8b8..73a935a 100755 --- a/scripts/make-public-dashboard.py +++ b/scripts/make-public-dashboard.py @@ -13,6 +13,14 @@ That is not secret when the repos are public, but it is someone's working state and it is meaningless to anyone else, so it is dropped. +3. Everything sourced from `jq_rhiza_*` is template drift, which is a rhiza + concern. Most people who open the public link do not use the template at + all, so "three releases behind" reads as a defect rather than as something + that does not apply to them. The whole Template drift section goes, along + with the drill-down table and the trend-chart series that read the same + metrics - a headline tile removed while a chart still plots the number is + not a removal. The private board keeps all of it. + Run after changing fleet.json; the output is provisioned like any other file. """ @@ -24,17 +32,51 @@ SRC = HERE / "grafana/dashboards/fleet.json" DST = HERE / "grafana/dashboards/fleet-public.json" -LOCAL = "jq_local" +# Metric families the public copy does not carry, each for its own reason +# (see 2 and 3 above). A panel is dropped when every one of its queries reads +# one of these; a panel that also reads something else keeps its other series. +DROPPED_METRICS = ("jq_local", "jq_rhiza") def targets_of(panel): return panel.get("targets", []) or [] -def is_local_only(panel): - """True when every query in the panel comes from local-clone metrics.""" +def is_dropped_metric(expr: str) -> bool: + return any(metric in expr for metric in DROPPED_METRICS) + + +def is_dropped_only(panel): + """True when every query in the panel reads a family we do not publish.""" exprs = [t.get("expr", "") for t in targets_of(panel)] - return bool(exprs) and all(LOCAL in e for e in exprs) + return bool(exprs) and all(is_dropped_metric(e) for e in exprs) + + +def drop_empty_rows(panels): + """A row whose every panel was dropped would render as a bare heading. + + Template drift loses all three of its panels, so the row goes with them + rather than leaving a title with nothing under it. + """ + ordered = sorted(panels, key=lambda p: (p["gridPos"]["y"], p["gridPos"]["x"])) + kept, i = [], 0 + while i < len(ordered): + panel = ordered[i] + if panel.get("type") != "row": + kept.append(panel) + i += 1 + continue + j = i + 1 + following = [] + while j < len(ordered) and ordered[j].get("type") != "row": + following.append(ordered[j]) + j += 1 + # Children live inside a collapsed row and beside an expanded one. + if panel.get("panels") or following: + kept.append(panel) + kept.extend(following) + i = j + return kept def mission_control(dash: dict) -> None: @@ -70,7 +112,7 @@ def restyle(title, *, lit, unit=None): for t in ("Repos monitored", "Open PRs", "Open issues"): restyle(t, lit=False) - for t in ("CI red on main", "Behind template", "PRs with red checks"): + for t in ("CI red on main", "PRs with red checks"): restyle(t, lit=True) # Data age is a verdict: stale data invalidates everything above it. restyle("Data age", lit=True, unit="s") @@ -120,7 +162,7 @@ def walk(panels): yield panel yield from walk(panel.get("panels", [])) - dropped_ids = {p["id"] for p in walk(dash["panels"]) if is_local_only(p)} + dropped_ids = {p["id"] for p in walk(dash["panels"]) if is_dropped_only(p)} def prune(panels): kept = [] @@ -130,13 +172,13 @@ def prune(panels): if panel.get("panels"): panel["panels"] = prune(panel["panels"]) # A mixed panel keeps its non-local series only. - if targets_of(panel) and not is_local_only(panel): - keep = [t for t in targets_of(panel) if LOCAL not in t.get("expr", "")] + if targets_of(panel) and not is_dropped_only(panel): + keep = [t for t in targets_of(panel) if not is_dropped_metric(t.get("expr", ""))] if len(keep) != len(targets_of(panel)): gone = { t.get("legendFormat") for t in targets_of(panel) - if LOCAL in t.get("expr", "") + if is_dropped_metric(t.get("expr", "")) } panel["targets"] = keep panel["fieldConfig"]["overrides"] = [ @@ -155,7 +197,7 @@ def prune(panels): kept.append(panel) return kept - dash["panels"] = prune(dash["panels"]) + dash["panels"] = drop_empty_rows(prune(dash["panels"])) # Strip every data link: none of them can work for an anonymous viewer. for panel in walk(dash["panels"]): @@ -231,13 +273,14 @@ def prune(panels): leftover = re.findall(r"\$repo", raw) if leftover: raise SystemExit(f"{len(leftover)} unresolved $repo references remain") - if LOCAL in raw: - raise SystemExit("local-clone metrics survived the strip") + for metric in DROPPED_METRICS: + if metric in raw: + raise SystemExit(f"{metric} metrics survived the strip") if "/d/jq-fleet" in raw: raise SystemExit("an absolute dashboard link survived - it would 401 a public viewer") DST.write_text(json.dumps(json.loads(raw), indent=2) + "\n") - print(f"wrote {DST.relative_to(HERE)} ({len(dropped_ids)} local panels dropped)") + print(f"wrote {DST.relative_to(HERE)} ({len(dropped_ids)} panels dropped)") if __name__ == "__main__":