ci: add dashboard-description lint

Checks every grafana dashboard panel/board description against a length
cap and a small keyword list (operator name, tracker-tag references) so
the descriptions just shortened don't regress. Wired into both CI and
pre-push, same as the other content lints.
This commit is contained in:
iris 2026-09-20 14:07:53 +02:00 committed by mara
commit a7e7ccd7df
3 changed files with 109 additions and 0 deletions

View file

@ -57,6 +57,17 @@ jobs:
- name: lint
run: sh scripts/check-attribution-trailers.sh
dashboard-descriptions:
name: dashboard-description lint
runs-on: [hive-ci]
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- name: lint
# jq isn't a project dependency elsewhere in this repo — same
# nix-shell-for-one-tool shape the vale/shellcheck jobs above use.
run: nix shell nixpkgs#jq --command sh scripts/check-dashboard-descriptions.sh
shellcheck:
name: shellcheck
runs-on: [hive-ci]

View file

@ -0,0 +1,90 @@
#!/bin/sh
# CI lint: keeps grafana dashboard panel/board descriptions short and free of
# meta-commentary — a tooltip answers "what is this number", not "how did we
# arrive at this panel" or "what did someone say about it".
#
# Two checks: length (over MAX_LEN means it became prose, not a short
# answer — fix by deleting the editorializing, not trimming words), and
# keywords (the operator's name, hardcoded rather than read from config —
# the class of mistake is what's caught, not one specific name; and a
# tracker tag `#1234`, reusing check-issue-refs.sh's own pattern and its
# same known false-positive class, e.g. `PKCS#11` — no `lint:allow` escape
# hatch here though, since JSON has no comment syntax to carry the marker;
# rephrase to drop the `#digits` shape instead).
#
# Deliberately NOT a blanket ban on the word "issue": forge/gitea panels
# legitimately show issue-tracker counts ("Currently open issues, across
# every repo." is exactly the short description this check wants to keep).
# The tracker-tag pattern is the part machine-checkable; a prose
# meta-reference with no `#N` needs a human reviewer, same as elsewhere.
#
# Uses `jq` — the right tool for JSON, unlike its zero-dependency siblings
# in this directory. This script does NOT nix-shell for it itself: the
# caller declares that dependency (see `pre-push` and
# `.forgejo/workflows/ci.yml`, the same `nix shell nixpkgs#jq --command sh
# <this file>` shape the vale/shellcheck jobs already use for their own
# tools), so a leaf script never hides a nix invocation inside itself.
#
# Emits a CI error annotation per hit, exits 1 if any hit is found.
set -eu
dashboards_dir="nix/host-modules/swarm-grafana/dashboards"
max_len=100
tracker_pattern='#[0-9]{2,5}([^0-9a-zA-Z]|$)'
files="$(find "$dashboards_dir" -maxdepth 1 -name '*.json' 2>/dev/null | sort)"
if [ -z "$files" ]; then
echo 'check-dashboard-descriptions: no dashboard json files found — path moved?' >&2
exit 1
fi
violations=0
checked=0
for f in $files; do
# One JSON object per description hit: {"title": "...", "description": "..."}
# -- recursive `..` walk catches both schema shapes uniformly, since in
# both a description always sits beside a sibling title in the same object.
while IFS= read -r row; do
[ -n "$row" ] || continue
title="$(printf '%s' "$row" | jq -r '.title')"
desc="$(printf '%s' "$row" | jq -r '.description')"
[ -n "$desc" ] || continue
checked=$((checked + 1))
len="$(printf '%s' "$desc" | wc -m | tr -d ' ')"
if [ "$len" -gt "$max_len" ]; then
violations=$((violations + 1))
printf '::error file=%s::panel "%s" description is %s chars (max %s) — shorten it to what the number is, drop the reasoning\n' \
"$f" "$title" "$len" "$max_len"
fi
if printf '%s' "$desc" | grep -qiE 'mara'; then
violations=$((violations + 1))
printf '::error file=%s::panel "%s" description names the operator — describe the metric, not who asked for the panel\n' \
"$f" "$title"
fi
if printf '%s' "$desc" | grep -qE "$tracker_pattern"; then
violations=$((violations + 1))
printf '::error file=%s::panel "%s" description references a tracker issue — write prose that stands on its own\n' \
"$f" "$title"
fi
done <<EOF
$(jq -c '[.. | objects | select(has("title") and has("description"))] | .[]' "$f")
EOF
done
if [ "$checked" -eq 0 ]; then
echo 'check-dashboard-descriptions: found zero title+description pairs — extractor or tree changed' >&2
exit 1
fi
if [ "$violations" -gt 0 ]; then
printf 'check-dashboard-descriptions: %s violation(s) across %s description(s) checked\n' \
"$violations" "$checked" >&2
exit 1
fi
echo "check-dashboard-descriptions: $checked description(s) checked, clean" >&2
exit 0

View file

@ -31,5 +31,13 @@ if ! sh "$repo_root/scripts/check-attribution-trailers.sh"; then
exit 1
fi
echo "pre-push: running dashboard-description lint..." >&2
# jq isn't a project dependency elsewhere in this repo, so the check script
# assumes nothing about the caller's PATH — nix-shell it here instead.
if ! nix shell nixpkgs#jq --command sh "$repo_root/scripts/check-dashboard-descriptions.sh"; then
echo "pre-push: dashboard-description lint FAILED — fix before pushing" >&2
exit 1
fi
echo "pre-push: lints passed ✓" >&2
exit 0