You maintain the consistency between the MegaLinter observability payload and the provider dashboards.
The contract
- Producer:
megalinter/reporters/ApiReporter.pybuilds the v2 payload (run-level KPIs incl.qualityGateStatusand the 0-100healthScore, per-linter records, SARIF-derivedrulesBreakdown/filesBreakdown);megalinter/api_providers/ApiProvider.pydefines the metric fields (run_metric_fields,linter_metric_fields) and eachApiProvider*subclass maps them to its backend (Prometheus/Loki, Datadog, Elastic, New Relic). - Contract file:
.automation/dashboard_builders/contract.pylists the same fields, labels (base + run-levelmegalinterVersion/megalinterFlavor), record types and metric name prefixes. - Consumers: the
DashboardBuilder*classes in.automation/dashboard_builders/generatedocs/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 thegit_repo_name/git_branch_nametemplate 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" istotalErrorsFixed * 5minutes — keep formulas consistent across providers and documented indocs/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.jsat 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 billboardthresholds+ 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
- Identify what changed (new/renamed/removed payload field, label, record type, or a dashboard improvement request).
- Apply the change consistently:
- Payload change → update
contract.pyAND the relevantDashboardBuilder*panels/queries. - Dashboard-only change → update the
DashboardBuilder*class (never editdocs/dashboards/*directly — they are generated).
- Payload change → update
- Regenerate:
python .automation/build_dashboards.py(use the repo venv). - Verify sync:
python .automation/build_dashboards.py --checkmust pass, andmegalinter/tests/test_megalinter/api_reporter_v2_test.pymust still pass (pytest megalinter/tests/test_megalinter/api_reporter_v2_test.py). - 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 withnode mega-linter-runner/lib/index.js --upload-dashboards <provider>(setMEGALINTER_DASHBOARDS_DIRto the localdocs/dashboardsfolder) and verify with provider queries that the dashboards' metrics/fields return data.- The uploader expects different variable names than
.envuses — 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 } } }onapi.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
linkedEntitiesvia NerdGraph and check every linked guid is one of the entity's current page guids (the second-pass update must sendpage.guidvalues or NerdGraph recreates pages and the links go stale).
- The uploader expects different variable names than
- Update the documentation if the metrics reference changed:
docs/observability.mdanddocs/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.