Fix dashboard migration discrepancies between backend and frontend implementations (use toEqual) (#110268)
**Highlights**
* **Single-version migrations**: add `targetVersion` to migrator & model, separate outputs, enforce exact version.
* **Datasource fixes**: include `apiVersion` in tests, empty-string → `{}`, preserve `{}` refs, drop unwanted defaults.
* **Panel defaults & nesting**: only top-level panels get defaults; preserve empty `transformations` context-aware; filter repeated panels.
* **Migration parity**
* V16: collapsed rows, grid height parsing (`px`).
* V17: omit `maxPerRow` when `minSpan=1`.
* V19–V20: cleanup defaults (`targetBlank`, style).
* V23–V24: template vars + table panel consistency.
* V28: full singlestat/stat parity, mappings & color.
* V30–V36: threshold logic, empty refs, nested targets.
* **Save-model cleanup**: replicate frontend defaults/filtering, drop null IDs, metadata, unused props.
* **Testing**: unified suites, dev dashboards (v42), full unit coverage for major migrations.
Co-authored-by: Ivan Ortega [ivanortegaalba@gmail.com](mailto:ivanortegaalba@gmail.com)
Co-authored-by: Dominik Prokop [dominik.prokop@grafana.com](mailto:dominik.prokop@grafana.com)
This commit is contained in:
co-authored by
Ivan Ortega [ivanortegaalba@gmail.com](mailto:ivanortegaalba@gmail.com)
Dominik Prokop [dominik.prokop@grafana.com](mailto:dominik.prokop@grafana.com)
parent
98fd3e8fe9
commit
a72e02f88a
@@ -1,851 +1,85 @@
|
||||
package schemaversion_test
|
||||
package schemaversion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
|
||||
)
|
||||
|
||||
const (
|
||||
// The pluginVersion to set after simulating auto-migrate for angular panels
|
||||
pluginVersionForAutoMigrate = "12.1.0"
|
||||
)
|
||||
|
||||
func TestV24(t *testing.T) {
|
||||
tests := []migrationTestCase{
|
||||
func TestV24TablePanelMigration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input map[string]interface{}
|
||||
expected map[string]interface{}
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "should migrate basic Angular table with defaults",
|
||||
name: "preserve_empty_display_name",
|
||||
input: map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{
|
||||
"type": "table",
|
||||
"styles": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"type": "table",
|
||||
"title": "Basic Table",
|
||||
"legend": true,
|
||||
"styles": []interface{}{
|
||||
map[string]interface{}{
|
||||
"thresholds": []interface{}{"10", "20", "30"},
|
||||
"colors": []interface{}{"red", "yellow", "green"},
|
||||
"pattern": "/.*/",
|
||||
},
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
"alias": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"schemaVersion": 24,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"type": "table",
|
||||
"title": "Basic Table",
|
||||
"legend": true,
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"defaults": map[string]interface{}{
|
||||
"custom": map[string]interface{}{
|
||||
"align": "auto",
|
||||
"cellOptions": map[string]interface{}{
|
||||
"type": "auto",
|
||||
},
|
||||
"footer": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
"inspect": false,
|
||||
},
|
||||
"mappings": []interface{}{},
|
||||
"thresholds": map[string]interface{}{
|
||||
"mode": "absolute",
|
||||
"steps": []interface{}{
|
||||
map[string]interface{}{"value": nil, "color": "red"},
|
||||
map[string]interface{}{"value": float64(10), "color": "yellow"},
|
||||
map[string]interface{}{"value": float64(20), "color": "green"},
|
||||
map[string]interface{}{"value": float64(30), "color": "red"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"overrides": []interface{}{},
|
||||
},
|
||||
"options": map[string]interface{}{
|
||||
"cellHeight": "sm",
|
||||
"footer": map[string]interface{}{
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": []interface{}{"sum"},
|
||||
"show": false,
|
||||
},
|
||||
"showHeader": true,
|
||||
},
|
||||
"transformations": []interface{}{},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
"pluginVersion": pluginVersionForAutoMigrate,
|
||||
"type": "table",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"defaults": map[string]interface{}{
|
||||
"displayName": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
description: "Empty displayName values should be preserved when migrating from empty alias in table panel styles",
|
||||
},
|
||||
{
|
||||
name: "should migrate table with complex defaults and overrides",
|
||||
name: "do_not_add_empty_transformations",
|
||||
input: map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"type": "table",
|
||||
"title": "Complex Table",
|
||||
"styles": []interface{}{
|
||||
// Default style
|
||||
map[string]interface{}{
|
||||
"pattern": "/.*/",
|
||||
"unit": "bytes",
|
||||
"decimals": float64(2),
|
||||
"align": "center",
|
||||
"colorMode": "cell",
|
||||
"thresholds": []interface{}{"100", "500"},
|
||||
"colors": []interface{}{"green", "yellow", "red"},
|
||||
},
|
||||
// Column-specific override with exact name
|
||||
map[string]interface{}{
|
||||
"pattern": "Status",
|
||||
"alias": "Current Status",
|
||||
"unit": "short",
|
||||
"decimals": float64(0),
|
||||
"colorMode": "value",
|
||||
"align": "left",
|
||||
},
|
||||
// Column-specific override with regex pattern
|
||||
map[string]interface{}{
|
||||
"pattern": "/Error.*/",
|
||||
"link": true,
|
||||
"linkUrl": "http://example.com/errors",
|
||||
"linkTooltip": "View error details",
|
||||
"linkTargetBlank": true,
|
||||
"colorMode": "row",
|
||||
"colors": []interface{}{"red", "orange"},
|
||||
},
|
||||
// Date column
|
||||
map[string]interface{}{
|
||||
"pattern": "Time",
|
||||
"type": "date",
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"alias": "Timestamp",
|
||||
},
|
||||
// Hidden column
|
||||
map[string]interface{}{
|
||||
"pattern": "Hidden",
|
||||
"type": "hidden",
|
||||
},
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": "table",
|
||||
"title": "Test Table",
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"schemaVersion": 24,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"type": "table",
|
||||
"title": "Complex Table",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"defaults": map[string]interface{}{
|
||||
"unit": "bytes",
|
||||
"decimals": 2,
|
||||
"custom": map[string]interface{}{
|
||||
"align": "center",
|
||||
"cellOptions": map[string]interface{}{
|
||||
"type": "color-background",
|
||||
},
|
||||
"footer": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
"inspect": false,
|
||||
},
|
||||
"mappings": []interface{}{},
|
||||
"thresholds": map[string]interface{}{
|
||||
"mode": "absolute",
|
||||
"steps": []interface{}{
|
||||
map[string]interface{}{"value": nil, "color": "green"},
|
||||
map[string]interface{}{"value": float64(100), "color": "yellow"},
|
||||
map[string]interface{}{"value": float64(500), "color": "red"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"matcher": map[string]interface{}{
|
||||
"id": "byName",
|
||||
"options": "Status",
|
||||
},
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{"id": "displayName", "value": "Current Status"},
|
||||
map[string]interface{}{"id": "unit", "value": "short"},
|
||||
map[string]interface{}{"id": "decimals", "value": 0},
|
||||
map[string]interface{}{"id": "custom.cellOptions", "value": map[string]interface{}{"type": "color-text"}},
|
||||
map[string]interface{}{"id": "custom.align", "value": "left"},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"matcher": map[string]interface{}{
|
||||
"id": "byRegexp",
|
||||
"options": "/Error.*/",
|
||||
},
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "links",
|
||||
"value": []interface{}{
|
||||
map[string]interface{}{
|
||||
"title": "View error details",
|
||||
"url": "http://example.com/errors",
|
||||
"targetBlank": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{"id": "custom.cellOptions", "value": map[string]interface{}{"type": "color-background"}},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"matcher": map[string]interface{}{
|
||||
"id": "byName",
|
||||
"options": "Time",
|
||||
},
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{"id": "displayName", "value": "Timestamp"},
|
||||
map[string]interface{}{"id": "unit", "value": "time: YYYY-MM-DD HH:mm:ss"},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"matcher": map[string]interface{}{
|
||||
"id": "byName",
|
||||
"options": "Hidden",
|
||||
},
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{"id": "custom.hideFrom.viz", "value": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"options": map[string]interface{}{
|
||||
"cellHeight": "sm",
|
||||
"footer": map[string]interface{}{
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": []interface{}{"sum"},
|
||||
"show": false,
|
||||
},
|
||||
"showHeader": true,
|
||||
},
|
||||
"transformations": []interface{}{},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
"pluginVersion": pluginVersionForAutoMigrate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should migrate table with timeseries_aggregations transform",
|
||||
input: map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 3,
|
||||
"type": "table",
|
||||
"title": "Table with Aggregations",
|
||||
"styles": []interface{}{
|
||||
map[string]interface{}{
|
||||
"pattern": "/.*/",
|
||||
"unit": "percent",
|
||||
"decimals": float64(1),
|
||||
},
|
||||
},
|
||||
"transform": "timeseries_aggregations",
|
||||
"columns": []interface{}{
|
||||
map[string]interface{}{"value": "avg", "text": "Average"},
|
||||
map[string]interface{}{"value": "max", "text": "Maximum"},
|
||||
map[string]interface{}{"value": "min", "text": "Minimum"},
|
||||
map[string]interface{}{"value": "total", "text": "Total"},
|
||||
map[string]interface{}{"value": "current", "text": "Current"},
|
||||
map[string]interface{}{"value": "count", "text": "Count"},
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"schemaVersion": 24,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 3,
|
||||
"type": "table",
|
||||
"title": "Table with Aggregations",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"defaults": map[string]interface{}{
|
||||
"unit": "percent",
|
||||
"decimals": 1,
|
||||
"custom": map[string]interface{}{
|
||||
"align": "auto",
|
||||
"cellOptions": map[string]interface{}{
|
||||
"type": "auto",
|
||||
},
|
||||
"footer": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
"inspect": false,
|
||||
},
|
||||
"mappings": []interface{}{},
|
||||
"thresholds": map[string]interface{}{
|
||||
"mode": "absolute",
|
||||
"steps": []interface{}{
|
||||
map[string]interface{}{"color": "green"},
|
||||
map[string]interface{}{"color": "red", "value": 80},
|
||||
},
|
||||
},
|
||||
},
|
||||
"overrides": []interface{}{},
|
||||
},
|
||||
"options": map[string]interface{}{
|
||||
"cellHeight": "sm",
|
||||
"footer": map[string]interface{}{
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": []interface{}{"sum"},
|
||||
"show": false,
|
||||
},
|
||||
"showHeader": true,
|
||||
},
|
||||
"transformations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "reduce",
|
||||
"options": map[string]interface{}{
|
||||
"reducers": []interface{}{"mean", "max", "min", "sum", "lastNotNull", "count"},
|
||||
"includeTimeField": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
"pluginVersion": pluginVersionForAutoMigrate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should migrate table with timeseries_to_rows transform",
|
||||
input: map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 4,
|
||||
"type": "table",
|
||||
"title": "Table with Rows Transform",
|
||||
"styles": []interface{}{
|
||||
map[string]interface{}{
|
||||
"pattern": "/.*/",
|
||||
"unit": "short",
|
||||
},
|
||||
},
|
||||
"transform": "timeseries_to_rows",
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"schemaVersion": 24,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 4,
|
||||
"type": "table",
|
||||
"title": "Table with Rows Transform",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"defaults": map[string]interface{}{
|
||||
"unit": "short",
|
||||
"custom": map[string]interface{}{
|
||||
"align": "auto",
|
||||
"cellOptions": map[string]interface{}{
|
||||
"type": "auto",
|
||||
},
|
||||
"footer": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
"inspect": false,
|
||||
},
|
||||
"mappings": []interface{}{},
|
||||
"thresholds": map[string]interface{}{
|
||||
"mode": "absolute",
|
||||
"steps": []interface{}{
|
||||
map[string]interface{}{"color": "green"},
|
||||
map[string]interface{}{"color": "red", "value": 80},
|
||||
},
|
||||
},
|
||||
},
|
||||
"overrides": []interface{}{},
|
||||
},
|
||||
"options": map[string]interface{}{
|
||||
"cellHeight": "sm",
|
||||
"footer": map[string]interface{}{
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": []interface{}{"sum"},
|
||||
"show": false,
|
||||
},
|
||||
"showHeader": true,
|
||||
},
|
||||
"transformations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "seriesToRows",
|
||||
"options": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
},
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
"pluginVersion": pluginVersionForAutoMigrate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should migrate table with timeseries_to_columns transform",
|
||||
input: map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 5,
|
||||
"type": "table",
|
||||
"title": "Table with Columns Transform",
|
||||
"styles": []interface{}{
|
||||
map[string]interface{}{
|
||||
"pattern": "/.*/",
|
||||
"unit": "bytes",
|
||||
},
|
||||
},
|
||||
"transform": "timeseries_to_columns",
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"schemaVersion": 24,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 5,
|
||||
"type": "table",
|
||||
"title": "Table with Columns Transform",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"defaults": map[string]interface{}{
|
||||
"unit": "bytes",
|
||||
"custom": map[string]interface{}{
|
||||
"align": "auto",
|
||||
"cellOptions": map[string]interface{}{
|
||||
"type": "auto",
|
||||
},
|
||||
"footer": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
"inspect": false,
|
||||
},
|
||||
"mappings": []interface{}{},
|
||||
"thresholds": map[string]interface{}{
|
||||
"mode": "absolute",
|
||||
"steps": []interface{}{
|
||||
map[string]interface{}{"color": "green"},
|
||||
map[string]interface{}{"color": "red", "value": 80},
|
||||
},
|
||||
},
|
||||
},
|
||||
"overrides": []interface{}{},
|
||||
},
|
||||
"options": map[string]interface{}{
|
||||
"cellHeight": "sm",
|
||||
"footer": map[string]interface{}{
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": []interface{}{"sum"},
|
||||
"show": false,
|
||||
},
|
||||
"showHeader": true,
|
||||
},
|
||||
"transformations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "seriesToColumns",
|
||||
"options": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
},
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
"pluginVersion": pluginVersionForAutoMigrate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should migrate table with table merge transform",
|
||||
input: map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 6,
|
||||
"type": "table",
|
||||
"title": "Table with Merge Transform",
|
||||
"styles": []interface{}{
|
||||
map[string]interface{}{
|
||||
"pattern": "/.*/",
|
||||
"align": "auto",
|
||||
},
|
||||
},
|
||||
"transform": "table",
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"schemaVersion": 24,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 6,
|
||||
"type": "table",
|
||||
"title": "Table with Merge Transform",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"defaults": map[string]interface{}{
|
||||
"custom": map[string]interface{}{
|
||||
"align": "",
|
||||
"cellOptions": map[string]interface{}{
|
||||
"type": "auto",
|
||||
},
|
||||
"footer": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
"inspect": false,
|
||||
},
|
||||
"mappings": []interface{}{},
|
||||
"thresholds": map[string]interface{}{
|
||||
"mode": "absolute",
|
||||
"steps": []interface{}{
|
||||
map[string]interface{}{"color": "green"},
|
||||
map[string]interface{}{"color": "red", "value": 80},
|
||||
},
|
||||
},
|
||||
},
|
||||
"overrides": []interface{}{},
|
||||
},
|
||||
"options": map[string]interface{}{
|
||||
"cellHeight": "sm",
|
||||
"footer": map[string]interface{}{
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": []interface{}{"sum"},
|
||||
"show": false,
|
||||
},
|
||||
"showHeader": true,
|
||||
},
|
||||
"transformations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "merge",
|
||||
"options": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
},
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
"pluginVersion": pluginVersionForAutoMigrate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should migrate table with existing transformations",
|
||||
input: map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 7,
|
||||
"type": "table",
|
||||
"title": "Table with Existing Transformations",
|
||||
"styles": []interface{}{
|
||||
map[string]interface{}{
|
||||
"pattern": "/.*/",
|
||||
"unit": "short",
|
||||
},
|
||||
},
|
||||
"transformations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "filterFieldsByName",
|
||||
"options": map[string]interface{}{
|
||||
"include": map[string]interface{}{
|
||||
"names": []interface{}{"field1", "field2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"transform": "timeseries_to_rows",
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"schemaVersion": 24,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 7,
|
||||
"type": "table",
|
||||
"title": "Table with Existing Transformations",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"defaults": map[string]interface{}{
|
||||
"unit": "short",
|
||||
"custom": map[string]interface{}{
|
||||
"align": "auto",
|
||||
"cellOptions": map[string]interface{}{
|
||||
"type": "auto",
|
||||
},
|
||||
"footer": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
"inspect": false,
|
||||
},
|
||||
"mappings": []interface{}{},
|
||||
"thresholds": map[string]interface{}{
|
||||
"mode": "absolute",
|
||||
"steps": []interface{}{
|
||||
map[string]interface{}{"color": "green"},
|
||||
map[string]interface{}{"color": "red", "value": 80},
|
||||
},
|
||||
},
|
||||
},
|
||||
"overrides": []interface{}{},
|
||||
},
|
||||
"options": map[string]interface{}{
|
||||
"cellHeight": "sm",
|
||||
"footer": map[string]interface{}{
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": []interface{}{"sum"},
|
||||
"show": false,
|
||||
},
|
||||
"showHeader": true,
|
||||
},
|
||||
"transformations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "filterFieldsByName",
|
||||
"options": map[string]interface{}{
|
||||
"include": map[string]interface{}{
|
||||
"names": []interface{}{"field1", "field2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": "seriesToRows",
|
||||
"options": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
},
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
"pluginVersion": pluginVersionForAutoMigrate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should not migrate angular table without styles",
|
||||
input: map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 8,
|
||||
"type": "table",
|
||||
"title": "Table without styles",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"schemaVersion": 24,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 8,
|
||||
"type": "table",
|
||||
"title": "Table without styles",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should not migrate react table (table2)",
|
||||
input: map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 9,
|
||||
"type": "table",
|
||||
"table": "table2",
|
||||
"title": "React table",
|
||||
"styles": []interface{}{
|
||||
map[string]interface{}{
|
||||
"pattern": "/.*/",
|
||||
"unit": "short",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"schemaVersion": 24,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 9,
|
||||
"type": "table",
|
||||
"table": "table2",
|
||||
"title": "React table",
|
||||
"styles": []interface{}{
|
||||
map[string]interface{}{
|
||||
"pattern": "/.*/",
|
||||
"unit": "short",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should not migrate non-table panels",
|
||||
input: map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 10,
|
||||
"type": "graph",
|
||||
"title": "Graph panel",
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 11,
|
||||
"type": "singlestat",
|
||||
"title": "Singlestat panel",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"schemaVersion": 24,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 10,
|
||||
"type": "graph",
|
||||
"title": "Graph panel",
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 11,
|
||||
"type": "singlestat",
|
||||
"title": "Singlestat panel",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should handle mixed numeric and string thresholds",
|
||||
input: map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 12,
|
||||
"type": "table",
|
||||
"title": "Mixed threshold types",
|
||||
"styles": []interface{}{
|
||||
map[string]interface{}{
|
||||
"pattern": "/.*/",
|
||||
"thresholds": []interface{}{10, "20", 30.5},
|
||||
"colors": []interface{}{"green", "yellow", "orange", "red"},
|
||||
},
|
||||
},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"schemaVersion": 24,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 12,
|
||||
"type": "table",
|
||||
"title": "Mixed threshold types",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"defaults": map[string]interface{}{
|
||||
"custom": map[string]interface{}{
|
||||
"align": "auto",
|
||||
"cellOptions": map[string]interface{}{
|
||||
"type": "auto",
|
||||
},
|
||||
"footer": map[string]interface{}{
|
||||
"reducers": []interface{}{},
|
||||
},
|
||||
"inspect": false,
|
||||
},
|
||||
"mappings": []interface{}{},
|
||||
"thresholds": map[string]interface{}{
|
||||
"mode": "absolute",
|
||||
"steps": []interface{}{
|
||||
map[string]interface{}{"value": nil, "color": "green"},
|
||||
map[string]interface{}{"value": float64(10), "color": "yellow"},
|
||||
map[string]interface{}{"value": float64(20), "color": "orange"},
|
||||
map[string]interface{}{"value": float64(30.5), "color": "red"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"overrides": []interface{}{},
|
||||
},
|
||||
"options": map[string]interface{}{
|
||||
"cellHeight": "sm",
|
||||
"footer": map[string]interface{}{
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": []interface{}{"sum"},
|
||||
"show": false,
|
||||
},
|
||||
"showHeader": true,
|
||||
},
|
||||
"transformations": []interface{}{},
|
||||
"targets": []interface{}{
|
||||
map[string]interface{}{"refId": "A"},
|
||||
},
|
||||
"pluginVersion": pluginVersionForAutoMigrate,
|
||||
},
|
||||
},
|
||||
"type": "table",
|
||||
"title": "Test Table",
|
||||
},
|
||||
description: "V24 migration should not add empty transformations arrays to table panels",
|
||||
},
|
||||
}
|
||||
|
||||
runMigrationTests(t, tests, schemaversion.V24)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dashboard := map[string]interface{}{
|
||||
"schemaVersion": 23,
|
||||
"panels": []interface{}{tt.input},
|
||||
}
|
||||
|
||||
err := V24(context.Background(), dashboard)
|
||||
if err != nil {
|
||||
t.Fatalf("V24 migration failed: %v", err)
|
||||
}
|
||||
|
||||
if dashboard["schemaVersion"] != 24 {
|
||||
t.Errorf("Expected schemaVersion to be 24, got %v", dashboard["schemaVersion"])
|
||||
}
|
||||
|
||||
panels, ok := dashboard["panels"].([]interface{})
|
||||
if !ok || len(panels) == 0 {
|
||||
t.Fatalf("Expected panels array with at least one panel")
|
||||
}
|
||||
|
||||
panel, ok := panels[0].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected panel to be a map")
|
||||
}
|
||||
|
||||
// Check that transformations array is not added if it wasn't in input
|
||||
if _, hasTransformations := tt.input["transformations"]; !hasTransformations {
|
||||
if _, exists := panel["transformations"]; exists {
|
||||
t.Errorf("Empty transformations array should not be added")
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("✓ %s: %s", tt.name, tt.description)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user