Dashboard Migrations: V2 - legacy services.filter and graphite panel type (#112282)
* migrate to v2 * Fix tests * graphite panels should be auto-migrated * lint * Provisioning: Fix dashboard export to preserve original API version * Add error handling for the edge case where conversion fails but no storedVersion is available.
This commit is contained in:
@@ -946,32 +946,13 @@ func applyPanelAutoMigration(panel map[string]interface{}) {
|
||||
var newType string
|
||||
|
||||
// Graph needs special logic as it can be migrated to multiple panels
|
||||
if panelType == "graph" {
|
||||
// Including graphite which was previously migrated to graph in the schema version 2 migration in DashboardMigrator.ts
|
||||
// but this was a bug because in there graphite was set to graph, but since those migrations run
|
||||
// after PanelModel.restoreModel where autoMigrateFrom is set, this caused the graph migration to be skipped.
|
||||
// And this resulted in a dashboard with invalid panels.
|
||||
if panelType == "graph" || panelType == "graphite" {
|
||||
// Check xaxis mode for special cases
|
||||
if xaxis, ok := panel["xaxis"].(map[string]interface{}); ok {
|
||||
if mode, ok := xaxis["mode"].(string); ok {
|
||||
switch mode {
|
||||
case "series":
|
||||
// Check legend values for bargauge
|
||||
if legend, ok := panel["legend"].(map[string]interface{}); ok {
|
||||
if values, ok := legend["values"].(bool); ok && values {
|
||||
newType = "bargauge"
|
||||
} else {
|
||||
newType = "barchart"
|
||||
}
|
||||
} else {
|
||||
newType = "barchart"
|
||||
}
|
||||
case "histogram":
|
||||
newType = "histogram"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default graph migration to timeseries
|
||||
if newType == "" {
|
||||
newType = "timeseries"
|
||||
}
|
||||
newType = getGraphAutoMigration(panel)
|
||||
} else {
|
||||
// Check autoMigrateAngular mapping
|
||||
autoMigrateAngular := map[string]string{
|
||||
@@ -995,6 +976,36 @@ func applyPanelAutoMigration(panel map[string]interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func getGraphAutoMigration(panel map[string]interface{}) string {
|
||||
newType := ""
|
||||
if xaxis, ok := panel["xaxis"].(map[string]interface{}); ok {
|
||||
if mode, ok := xaxis["mode"].(string); ok {
|
||||
switch mode {
|
||||
case "series":
|
||||
// Check legend values for bargauge
|
||||
if legend, ok := panel["legend"].(map[string]interface{}); ok {
|
||||
if values, ok := legend["values"].(bool); ok && values {
|
||||
newType = "bargauge"
|
||||
} else {
|
||||
newType = "barchart"
|
||||
}
|
||||
} else {
|
||||
newType = "barchart"
|
||||
}
|
||||
case "histogram":
|
||||
newType = "histogram"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default graph migration to timeseries
|
||||
if newType == "" {
|
||||
newType = "timeseries"
|
||||
}
|
||||
|
||||
return newType
|
||||
}
|
||||
|
||||
// removeNullValuesRecursively removes null values from nested objects and arrays
|
||||
// This matches the frontend's JSON.stringify/parse behavior
|
||||
func removeNullValuesRecursively(data interface{}) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
MIN_VERSION = 2
|
||||
MIN_VERSION = 0
|
||||
LATEST_VERSION = 42
|
||||
)
|
||||
|
||||
@@ -35,6 +35,7 @@ type PanelPluginInfo struct {
|
||||
|
||||
func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionMigrationFunc {
|
||||
return map[int]SchemaVersionMigrationFunc{
|
||||
2: V2,
|
||||
3: V3,
|
||||
4: V4,
|
||||
5: V5,
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package schemaversion
|
||||
|
||||
import "context"
|
||||
|
||||
// V2 migrates dashboard from schema version 0 or 1 to 2.
|
||||
// This migration handles the legacy services.filter structure.
|
||||
// It matches the frontend DashboardMigrator.ts logic for oldVersion < 2 && finalTargetVersion >= 2.
|
||||
//
|
||||
// Key migrations:
|
||||
// 1. Services filter migration: old.services.filter.time -> dashboard.time
|
||||
// 2. Services filter migration: old.services.filter.list -> dashboard.templating.list
|
||||
//
|
||||
// Example before migration:
|
||||
//
|
||||
// {
|
||||
// "schemaVersion": 1,
|
||||
// "services": {
|
||||
// "filter": {
|
||||
// "time": {"from": "now-1h", "to": "now"},
|
||||
// "list": [{"name": "var1", "type": "query"}]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Example after migration:
|
||||
//
|
||||
// {
|
||||
// "schemaVersion": 2,
|
||||
// "time": {"from": "now-1h", "to": "now"},
|
||||
// "templating": {
|
||||
// "list": [{"name": "var1", "type": "query"}]
|
||||
// }
|
||||
// }
|
||||
func V2(_ context.Context, dashboard map[string]interface{}) error {
|
||||
dashboard["schemaVersion"] = 2
|
||||
|
||||
// Migrate services.filter structure
|
||||
migrateServicesFilter(dashboard)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateServicesFilter migrates the legacy services.filter structure
|
||||
func migrateServicesFilter(dashboard map[string]interface{}) {
|
||||
services, ok := dashboard["services"].(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
filter, ok := services["filter"].(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Migrate time property
|
||||
if time, ok := filter["time"]; ok {
|
||||
dashboard["time"] = time
|
||||
}
|
||||
|
||||
// Migrate templating list
|
||||
if list, ok := filter["list"]; ok {
|
||||
if _, exists := dashboard["templating"]; !exists {
|
||||
dashboard["templating"] = map[string]interface{}{}
|
||||
}
|
||||
templating := dashboard["templating"].(map[string]interface{})
|
||||
templating["list"] = list
|
||||
}
|
||||
|
||||
// Remove the services property after migration
|
||||
delete(dashboard, "services")
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package schemaversion_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
|
||||
)
|
||||
|
||||
func TestV2(t *testing.T) {
|
||||
tests := []migrationTestCase{
|
||||
{
|
||||
name: "services filter migration moves time and templating list",
|
||||
input: map[string]interface{}{
|
||||
"title": "V2 Services Filter Migration Test",
|
||||
"schemaVersion": 1,
|
||||
"services": map[string]interface{}{
|
||||
"filter": map[string]interface{}{
|
||||
"time": map[string]interface{}{
|
||||
"from": "now-1h",
|
||||
"to": "now",
|
||||
},
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "var1",
|
||||
"type": "query",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V2 Services Filter Migration Test",
|
||||
"schemaVersion": 2,
|
||||
"time": map[string]interface{}{
|
||||
"from": "now-1h",
|
||||
"to": "now",
|
||||
},
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "var1",
|
||||
"type": "query",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "comprehensive services filter migration",
|
||||
input: map[string]interface{}{
|
||||
"title": "V2 Comprehensive Services Migration Test",
|
||||
"schemaVersion": 1,
|
||||
"services": map[string]interface{}{
|
||||
"filter": map[string]interface{}{
|
||||
"time": map[string]interface{}{
|
||||
"from": "now-6h",
|
||||
"to": "now",
|
||||
},
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "server",
|
||||
"type": "query",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"type": "graphite",
|
||||
"title": "CPU Usage",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V2 Comprehensive Services Migration Test",
|
||||
"schemaVersion": 2,
|
||||
"time": map[string]interface{}{
|
||||
"from": "now-6h",
|
||||
"to": "now",
|
||||
},
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "server",
|
||||
"type": "query",
|
||||
},
|
||||
},
|
||||
},
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"type": "graphite", // Panel types are not migrated by V2 anymore
|
||||
"title": "CPU Usage",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dashboard with no services or panels",
|
||||
input: map[string]interface{}{
|
||||
"title": "V2 Minimal Migration Test",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V2 Minimal Migration Test",
|
||||
"schemaVersion": 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "services filter with only time (no list)",
|
||||
input: map[string]interface{}{
|
||||
"title": "V2 Services Time Only Test",
|
||||
"schemaVersion": 1,
|
||||
"services": map[string]interface{}{
|
||||
"filter": map[string]interface{}{
|
||||
"time": map[string]interface{}{
|
||||
"from": "now-2h",
|
||||
"to": "now",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V2 Services Time Only Test",
|
||||
"schemaVersion": 2,
|
||||
"time": map[string]interface{}{
|
||||
"from": "now-2h",
|
||||
"to": "now",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "services filter with only list (no time)",
|
||||
input: map[string]interface{}{
|
||||
"title": "V2 Services List Only Test",
|
||||
"schemaVersion": 1,
|
||||
"services": map[string]interface{}{
|
||||
"filter": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "env",
|
||||
"type": "custom",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V2 Services List Only Test",
|
||||
"schemaVersion": 2,
|
||||
"templating": map[string]interface{}{
|
||||
"list": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "env",
|
||||
"type": "custom",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "panels remain unchanged when no services filter",
|
||||
input: map[string]interface{}{
|
||||
"title": "V2 Panels Unchanged Test",
|
||||
"schemaVersion": 1,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"type": "graphite",
|
||||
"legend": true,
|
||||
"y_format": "short",
|
||||
"y2_format": "bytes",
|
||||
"grid": map[string]interface{}{
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"type": "graph",
|
||||
"legend": map[string]interface{}{
|
||||
"show": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "V2 Panels Unchanged Test",
|
||||
"schemaVersion": 2,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"type": "graphite", // Panel migrations handled by auto-migration
|
||||
"legend": true,
|
||||
"y_format": "short",
|
||||
"y2_format": "bytes",
|
||||
"grid": map[string]interface{}{
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"type": "graph",
|
||||
"legend": map[string]interface{}{
|
||||
"show": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runMigrationTests(t, tests, schemaversion.V2)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"title": "V2 Comprehensive Migration Test Dashboard",
|
||||
"services": {
|
||||
"filter": {
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"name": "server",
|
||||
"type": "query",
|
||||
"datasource": "prometheus",
|
||||
"query": "label_values(server)"
|
||||
},
|
||||
{
|
||||
"name": "env",
|
||||
"type": "custom",
|
||||
"options": [
|
||||
{"text": "Production", "value": "prod"},
|
||||
{"text": "Staging", "value": "stage"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "graphite",
|
||||
"title": "CPU Usage",
|
||||
"legend": true,
|
||||
"grid": {
|
||||
"min": 0,
|
||||
"max": 100
|
||||
},
|
||||
"y_format": "percent",
|
||||
"y2_format": "short",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"target": "cpu.usage"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "graph",
|
||||
"title": "Memory Usage",
|
||||
"legend": false,
|
||||
"grid": {
|
||||
"min": 0
|
||||
},
|
||||
"y_format": "bytes",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"target": "memory.usage"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "table",
|
||||
"title": "Server Stats",
|
||||
"legend": true,
|
||||
"grid": {
|
||||
"min": 0,
|
||||
"max": 100
|
||||
},
|
||||
"y_format": "short",
|
||||
"y2_format": "bytes"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "graphite",
|
||||
"title": "Disk I/O",
|
||||
"legend": true,
|
||||
"y2_format": "Bps",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"target": "disk.io"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+168
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"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": [
|
||||
{
|
||||
"autoMigrateFrom": "graphite",
|
||||
"datasource": {
|
||||
"apiVersion": "v1",
|
||||
"type": "prometheus",
|
||||
"uid": "default-ds-uid"
|
||||
},
|
||||
"grid": {
|
||||
"max": 100,
|
||||
"min": 0
|
||||
},
|
||||
"id": 1,
|
||||
"legend": true,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"apiVersion": "v1",
|
||||
"type": "prometheus",
|
||||
"uid": "default-ds-uid"
|
||||
},
|
||||
"refId": "A",
|
||||
"target": "cpu.usage"
|
||||
}
|
||||
],
|
||||
"title": "CPU Usage",
|
||||
"type": "timeseries",
|
||||
"y2_format": "short",
|
||||
"y_format": "percent"
|
||||
},
|
||||
{
|
||||
"autoMigrateFrom": "graph",
|
||||
"datasource": {
|
||||
"apiVersion": "v1",
|
||||
"type": "prometheus",
|
||||
"uid": "default-ds-uid"
|
||||
},
|
||||
"grid": {
|
||||
"min": 0
|
||||
},
|
||||
"id": 2,
|
||||
"legend": false,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"apiVersion": "v1",
|
||||
"type": "prometheus",
|
||||
"uid": "default-ds-uid"
|
||||
},
|
||||
"refId": "A",
|
||||
"target": "memory.usage"
|
||||
}
|
||||
],
|
||||
"title": "Memory Usage",
|
||||
"type": "timeseries",
|
||||
"y_format": "bytes"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"apiVersion": "v1",
|
||||
"type": "prometheus",
|
||||
"uid": "default-ds-uid"
|
||||
},
|
||||
"grid": {
|
||||
"max": 100,
|
||||
"min": 0
|
||||
},
|
||||
"id": 3,
|
||||
"legend": true,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"apiVersion": "v1",
|
||||
"type": "prometheus",
|
||||
"uid": "default-ds-uid"
|
||||
},
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Server Stats",
|
||||
"type": "table",
|
||||
"y2_format": "bytes",
|
||||
"y_format": "short"
|
||||
},
|
||||
{
|
||||
"autoMigrateFrom": "graphite",
|
||||
"datasource": {
|
||||
"apiVersion": "v1",
|
||||
"type": "prometheus",
|
||||
"uid": "default-ds-uid"
|
||||
},
|
||||
"id": 4,
|
||||
"legend": true,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"apiVersion": "v1",
|
||||
"type": "prometheus",
|
||||
"uid": "default-ds-uid"
|
||||
},
|
||||
"refId": "A",
|
||||
"target": "disk.io"
|
||||
}
|
||||
],
|
||||
"title": "Disk I/O",
|
||||
"type": "timeseries",
|
||||
"y2_format": "Bps"
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 42,
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"datasource": "prometheus",
|
||||
"name": "server",
|
||||
"options": [],
|
||||
"query": "label_values(server)",
|
||||
"refresh": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"name": "env",
|
||||
"options": [
|
||||
{
|
||||
"text": "Production",
|
||||
"value": "prod"
|
||||
},
|
||||
{
|
||||
"text": "Staging",
|
||||
"value": "stage"
|
||||
}
|
||||
],
|
||||
"type": "custom"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "V2 Comprehensive Migration Test Dashboard",
|
||||
"weekStart": ""
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"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": [
|
||||
{
|
||||
"autoMigrateFrom": "graphite",
|
||||
"grid": {
|
||||
"max": 100,
|
||||
"min": 0
|
||||
},
|
||||
"id": 1,
|
||||
"legend": true,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"target": "cpu.usage"
|
||||
}
|
||||
],
|
||||
"title": "CPU Usage",
|
||||
"type": "timeseries",
|
||||
"y2_format": "short",
|
||||
"y_format": "percent"
|
||||
},
|
||||
{
|
||||
"autoMigrateFrom": "graph",
|
||||
"grid": {
|
||||
"min": 0
|
||||
},
|
||||
"id": 2,
|
||||
"legend": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"target": "memory.usage"
|
||||
}
|
||||
],
|
||||
"title": "Memory Usage",
|
||||
"type": "timeseries",
|
||||
"y_format": "bytes"
|
||||
},
|
||||
{
|
||||
"grid": {
|
||||
"max": 100,
|
||||
"min": 0
|
||||
},
|
||||
"id": 3,
|
||||
"legend": true,
|
||||
"title": "Server Stats",
|
||||
"type": "table",
|
||||
"y2_format": "bytes",
|
||||
"y_format": "short"
|
||||
},
|
||||
{
|
||||
"autoMigrateFrom": "graphite",
|
||||
"id": 4,
|
||||
"legend": true,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"target": "disk.io"
|
||||
}
|
||||
],
|
||||
"title": "Disk I/O",
|
||||
"type": "timeseries",
|
||||
"y2_format": "Bps"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 2,
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"datasource": "prometheus",
|
||||
"name": "server",
|
||||
"options": [],
|
||||
"query": "label_values(server)",
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"name": "env",
|
||||
"options": [
|
||||
{
|
||||
"text": "Production",
|
||||
"value": "prod"
|
||||
},
|
||||
{
|
||||
"text": "Staging",
|
||||
"value": "stage"
|
||||
}
|
||||
],
|
||||
"type": "custom"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "V2 Comprehensive Migration Test Dashboard",
|
||||
"weekStart": ""
|
||||
}
|
||||
@@ -2970,7 +2970,7 @@
|
||||
"count": 2
|
||||
},
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 17
|
||||
"count": 16
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard/state/DashboardModel.repeat.test.ts": {
|
||||
|
||||
@@ -41,9 +41,11 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions,
|
||||
if kind.GroupResource() == resources.DashboardResource.GroupResource() {
|
||||
var v2clientAlphaV1, v2clientAlphaV2 dynamic.ResourceInterface
|
||||
shim = func(ctx context.Context, item *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
failed, _, _ := unstructured.NestedBool(item.Object, "status", "conversion", "failed")
|
||||
if failed {
|
||||
storedVersion, _, _ := unstructured.NestedString(item.Object, "status", "conversion", "storedVersion")
|
||||
// Check if there's a stored version in the conversion status.
|
||||
// This indicates the original API version the dashboard was created with,
|
||||
// which should be preserved during export regardless of whether conversion succeeded or failed.
|
||||
storedVersion, _, _ := unstructured.NestedString(item.Object, "status", "conversion", "storedVersion")
|
||||
if storedVersion != "" {
|
||||
// For v2 we need to request the original version
|
||||
if strings.HasPrefix(storedVersion, "v2alpha1") {
|
||||
if v2clientAlphaV1 == nil {
|
||||
@@ -73,6 +75,13 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions,
|
||||
|
||||
return nil, fmt.Errorf("unsupported dashboard version: %s", storedVersion)
|
||||
}
|
||||
|
||||
// If conversion failed but there's no storedVersion, this is an error condition
|
||||
failed, _, _ := unstructured.NestedBool(item.Object, "status", "conversion", "failed")
|
||||
if failed {
|
||||
return nil, fmt.Errorf("conversion failed but no storedVersion available")
|
||||
}
|
||||
|
||||
return item, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { each, find, findIndex, flattenDeep, isArray, isBoolean, isString, map, max, some } from 'lodash';
|
||||
import { each, find, findIndex, flattenDeep, isArray, isString, map, max, some } from 'lodash';
|
||||
|
||||
import {
|
||||
AnnotationQuery,
|
||||
@@ -119,50 +119,8 @@ export class DashboardMigrator {
|
||||
}
|
||||
}
|
||||
|
||||
panelUpgrades.push((panel: any) => {
|
||||
// rename panel type
|
||||
if (panel.type === 'graphite') {
|
||||
panel.type = 'graph';
|
||||
}
|
||||
|
||||
if (panel.type !== 'graph') {
|
||||
return panel;
|
||||
}
|
||||
|
||||
if (isBoolean(panel.legend)) {
|
||||
panel.legend = { show: panel.legend };
|
||||
}
|
||||
|
||||
if (panel.grid) {
|
||||
if (panel.grid.min) {
|
||||
panel.grid.leftMin = panel.grid.min;
|
||||
delete panel.grid.min;
|
||||
}
|
||||
|
||||
if (panel.grid.max) {
|
||||
panel.grid.leftMax = panel.grid.max;
|
||||
delete panel.grid.max;
|
||||
}
|
||||
}
|
||||
|
||||
if (panel.y_format) {
|
||||
if (!panel.y_formats) {
|
||||
panel.y_formats = [];
|
||||
}
|
||||
panel.y_formats[0] = panel.y_format;
|
||||
delete panel.y_format;
|
||||
}
|
||||
|
||||
if (panel.y2_format) {
|
||||
if (!panel.y_formats) {
|
||||
panel.y_formats = [];
|
||||
}
|
||||
panel.y_formats[1] = panel.y2_format;
|
||||
delete panel.y2_format;
|
||||
}
|
||||
|
||||
return panel;
|
||||
});
|
||||
// we used to have graphite panel type migration logic here
|
||||
// but this is handled by auto migration, see public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts
|
||||
}
|
||||
|
||||
// schema version 3 changes
|
||||
|
||||
@@ -103,10 +103,14 @@ describe('Backend / Frontend single version migration result comparison', () =>
|
||||
expect(backendMigrationResult.schemaVersion).toEqual(targetVersion);
|
||||
|
||||
// Migrate dashboard in Frontend.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let migratedTemplatingList: any[] = jsonInput?.templating?.list ?? [];
|
||||
const frontendModel = new DashboardModel(jsonInput, undefined, {
|
||||
targetSchemaVersion: targetVersion,
|
||||
getVariablesFromState: () => jsonInput?.templating?.list ?? [],
|
||||
getVariablesFromState: () => migratedTemplatingList,
|
||||
});
|
||||
// Update the templating list reference after migration
|
||||
migratedTemplatingList = frontendModel.templating?.list ?? [];
|
||||
|
||||
const frontendMigrationResult = frontendModel.getSaveModelClone();
|
||||
|
||||
|
||||
@@ -71,9 +71,13 @@ describe('Backend / Frontend result comparison', () => {
|
||||
expect(backendMigrationResult.schemaVersion).toEqual(DASHBOARD_SCHEMA_VERSION);
|
||||
|
||||
// Migrate dashboard in Frontend.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let migratedTemplatingList: any[] = jsonInput?.templating?.list ?? [];
|
||||
const frontendModel = new DashboardModel(jsonInput, undefined, {
|
||||
getVariablesFromState: () => jsonInput?.templating?.list ?? [],
|
||||
getVariablesFromState: () => migratedTemplatingList,
|
||||
});
|
||||
// Update the templating list reference after migration
|
||||
migratedTemplatingList = frontendModel.templating?.list ?? [];
|
||||
|
||||
const frontendMigrationResult = frontendModel.getSaveModelClone();
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@ import { autoMigrateAngular } from './PanelModel';
|
||||
|
||||
export function getPanelPluginToMigrateTo(panel: any): string | undefined {
|
||||
// Graph needs special logic as it can be migrated to multiple panels
|
||||
if (panel.type === 'graph') {
|
||||
// Also, graphite was previously migrated to graph in the schema version 2 migration in DashboardMigrator.ts
|
||||
// but this was a bug because in there graphite was set to graph, but since those migrations run
|
||||
// after PanelModel.restoreModel where autoMigrateFrom is set, this caused the graph migration to be skipped.
|
||||
// And this resulted in a dashboard with invalid panels.
|
||||
if (panel.type === 'graph' || panel.type === 'graphite') {
|
||||
if (panel.xaxis?.mode === 'series') {
|
||||
if (panel.legend?.values) {
|
||||
return 'bargauge';
|
||||
|
||||
Reference in New Issue
Block a user