# Sync Dashboards

> Keep the MegaLinter observability dashboards (docs/dashboards/) in sync with the ApiReporter v2 payload and metrics contract. Use after modifying ApiReporter.py, megalinter/api_providers/, or the dashboards themselves.

- Skill: `oxsecurity/sync-dashboards` (Agent Skill)
- Install (CLI): `npx skillmds@latest add oxsecurity/sync-dashboards`
- Raw SKILL.md: https://api.skillmd.com/api/skills/oxsecurity/sync-dashboards/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: oxsecurity (https://skillmd.com/u/oxsecurity)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/oxsecurity/sync-dashboards

---


You maintain the consistency between the MegaLinter observability payload and the provider dashboards.

## The contract

- **Producer**: `megalinter/reporters/ApiReporter.py` builds the v2 payload (run-level KPIs incl. `qualityGateStatus` and the 0-100 `healthScore`, per-linter records, SARIF-derived `rulesBreakdown`/`filesBreakdown`); `megalinter/api_providers/ApiProvider.py` defines the metric fields (`run_metric_fields`, `linter_metric_fields`) and each `ApiProvider*` subclass maps them to its backend (Prometheus/Loki, Datadog, Elastic, New Relic).
- **Contract file**: `.automation/dashboard_builders/contract.py` lists the same fields, labels (base + run-level `megalinterVersion`/`megalinterFlavor`), record types and metric name prefixes.
- **Consumers**: the `DashboardBuilder*` classes in `.automation/dashboard_builders/` generate `docs/dashboards/` (Grafana JSON, Datadog JSON, New Relic JSON, Kibana ndjson + `manifest.json`).

## Dashboard design invariants (keep them when editing)

- **Fleet-to-detail navigation**: Grafana org table/charts carry data links to the Repository dashboard (`var-repo`), which links to Linter Detail (`var-linter`); Datadog uses the `git_repo_name`/`git_branch_name` template variables + `__SELF_URL__` custom links (substituted by the uploader); New Relic has Repository/Branch NRQL variables, a "Repository detail" page and facet links (`__REPOSITORY_DETAIL_PAGE_GUID__`, substituted by the uploader's second pass); Kibana relies on native click-to-filter.
- **Branch filtering** must stay available on every platform (Grafana `$branch`, Datadog `$git_branch_name`, New Relic `{{gitBranchName}}` with `%` default, Kibana filter bar).
- **Rating drill-down**: every rating (A-E) tile leads to a "Why this rating?" explanation — Grafana dedicated dashboard (uid `megalinter-rating`, linked from the rating stats), New Relic dedicated page (facet-linkable via the `__RATING_PAGE_GUID__` marker), Datadog group widget, Elastic markdown + breakdown panels. It must always show: the formula, linters success/warning/error counts, and the linters dragging the score down.
- **Derived KPIs**: the A-E rating is a dashboard-side range mapping of `healthScore` (A>=90, B>=80, C>=65, D>=50, else E); "time saved" is `totalErrorsFixed * 5` minutes — keep formulas consistent across providers and documented in `docs/observability.md`.
- **Agnostic dashboards**: no account ids, datasource uids, or instance URLs hardcoded in generated files — placeholders are resolved by `mega-linter-runner/lib/upload-dashboards.js` at upload time so anyone can provision them on their own account.
- **Value-conditional styling**: KPIs are colored by value on every platform — Grafana thresholds/background stat tiles/color-background table cells/threshold-zone areas, Datadog `conditional_formats` + semantic timeseries palettes (warm=errors, cool=health, purple=duration), New Relic billboard `thresholds` + fixed series colors (Blocking=red, Non-blocking=yellow), Kibana Lens custom palettes on metric panels. Keep the semantics: green=good, yellow=warning, red=blocking, blue=neutral/informative.
- **Cardinality**: `runId`, `jobUrl`, rule ids and file paths never become metric tags or Loki stream labels.

## Process

1. Identify what changed (new/renamed/removed payload field, label, record type, or a dashboard improvement request).
2. Apply the change consistently:
   - Payload change → update `contract.py` AND the relevant `DashboardBuilder*` panels/queries.
   - Dashboard-only change → update the `DashboardBuilder*` class (never edit `docs/dashboards/*` directly — they are generated).
3. Regenerate: `python .automation/build_dashboards.py` (use the repo venv).
4. Verify sync: `python .automation/build_dashboards.py --check` must pass, and `megalinter/tests/test_megalinter/api_reporter_v2_test.py` must still pass (`pytest megalinter/tests/test_megalinter/api_reporter_v2_test.py`).
5. If credentials are available in `.env` (GRAFANA_HOST/GRAFANA_TOKEN, DD_TOKEN, ELASTIC_HOST/ELASTIC_API_KEY, NEW_RELIC_HOST/NEW_RELIC_API_KEY), offer to upload the regenerated dashboards to the live instances with `node mega-linter-runner/lib/index.js --upload-dashboards <provider>` (set `MEGALINTER_DASHBOARDS_DIR` to the local `docs/dashboards` folder) and verify with provider queries that the dashboards' metrics/fields return data.
   - The uploader expects different variable names than `.env` uses — map them: `GRAFANA_URL=$GRAFANA_HOST`, `DD_BEARER_TOKEN=$DD_TOKEN` + `DD_SITE=datadoghq.eu` (the token is an EU-site bearer token; the default site 401s), `KIBANA_URL=$ELASTIC_HOST`, `NEW_RELIC_REGION=EU` + `NEW_RELIC_ACCOUNT_ID` (resolve it via NerdGraph `{ actor { accounts { id } } }` on `api.eu.newrelic.com` — take the non-storage account).
   - To query Elastic data directly, derive the Elasticsearch endpoint from the Kibana host (`.kb.` → `.es.`, strip trailing slash); the Kibana console proxy is disabled (404).
   - After a New Relic upload, verify the facet links: fetch the dashboard entity's pages + widgets `linkedEntities` via NerdGraph and check every linked guid is one of the entity's current page guids (the second-pass update must send `page.guid` values or NerdGraph recreates pages and the links go stale).
6. Update the documentation if the metrics reference changed: `docs/observability.md` and `docs/observability/<provider>.md`.

## Rules

- Payload/metric renames are breaking changes: mention them in `CHANGELOG.md` (beta section) with a migration note.
- Keep metric label cardinality low: `runId`, `jobUrl`, rule ids and file paths never become metric tags or Loki stream labels — they belong in log lines/documents.
- Datadog tag keys are snake_case (`git_repo_name`, `linter_key`, `record_type`); everywhere else camelCase.

