From 5df7d43f8cb300c81709e80c0ff7733e371dfa48 Mon Sep 17 00:00:00 2001 From: Kristina Demeshchik Date: Tue, 13 Jan 2026 10:08:51 -0500 Subject: [PATCH] Update migration function to look into rows --- .../pkg/migration/frontend_defaults.go | 17 ++++ .../pkg/migration/frontend_defaults_test.go | 89 +++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/apps/dashboard/pkg/migration/frontend_defaults.go b/apps/dashboard/pkg/migration/frontend_defaults.go index e8c779406a6..9a9e6c72929 100644 --- a/apps/dashboard/pkg/migration/frontend_defaults.go +++ b/apps/dashboard/pkg/migration/frontend_defaults.go @@ -432,6 +432,23 @@ func getPanels(dashboard map[string]interface{}) []map[string]interface{} { } } + // Also get panels from rows (pre-v16 dashboard format) + // This is needed for ensurePanelsHaveUniqueIds to work correctly + // before the row upgrade migration runs + if rows, ok := dashboard["rows"].([]interface{}); ok { + for _, rowInterface := range rows { + if row, ok := rowInterface.(map[string]interface{}); ok { + if rowPanels, ok := row["panels"].([]interface{}); ok { + for _, panelInterface := range rowPanels { + if panel, ok := panelInterface.(map[string]interface{}); ok { + panels = append(panels, panel) + } + } + } + } + } + } + return panels } diff --git a/apps/dashboard/pkg/migration/frontend_defaults_test.go b/apps/dashboard/pkg/migration/frontend_defaults_test.go index 5bc126eacec..38fef696c1b 100644 --- a/apps/dashboard/pkg/migration/frontend_defaults_test.go +++ b/apps/dashboard/pkg/migration/frontend_defaults_test.go @@ -1802,6 +1802,95 @@ func TestEnsurePanelsHaveUniqueIds(t *testing.T) { }, }, }, + { + name: "assign_ids_considering_panels_in_rows", + input: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{"type": "text", "title": "Top level panel"}, // No ID + }, + "rows": []interface{}{ + map[string]interface{}{ + "title": "Row 1", + "panels": []interface{}{ + map[string]interface{}{"type": "timeseries", "id": float64(1), "title": "Row panel 1"}, + map[string]interface{}{"type": "table", "id": float64(2), "title": "Row panel 2"}, + }, + }, + }, + }, + expected: map[string]interface{}{ + "panels": []interface{}{ + // Should get ID 3 because 1 and 2 are already used in rows + map[string]interface{}{"type": "text", "title": "Top level panel", "id": float64(3)}, + }, + "rows": []interface{}{ + map[string]interface{}{ + "title": "Row 1", + "panels": []interface{}{ + map[string]interface{}{"type": "timeseries", "id": float64(1), "title": "Row panel 1"}, + map[string]interface{}{"type": "table", "id": float64(2), "title": "Row panel 2"}, + }, + }, + }, + }, + }, + { + name: "fix_duplicate_ids_across_panels_and_rows", + input: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{"type": "text", "id": float64(1), "title": "Top level panel"}, // ID 1 + }, + "rows": []interface{}{ + map[string]interface{}{ + "title": "Row 1", + "panels": []interface{}{ + map[string]interface{}{"type": "timeseries", "id": float64(1), "title": "Row panel 1"}, // Duplicate ID 1 + map[string]interface{}{"type": "table", "id": float64(2), "title": "Row panel 2"}, + }, + }, + }, + }, + expected: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{"type": "text", "id": float64(1), "title": "Top level panel"}, // Keeps ID 1 + }, + "rows": []interface{}{ + map[string]interface{}{ + "title": "Row 1", + "panels": []interface{}{ + // Should get new ID 3 because 1 is already used + map[string]interface{}{"type": "timeseries", "id": float64(3), "title": "Row panel 1"}, + map[string]interface{}{"type": "table", "id": float64(2), "title": "Row panel 2"}, + }, + }, + }, + }, + }, + { + name: "assign_ids_to_row_panels_without_ids", + input: map[string]interface{}{ + "rows": []interface{}{ + map[string]interface{}{ + "title": "Row 1", + "panels": []interface{}{ + map[string]interface{}{"type": "timeseries", "title": "Row panel 1"}, // No ID + map[string]interface{}{"type": "table", "title": "Row panel 2"}, // No ID + }, + }, + }, + }, + expected: map[string]interface{}{ + "rows": []interface{}{ + map[string]interface{}{ + "title": "Row 1", + "panels": []interface{}{ + map[string]interface{}{"type": "timeseries", "title": "Row panel 1", "id": float64(1)}, + map[string]interface{}{"type": "table", "title": "Row panel 2", "id": float64(2)}, + }, + }, + }, + }, + }, } for _, tt := range tests {