fix(grafana): pin a stable uid on every provisioned dashboard - #195
Conversation
Grafana's file provider identifies a dashboard by uid, and none of the seven dashboard files carried one, so each got a random uid at provision time. That left every dashboard without a durable identity, which shows up the moment anyone edits one in the UI -- something this stack allows on purpose (allowUiUpdates: true, so students can build a panel by clicking rather than by hand-writing JSON). Saving from the UI then detached the dashboard from its file: on the next restart the edited copy was orphaned into the General folder with its provisioning link severed, and the provider built a second dashboard with the same title beside it. Measured against a clean stack: eight dashboards, two of them named "DevOps Demo", and which one a reader opens is a coin flip. With the uid pinned the provider reclaims the same dashboard instead of creating a sibling: seven dashboards, no duplicate, and the file's content wins on restart. Losing an uncommitted UI edit at restart is the correct outcome -- the file is the source of truth -- and it is the behaviour the accompanying exercise will teach. The uids read as URL paths (/d/devops-demo/, /d/analytics-history/) rather than generated hashes, so dashboard links are now stable across a rebuild and can be cited from docs and runbooks. scripts/check-dashboard-uids.sh keeps this from regressing, in the same shape as the toolchain drift gate: it rejects a missing uid, a uid shared by two files, one longer than Grafana's 40-character limit, and unparseable JSON. Without a gate the next dashboard lands without a uid and the duplication returns silently, which is how this arrived in the first place. Datasources needed no equivalent change: every dashboard resolves them through a datasource-type template variable queried by type, not by a hardcoded uid.
|
Warning Review limit reached
Next review available in: 44 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughThe change assigns stable UIDs to seven Grafana dashboards, adds a validator for presence, length, parsing, and uniqueness, and runs that validator through a new local pre-commit hook. ChangesDashboard UID validation
Estimated code review effort: 2 (Simple) | ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@observability/grafana/dashboards/analytics.json`:
- Line 2: Add one ADR or RFC amendment linked in the PR documenting the stable
dashboard UID and provisioning ownership contract for analytics-ingest,
devops-demo, analytics-history, load-k6, monitoring-layers, reports-ui, and
reports-jvm; no direct changes are required in the seven dashboard files beyond
documenting their existing UID assignments.
In `@scripts/check-dashboard-uids.sh`:
- Around line 35-45: Update the dashboard validation loop around dashboard.get
and by_uid in scripts/check-dashboard-uids.sh to first validate that each
dashboard is an object and that any present uid is a string. Append descriptive
failures for invalid dashboard shapes or UID types, and only perform truthiness,
length, and uniqueness checks after those validations pass.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 325187b8-4bc1-4e5d-8760-378787cfb378
📒 Files selected for processing (9)
.pre-commit-config.yamlobservability/grafana/dashboards/analytics.jsonobservability/grafana/dashboards/devops-demo-dashboard.jsonobservability/grafana/dashboards/history.jsonobservability/grafana/dashboards/load.jsonobservability/grafana/dashboards/monitoring-layers.jsonobservability/grafana/dashboards/reports-ui.jsonobservability/grafana/dashboards/reports.jsonscripts/check-dashboard-uids.sh
The gate assumed every dashboard file parses into an object with a string uid. Neither is guaranteed by JSON, so a file whose top level is an array raised AttributeError on dashboard.get, and a numeric uid raised TypeError on len(). The exit code was already correct in both cases -- the gate failed closed, so nothing malformed could pass -- but it failed with a Python traceback instead of naming the file and the problem. For a check whose only job is to explain what is wrong with a file, that is the failure mode that matters. Each shape now gets its own message: a non-object root reports the type it found, a non-string uid reports the type it found, and a null or empty uid falls in with the missing-uid case since the remedy is identical. Verified against every shape: array root, numeric uid, null uid, empty uid, missing uid, over-long uid, unparseable JSON, and two files sharing a uid all exit 1 with a named file, and the seven real dashboards still exit 0.
Grafana's file provider identifies a dashboard by uid, and none of the seven dashboard files carried one, so each got a random uid at provision time. That left every dashboard without a durable identity, which shows up the moment anyone edits one in the UI -- something this stack allows on purpose (allowUiUpdates: true, so students can build a panel by clicking rather than by hand-writing JSON).
Saving from the UI then detached the dashboard from its file: on the next restart the edited copy was orphaned into the General folder with its provisioning link severed, and the provider built a second dashboard with the same title beside it. Measured against a clean stack: eight dashboards, two of them named "DevOps Demo", and which one a reader opens is a coin flip.
With the uid pinned the provider reclaims the same dashboard instead of creating a sibling: seven dashboards, no duplicate, and the file's content wins on restart. Losing an uncommitted UI edit at restart is the correct outcome -- the file is the source of truth -- and it is the behaviour the accompanying exercise will teach.
The uids read as URL paths (/d/devops-demo/, /d/analytics-history/) rather than generated hashes, so dashboard links are now stable across a rebuild and can be cited from docs and runbooks.
scripts/check-dashboard-uids.sh keeps this from regressing, in the same shape as the toolchain drift gate: it rejects a missing uid, a uid shared by two files, one longer than Grafana's 40-character limit, and unparseable JSON. Without a gate the next dashboard lands without a uid and the duplication returns silently, which is how this arrived in the first place.
Datasources needed no equivalent change: every dashboard resolves them through a datasource-type template variable queried by type, not by a hardcoded uid.
Summary by CodeRabbit
New Features
Chores