Schema: convert dashboards from v1beta1 to v2beta1 (#109037)

- Implement full conversion pipeline from v1beta1 → v2beta1
- Ensure frontend–backend parity for all dashboard serialization paths
- Add automatic data loss detection for conversions (panels, queries, annotations, links, variables)
- Extract atomic conversion functions for v0 → v1beta1 → v2alpha1 → v2beta1
- Introduce conversion metrics and detailed logging for loss tracking
- Normalize datasource resolution, defaults, and annotation processing
- Improve panel layout serialization and y-coordinate normalization
- Fix inconsistencies in nested panels and collapsed row behavior
- Refine variable handling:
  - Filter refId from variable query specs
  - Default variable refresh to 'never' (matches frontend)
  - Fix constant and interval variable handling for missing queries
- Unify schema defaults (enable, hide, iconColor, editable, liveNow)
- Fix pluginId usage (UID vs type) and datasource references
- Fix metrics.go bug swallowing errors (return nil → return err)
- Add tests for version-specific conversion error handling
- Add data loss detection tests using source/target version comparison
- Clean up lint issues, legacy code, and redundant files
- Update OpenAPI snapshots and migrated dashboards
- Improve backend migrator to reuse datasource provider and match frontend logic

Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com>
Co-authored-by: Oscar Kilhed <oscar.kilhed@grafana.com>
Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
This commit is contained in:
Ivan Ortega Alba
2025-11-12 11:43:46 +01:00
committed by GitHub
co-authored by Haris Rozajac Oscar Kilhed Stephanie Hingtgen
parent 09942c08db
commit e463781077
273 changed files with 75003 additions and 588 deletions
@@ -12,11 +12,6 @@
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"type": "dashboard"
},
{
"datasource": "grafana",
"enable": true,
"name": "Annotations \u0026 Alerts"
}
]
},
@@ -14,7 +14,10 @@
"type": "dashboard"
},
{
"datasource": "grafana",
"datasource": {
"type": "grafana",
"uid": "grafana"
},
"enable": true,
"name": "Annotations \u0026 Alerts"
}
@@ -78,6 +78,7 @@
"type": "table"
},
{
"autoMigrateFrom": "graph",
"gridPos": {
"h": 6,
"w": 12,
@@ -87,9 +88,10 @@
"height": 200,
"id": 4,
"title": "Network I/O",
"type": "graph"
"type": "timeseries"
},
{
"autoMigrateFrom": "graph",
"gridPos": {
"h": 6,
"w": 12,
@@ -99,7 +101,7 @@
"height": 200,
"id": 5,
"title": "Disk I/O",
"type": "graph"
"type": "timeseries"
}
],
"title": "Collapsed Row",
@@ -30,9 +30,10 @@
"id": 4,
"panels": [
{
"autoMigrateFrom": "graph",
"id": 6,
"title": "Normal nested panel",
"type": "graph"
"type": "timeseries"
}
],
"title": "Row with repeated panels",
@@ -115,7 +115,9 @@
},
{
"datasource": {
"uid": "prometheus"
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"enable": true,
"iconColor": "purple",
@@ -47,6 +47,8 @@
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "unknown-datasource-name"
},
"name": "Unknown Datasource Annotation - Tests unknown datasource preserved as UID"
@@ -135,6 +137,8 @@
},
{
"datasource": {
"apiVersion": "v2",
"type": "elasticsearch",
"uid": "existing-target-uid"
},
"refId": "B"
@@ -152,6 +156,7 @@
"targets": [
{
"datasource": {
"apiVersion": "v2",
"type": "elasticsearch",
"uid": "existing-target-uid"
},
@@ -178,6 +183,8 @@
},
{
"datasource": {
"apiVersion": "v2",
"type": "elasticsearch",
"uid": "existing-target-uid"
},
"description": "Tests panel with expression query - should not inherit expression as panel datasource",
@@ -185,6 +192,8 @@
"targets": [
{
"datasource": {
"apiVersion": "v2",
"type": "elasticsearch",
"uid": "existing-target-uid"
},
"refId": "A"
@@ -201,6 +210,8 @@
},
{
"datasource": {
"apiVersion": "v2",
"type": "elasticsearch",
"uid": "existing-target-uid"
},
"description": "Tests panel inheriting datasource from target when panel datasource was default",
@@ -208,6 +219,8 @@
"targets": [
{
"datasource": {
"apiVersion": "v2",
"type": "elasticsearch",
"uid": "existing-target-uid"
},
"refId": "A"
@@ -290,6 +303,8 @@
"panels": [
{
"datasource": {
"apiVersion": "v2",
"type": "elasticsearch",
"uid": "existing-target-uid"
},
"description": "Nested panel in collapsed row with default datasource",
@@ -297,6 +312,8 @@
"targets": [
{
"datasource": {
"apiVersion": "v2",
"type": "elasticsearch",
"uid": "existing-target-uid"
},
"refId": "A"
@@ -117,6 +117,7 @@
"type": "timeseries"
},
{
"autoMigrateFrom": "graph",
"id": 12,
"options": {
"legend": {
@@ -125,7 +126,7 @@
}
},
"title": "Nested Panel with Hidden DisplayMode",
"type": "graph"
"type": "timeseries"
},
{
"id": 13,
@@ -0,0 +1,62 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [
{
"fieldConfig": {
"defaults": {},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Field1"
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"tooltip": true,
"viz": true
}
}
]
}
]
},
"id": 1,
"title": "Panel with hideFrom.viz = true",
"type": "timeseries"
}
],
"schemaVersion": 42,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "v42 Migration Test - Harky Must",
"weekStart": ""
}
@@ -67,14 +67,23 @@
]
},
{
"__systemRef": "hideSeriesFrom",
"matcher": {
"id": "__systemRef",
"options": "hiddenSeries"
"id": "byNames",
"options": {
"mode": "exclude",
"names": [
"foo"
],
"prefix": "All except:",
"readOnly": true
}
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"legend": false,
"tooltip": true,
"viz": true
}
@@ -121,6 +130,10 @@
"defaults": {},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "unit",
@@ -143,13 +156,18 @@
"defaults": {},
"overrides": [
{
"matcher": {
"id": "byValue",
"options": {
"op": "gte",
"reducer": "allIsZero",
"value": 0
}
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"tooltip": false,
"viz": false
}
"id": "unit",
"value": "short"
}
]
}
@@ -164,10 +182,15 @@
"defaults": {},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "{__name__=\"ALERTS\", alertname=\"k6CloudServiceErrorsLogged\"}"
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"legend": false,
"tooltip": true,
"viz": true
}
@@ -14,14 +14,20 @@
"type": "dashboard"
},
{
"datasource": "prometheus",
"datasource": {
"type": "prometheus",
"uid": "default-ds-uid"
},
"enable": true,
"iconColor": "red",
"name": "deployment",
"query": "ALERTS{alertname=\"DeploymentStarted\"}"
},
{
"datasource": "loki",
"datasource": {
"type": "loki",
"uid": "loki-uid"
},
"enable": false,
"iconColor": "yellow",
"name": "alerts",
@@ -79,6 +85,7 @@
{
"name": "environment",
"options": [],
"query": "label_values(up, instance)",
"type": "query"
},
{
@@ -86,6 +93,7 @@
"datasource": "prometheus",
"name": "service",
"options": [],
"query": "label_values(up, instance)",
"type": "query"
},
{