From 14cb6aea9256137d1cfeb4f81951ded1412605d3 Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Tue, 15 Jul 2025 17:28:48 +0200 Subject: [PATCH] Dashboard Migrations: V32 no-op migration + V31 LabelsToFields-Merge Migration (#107947) * Dashboard Migrations: V31 LabelsToFields-Merge Migration * Dashboard Migrations: V32 No-op migration --- .../pkg/migration/schemaversion/migrations.go | 4 +- .../pkg/migration/schemaversion/v31.go | 132 +++++++ .../pkg/migration/schemaversion/v31_test.go | 336 ++++++++++++++++++ .../pkg/migration/schemaversion/v32.go | 27 ++ .../pkg/migration/schemaversion/v32_test.go | 38 ++ .../input/v31.labels_to_fields_merge.json | 126 +++++++ .../testdata/input/v32.no_op_migration.json | 80 +++++ .../output/v31.labels_to_fields_merge.json | 290 +++++++++++++++ .../testdata/output/v32.no_op_migration.json | 159 +++++++++ 9 files changed, 1191 insertions(+), 1 deletion(-) create mode 100644 apps/dashboard/pkg/migration/schemaversion/v31.go create mode 100644 apps/dashboard/pkg/migration/schemaversion/v31_test.go create mode 100644 apps/dashboard/pkg/migration/schemaversion/v32.go create mode 100644 apps/dashboard/pkg/migration/schemaversion/v32_test.go create mode 100644 apps/dashboard/pkg/migration/testdata/input/v31.labels_to_fields_merge.json create mode 100644 apps/dashboard/pkg/migration/testdata/input/v32.no_op_migration.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/v31.labels_to_fields_merge.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/v32.no_op_migration.json diff --git a/apps/dashboard/pkg/migration/schemaversion/migrations.go b/apps/dashboard/pkg/migration/schemaversion/migrations.go index aeafd4ab892..296e892d573 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migrations.go +++ b/apps/dashboard/pkg/migration/schemaversion/migrations.go @@ -5,7 +5,7 @@ import ( ) const ( - MIN_VERSION = 32 + MIN_VERSION = 30 LATEST_VERSION = 41 ) @@ -26,6 +26,8 @@ type DataSourceInfoProvider interface { func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionMigrationFunc { return map[int]SchemaVersionMigrationFunc{ + 31: V31, + 32: V32, 33: V33(dsInfoProvider), 34: V34, 35: V35, diff --git a/apps/dashboard/pkg/migration/schemaversion/v31.go b/apps/dashboard/pkg/migration/schemaversion/v31.go new file mode 100644 index 00000000000..3b4c907ce5d --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v31.go @@ -0,0 +1,132 @@ +package schemaversion + +// V31 adds a merge transformer after any labelsToFields transformer in panel transformations. +// +// This migration addresses data processing workflow optimization by automatically inserting +// a merge transformation after the labelsToFields transformation. When the labelsToFields +// transformer converts time series labels to individual fields, it can result in multiple +// data frames that need to be consolidated for proper visualization and analysis. +// +// The migration works by: +// 1. Iterating through all panels in the dashboard, including nested panels in collapsed rows +// 2. Examining the transformations array within each panel +// 3. Identifying transformations with id 'labelsToFields' +// 4. Inserting a merge transformation with empty options immediately after each labelsToFields transformation +// 5. Preserving the original labelsToFields options (mode, keepLabels, valueLabel, etc.) without modification +// 6. Using empty options for merge transformations to enable optimal default consolidation behavior +// 7. Preserving the original order and configuration of all other transformations +// +// The migration handles complex scenarios including: +// - Multiple labelsToFields transformations within a single panel +// - Panels with mixed transformation types +// - Nested panels within collapsed row panels +// - Panels with no transformations (left unchanged) +// - Panels with transformations but no labelsToFields (left unchanged) +// +// Example transformation: +// +// Before migration: +// +// panel: { +// "transformations": [ +// { "id": "organize", "options": {} }, +// { "id": "labelsToFields", "options": {} }, +// { "id": "calculateField", "options": {} }, +// { "id": "labelsToFields", "options": { "mode": "rows", "keepLabels": ["job", "instance"] } }, +// ] +// } +// +// After migration: +// +// panel: { +// "transformations": [ +// { "id": "organize", "options": {} }, +// { "id": "labelsToFields", "options": {} }, +// { "id": "merge", "options": {} }, +// { "id": "calculateField", "options": {} }, +// { "id": "labelsToFields", "options": { "mode": "rows", "keepLabels": ["job", "instance"] } }, +// { "id": "merge", "options": {} } +// ] +// } +func V31(dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = int(31) + + panels, ok := dashboard["panels"].([]interface{}) + if !ok { + return nil + } + + // Process all panels, including nested ones + processPanelsV31(panels) + + return nil +} + +// processPanelsV31 recursively processes panels, including nested panels within rows +func processPanelsV31(panels []interface{}) { + for _, panel := range panels { + p, ok := panel.(map[string]interface{}) + if !ok { + continue + } + + // Process nested panels if this is a row panel + if p["type"] == "row" { + nestedPanels, hasNested := p["panels"].([]interface{}) + if !hasNested { + continue + } + processPanelsV31(nestedPanels) + continue + } + + transformations, ok := p["transformations"].([]interface{}) + if !ok { + continue + } + + // Check if we have any labelsToFields transformations + hasLabelsToFields := false + for _, transformation := range transformations { + t, ok := transformation.(map[string]interface{}) + if !ok { + continue + } + if t["id"] == "labelsToFields" { + hasLabelsToFields = true + break + } + } + + if !hasLabelsToFields { + continue + } + + // Create new transformations array with merge transformations added + newTransformations := []interface{}{} + + for _, transformation := range transformations { + t, ok := transformation.(map[string]interface{}) + if !ok { + newTransformations = append(newTransformations, transformation) + continue + } + + // Add the current transformation (preserving all original options) + newTransformations = append(newTransformations, transformation) + + // If this is a labelsToFields transformation, add a merge transformation after it + // with empty options to enable optimal default consolidation behavior + if t["id"] == "labelsToFields" { + mergeTransformation := map[string]interface{}{ + "id": "merge", + "options": map[string]interface{}{}, + } + newTransformations = append(newTransformations, mergeTransformation) + } + } + + // Update the panel with the new transformations + p["transformations"] = newTransformations + } +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v31_test.go b/apps/dashboard/pkg/migration/schemaversion/v31_test.go new file mode 100644 index 00000000000..e12040585de --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v31_test.go @@ -0,0 +1,336 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV31(t *testing.T) { + tests := []migrationTestCase{ + { + name: "panel with basic labelsToFields transformation gets merge transformation added", + input: map[string]interface{}{ + "title": "V31 LabelsToFields Migration Test Dashboard", + "schemaVersion": 30, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with basic labelsToFields", + "id": 1, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "labelsToFields", + "options": map[string]interface{}{}, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V31 LabelsToFields Migration Test Dashboard", + "schemaVersion": 31, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with basic labelsToFields", + "id": 1, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "labelsToFields", + "options": map[string]interface{}{}, + }, + map[string]interface{}{ + "id": "merge", + "options": map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + { + name: "panel with labelsToFields options preserved during migration", + input: map[string]interface{}{ + "title": "V31 LabelsToFields Options Preservation Test Dashboard", + "schemaVersion": 30, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with labelsToFields options", + "id": 1, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "labelsToFields", + "options": map[string]interface{}{ + "mode": "rows", + "keepLabels": []interface{}{"job", "instance"}, + "valueLabel": "value", + }, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V31 LabelsToFields Options Preservation Test Dashboard", + "schemaVersion": 31, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with labelsToFields options", + "id": 1, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "labelsToFields", + "options": map[string]interface{}{ + "mode": "rows", + "keepLabels": []interface{}{"job", "instance"}, + "valueLabel": "value", + }, + }, + map[string]interface{}{ + "id": "merge", + "options": map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + { + name: "panel with multiple labelsToFields transformations", + input: map[string]interface{}{ + "title": "V31 Multiple LabelsToFields Migration Test Dashboard", + "schemaVersion": 30, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with multiple labelsToFields", + "id": 1, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "organize", + "options": map[string]interface{}{}, + }, + map[string]interface{}{ + "id": "labelsToFields", + "options": map[string]interface{}{}, + }, + map[string]interface{}{ + "id": "calculateField", + "options": map[string]interface{}{}, + }, + map[string]interface{}{ + "id": "labelsToFields", + "options": map[string]interface{}{ + "mode": "rows", + }, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V31 Multiple LabelsToFields Migration Test Dashboard", + "schemaVersion": 31, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with multiple labelsToFields", + "id": 1, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "organize", + "options": map[string]interface{}{}, + }, + map[string]interface{}{ + "id": "labelsToFields", + "options": map[string]interface{}{}, + }, + map[string]interface{}{ + "id": "merge", + "options": map[string]interface{}{}, + }, + map[string]interface{}{ + "id": "calculateField", + "options": map[string]interface{}{}, + }, + map[string]interface{}{ + "id": "labelsToFields", + "options": map[string]interface{}{ + "mode": "rows", + }, + }, + map[string]interface{}{ + "id": "merge", + "options": map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + { + name: "panel with no transformations remains unchanged", + input: map[string]interface{}{ + "title": "V31 No Transformations Test Dashboard", + "schemaVersion": 30, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with no transformations", + "id": 1, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V31 No Transformations Test Dashboard", + "schemaVersion": 31, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with no transformations", + "id": 1, + }, + }, + }, + }, + { + name: "panel with transformations but no labelsToFields remains unchanged", + input: map[string]interface{}{ + "title": "V31 Other Transformations Test Dashboard", + "schemaVersion": 30, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with other transformations", + "id": 1, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "organize", + "options": map[string]interface{}{}, + }, + map[string]interface{}{ + "id": "reduce", + "options": map[string]interface{}{}, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V31 Other Transformations Test Dashboard", + "schemaVersion": 31, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel with other transformations", + "id": 1, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "organize", + "options": map[string]interface{}{}, + }, + map[string]interface{}{ + "id": "reduce", + "options": map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + { + name: "nested panels in row with labelsToFields transformation", + input: map[string]interface{}{ + "title": "V31 Nested Panels Test Dashboard", + "schemaVersion": 30, + "panels": []interface{}{ + map[string]interface{}{ + "type": "row", + "title": "Row with nested panels", + "id": 1, + "collapsed": false, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Nested panel with labelsToFields", + "id": 2, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "labelsToFields", + "options": map[string]interface{}{}, + }, + }, + }, + map[string]interface{}{ + "type": "timeseries", + "title": "Nested panel without labelsToFields", + "id": 3, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "organize", + "options": map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V31 Nested Panels Test Dashboard", + "schemaVersion": 31, + "panels": []interface{}{ + map[string]interface{}{ + "type": "row", + "title": "Row with nested panels", + "id": 1, + "collapsed": false, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Nested panel with labelsToFields", + "id": 2, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "labelsToFields", + "options": map[string]interface{}{}, + }, + map[string]interface{}{ + "id": "merge", + "options": map[string]interface{}{}, + }, + }, + }, + map[string]interface{}{ + "type": "timeseries", + "title": "Nested panel without labelsToFields", + "id": 3, + "transformations": []interface{}{ + map[string]interface{}{ + "id": "organize", + "options": map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "dashboard with no panels", + input: map[string]interface{}{ + "title": "V31 No Panels Test Dashboard", + "schemaVersion": 30, + }, + expected: map[string]interface{}{ + "title": "V31 No Panels Test Dashboard", + "schemaVersion": 31, + }, + }, + } + runMigrationTests(t, tests, schemaversion.V31) +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v32.go b/apps/dashboard/pkg/migration/schemaversion/v32.go new file mode 100644 index 00000000000..6ea48bf0a73 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v32.go @@ -0,0 +1,27 @@ +package schemaversion + +// V32 is a no-op migration that serves as a placeholder for consistency. +// +// The migration performs no modifications to the dashboard structure and simply +// updates the schema version number. +// Example (no changes made): +// +// Before migration: +// +// dashboard: { +// "title": "My Dashboard", +// "schemaVersion": 31, +// "panels": [...] +// } +// +// After migration: +// +// dashboard: { +// "title": "My Dashboard", +// "schemaVersion": 32, +// "panels": [...] // unchanged +// } +func V32(dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = int(32) + return nil +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v32_test.go b/apps/dashboard/pkg/migration/schemaversion/v32_test.go new file mode 100644 index 00000000000..23df4b26d28 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v32_test.go @@ -0,0 +1,38 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV32(t *testing.T) { + tests := []migrationTestCase{ + { + name: "v32 no-op migration updates schema version only", + input: map[string]interface{}{ + "title": "V32 No-Op Migration Test Dashboard", + "schemaVersion": 31, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel remains unchanged", + "id": 1, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V32 No-Op Migration Test Dashboard", + "schemaVersion": 32, + "panels": []interface{}{ + map[string]interface{}{ + "type": "timeseries", + "title": "Panel remains unchanged", + "id": 1, + }, + }, + }, + }, + } + runMigrationTests(t, tests, schemaversion.V32) +} diff --git a/apps/dashboard/pkg/migration/testdata/input/v31.labels_to_fields_merge.json b/apps/dashboard/pkg/migration/testdata/input/v31.labels_to_fields_merge.json new file mode 100644 index 00000000000..be959f93568 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v31.labels_to_fields_merge.json @@ -0,0 +1,126 @@ +{ + "title": "V31 LabelsToFields Merge Migration Test Dashboard", + "schemaVersion": 30, + "panels": [ + { + "type": "timeseries", + "title": "Panel with basic labelsToFields transformation", + "id": 1, + "transformations": [ + { + "id": "labelsToFields", + "options": {} + } + ] + }, + { + "type": "timeseries", + "title": "Panel with labelsToFields options preserved", + "id": 9, + "transformations": [ + { + "id": "labelsToFields", + "options": { + "mode": "rows", + "keepLabels": ["job", "instance", "region"], + "valueLabel": "value" + } + } + ] + }, + { + "type": "timeseries", + "title": "Panel with multiple labelsToFields transformations", + "id": 2, + "transformations": [ + { + "id": "organize", + "options": {} + }, + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "calculateField", + "options": {} + }, + { + "id": "labelsToFields", + "options": { + "mode": "rows" + } + } + ] + }, + { + "type": "timeseries", + "title": "Panel with no transformations", + "id": 3 + }, + { + "type": "timeseries", + "title": "Panel with other transformations only", + "id": 4, + "transformations": [ + { + "id": "organize", + "options": {} + }, + { + "id": "reduce", + "options": {} + } + ] + }, + { + "type": "row", + "title": "Row with nested panels", + "id": 5, + "collapsed": false, + "panels": [ + { + "type": "timeseries", + "title": "Nested panel with labelsToFields", + "id": 6, + "transformations": [ + { + "id": "labelsToFields", + "options": {} + } + ] + }, + { + "type": "timeseries", + "title": "Nested panel without labelsToFields", + "id": 7, + "transformations": [ + { + "id": "organize", + "options": {} + } + ] + } + ] + }, + { + "type": "timeseries", + "title": "Panel with labelsToFields and existing merge", + "id": 8, + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + }, + { + "id": "reduce", + "options": {} + } + ] + } + ] +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/input/v32.no_op_migration.json b/apps/dashboard/pkg/migration/testdata/input/v32.no_op_migration.json new file mode 100644 index 00000000000..a190ad11be0 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v32.no_op_migration.json @@ -0,0 +1,80 @@ +{ + "title": "V32 No-Op Migration Test Dashboard", + "schemaVersion": 31, + "panels": [ + { + "type": "timeseries", + "title": "Panel with transformations remains unchanged", + "id": 1, + "transformations": [ + { + "id": "labelsToFields", + "options": { + "mode": "rows", + "keepLabels": ["job", "instance"] + } + }, + { + "id": "merge", + "options": {} + } + ] + }, + { + "type": "graph", + "title": "Graph panel remains unchanged", + "id": 2, + "yAxes": [ + { + "show": true, + "min": null, + "max": null + } + ] + }, + { + "type": "row", + "title": "Row with nested panels", + "id": 3, + "collapsed": false, + "panels": [ + { + "type": "stat", + "title": "Nested stat panel", + "id": 4, + "fieldConfig": { + "defaults": { + "unit": "bytes" + } + } + } + ] + } + ], + "templating": { + "list": [ + { + "name": "environment", + "type": "query", + "datasource": "prometheus", + "options": [] + } + ] + }, + "annotations": { + "list": [ + { + "name": "Deployments", + "datasource": "grafana", + "enable": true + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/v31.labels_to_fields_merge.json b/apps/dashboard/pkg/migration/testdata/output/v31.labels_to_fields_merge.json new file mode 100644 index 00000000000..d48c4f46c2e --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/v31.labels_to_fields_merge.json @@ -0,0 +1,290 @@ +{ + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with basic labelsToFields transformation", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 9, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with labelsToFields options preserved", + "transformations": [ + { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "job", + "instance", + "region" + ], + "mode": "rows", + "valueLabel": "value" + } + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with multiple labelsToFields transformations", + "transformations": [ + { + "id": "organize", + "options": {} + }, + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + }, + { + "id": "calculateField", + "options": {} + }, + { + "id": "labelsToFields", + "options": { + "mode": "rows" + } + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with no transformations", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 4, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with other transformations only", + "transformations": [ + { + "id": "organize", + "options": {} + }, + { + "id": "reduce", + "options": {} + } + ], + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 5, + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 6, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Nested panel with labelsToFields", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 7, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Nested panel without labelsToFields", + "transformations": [ + { + "id": "organize", + "options": {} + } + ], + "type": "timeseries" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Row with nested panels", + "type": "row" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 8, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with labelsToFields and existing merge", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + }, + { + "id": "merge", + "options": {} + }, + { + "id": "reduce", + "options": {} + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 41, + "title": "V31 LabelsToFields Merge Migration Test Dashboard" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/v32.no_op_migration.json b/apps/dashboard/pkg/migration/testdata/output/v32.no_op_migration.json new file mode 100644 index 00000000000..ad9e5158fe4 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/v32.no_op_migration.json @@ -0,0 +1,159 @@ +{ + "annotations": { + "list": [ + { + "datasource": { + "uid": "grafana" + }, + "enable": true, + "name": "Deployments" + } + ] + }, + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with transformations remains unchanged", + "transformations": [ + { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "job", + "instance" + ], + "mode": "rows" + } + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph panel remains unchanged", + "type": "graph", + "yAxes": [ + { + "max": null, + "min": null, + "show": true + } + ] + }, + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "unit": "bytes" + } + }, + "id": 4, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Nested stat panel", + "type": "stat" + } + ], + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Row with nested panels", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 41, + "templating": { + "list": [ + { + "datasource": { + "uid": "prometheus" + }, + "name": "environment", + "options": [], + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "title": "V32 No-Op Migration Test Dashboard" +} \ No newline at end of file