Dashboards: Remove panel plugin provider from migrations (#110477)

This commit is contained in:
Ryan McKinley
2025-09-04 14:17:22 +03:00
committed by GitHub
parent 3d009ff7ed
commit 8052ecb3ba
23 changed files with 143 additions and 518 deletions
@@ -9,6 +9,9 @@ import (
const (
MIN_VERSION = 13
LATEST_VERSION = 41
// The pluginVersion to set after simulating auto-migrate for angular panels
pluginVersionForAutoMigrate = "12.1.0"
)
type SchemaVersionMigrationFunc func(context.Context, map[string]interface{}) error
@@ -33,14 +36,7 @@ type PanelPluginInfo struct {
Version string
}
type PanelPluginInfoProvider interface {
// Gets all the panels from the plugin store.
// Equivalent to grafanaBootData.settings.panels on the frontend.
GetPanels() []PanelPluginInfo
GetPanelPlugin(id string) PanelPluginInfo
}
func GetMigrations(dsInfoProvider DataSourceInfoProvider, panelProvider PanelPluginInfoProvider) map[int]SchemaVersionMigrationFunc {
func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionMigrationFunc {
return map[int]SchemaVersionMigrationFunc{
14: V14,
15: V15,
@@ -52,11 +48,11 @@ func GetMigrations(dsInfoProvider DataSourceInfoProvider, panelProvider PanelPlu
21: V21,
22: V22,
23: V23,
24: V24(panelProvider),
24: V24,
25: V25,
26: V26,
27: V27,
28: V28(panelProvider),
28: V28,
29: V29,
30: V30,
31: V31,
@@ -4,8 +4,9 @@ import (
"context"
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestGetSchemaVersion(t *testing.T) {
@@ -65,7 +65,7 @@ import (
// },
// "transformations": [],
// "targets": [{ "refId": "A" }],
// "pluginVersion": "1.0.0"
// "pluginVersion": "{current_grafana_version}"
// }
// ]
// }
@@ -181,26 +181,12 @@ import (
// }
// ],
// "targets": [{ "refId": "A" }],
// "pluginVersion": "1.0.0"
// "pluginVersion": "{current_grafana_version}"
// }
// ]
// }
type v24Migrator struct {
panelProvider PanelPluginInfoProvider
panelPlugins []PanelPluginInfo
}
func V24(panelProvider PanelPluginInfoProvider) SchemaVersionMigrationFunc {
migrator := &v24Migrator{
panelProvider: panelProvider,
panelPlugins: panelProvider.GetPanels(),
}
return migrator.migrate
}
func (m *v24Migrator) migrate(_ context.Context, dashboard map[string]interface{}) error {
func V24(_ context.Context, dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 24
panels, ok := dashboard["panels"].([]interface{})
@@ -225,12 +211,8 @@ func (m *v24Migrator) migrate(_ context.Context, dashboard map[string]interface{
continue
}
// Find if the panel plugin exists
tablePanelPlugin := m.panelProvider.GetPanelPlugin("table")
if tablePanelPlugin.ID == "" {
return NewMigrationError("table panel plugin not found when migrating dashboard to schema version 24", 24, LATEST_VERSION, "V24")
}
panelMap["pluginVersion"] = tablePanelPlugin.Version
// The grafana version that matches the hardcoded autoMigrate plugins
panelMap["pluginVersion"] = pluginVersionForAutoMigrate
err := tablePanelChangedHandler(panelMap)
if err != nil {
return err
@@ -4,7 +4,11 @@ import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/testutil"
)
const (
// The pluginVersion to set after simulating auto-migrate for angular panels
pluginVersionForAutoMigrate = "12.1.0"
)
func TestV24(t *testing.T) {
@@ -79,7 +83,7 @@ func TestV24(t *testing.T) {
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
},
},
},
@@ -241,7 +245,7 @@ func TestV24(t *testing.T) {
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
},
},
},
@@ -331,7 +335,7 @@ func TestV24(t *testing.T) {
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
},
},
},
@@ -410,7 +414,7 @@ func TestV24(t *testing.T) {
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
},
},
},
@@ -489,7 +493,7 @@ func TestV24(t *testing.T) {
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
},
},
},
@@ -567,7 +571,7 @@ func TestV24(t *testing.T) {
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
},
},
},
@@ -664,7 +668,7 @@ func TestV24(t *testing.T) {
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
},
},
},
@@ -836,12 +840,12 @@ func TestV24(t *testing.T) {
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
},
},
},
},
}
runMigrationTests(t, tests, schemaversion.V24(testutil.GetTestPanelProvider()))
runMigrationTests(t, tests, schemaversion.V24)
}
@@ -47,37 +47,12 @@ import (
// { "name": "var1" }
// ]
// }
type v28Migrator struct {
panelProvider PanelPluginInfoProvider
panelPlugins []PanelPluginInfo
statPanelVersion string // Cached stat panel version
}
func V28(panelProvider PanelPluginInfoProvider) SchemaVersionMigrationFunc {
// Get stat panel version once during initialization
statPanelPlugin := panelProvider.GetPanelPlugin("stat")
statPanelVersion := ""
if statPanelPlugin.ID != "" {
statPanelVersion = statPanelPlugin.Version
}
migrator := &v28Migrator{
panelProvider: panelProvider,
panelPlugins: panelProvider.GetPanels(),
statPanelVersion: statPanelVersion,
}
return func(ctx context.Context, dashboard map[string]interface{}) error {
return migrator.migrate(context.Background(), dashboard)
}
}
func (m *v28Migrator) migrate(_ context.Context, dashboard map[string]interface{}) error {
func V28(_ context.Context, dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 28
// Migrate singlestat panels
if panels, ok := dashboard["panels"].([]interface{}); ok {
if err := m.processPanels(panels); err != nil {
if err := processPanels(panels); err != nil {
return err
}
}
@@ -96,7 +71,7 @@ func (m *v28Migrator) migrate(_ context.Context, dashboard map[string]interface{
return nil
}
func (m *v28Migrator) processPanels(panels []interface{}) error {
func processPanels(panels []interface{}) error {
for _, panel := range panels {
p, ok := panel.(map[string]interface{})
if !ok {
@@ -106,7 +81,7 @@ func (m *v28Migrator) processPanels(panels []interface{}) error {
// Process nested panels if this is a row panel
if p["type"] == "row" {
if nestedPanels, ok := p["panels"].([]interface{}); ok {
if err := m.processPanels(nestedPanels); err != nil {
if err := processPanels(nestedPanels); err != nil {
return err
}
}
@@ -115,23 +90,27 @@ func (m *v28Migrator) processPanels(panels []interface{}) error {
// Migrate singlestat panels
if p["type"] == "singlestat" || p["type"] == "grafana-singlestat-panel" {
if err := m.migrateSinglestatPanel(p); err != nil {
if err := migrateSinglestatPanel(p); err != nil {
return err
}
}
// Normalize existing stat panels to ensure they have current default options
if p["type"] == "stat" {
m.normalizeStatPanel(p)
normalizeStatPanel(p)
}
}
return nil
}
func (m *v28Migrator) migrateSinglestatPanel(panel map[string]interface{}) error {
func migrateSinglestatPanel(panel map[string]interface{}) error {
targetType := "stat"
// NOTE: The legacy types "singlestat" and "gauge" are both angular only
// This are not supported by any version that could run this migration, so there is
// no need to maintain a distinction or fallback to the non-stat version
// NOTE: DashboardMigrator's migrateSinglestat function has some logic that never gets called
// migrateSinglestat will only run if (panel.type === 'singlestat')
// but this will not be the case because PanelModel runs restoreModel in the constructor
@@ -146,22 +125,16 @@ func (m *v28Migrator) migrateSinglestatPanel(panel map[string]interface{}) error
originalType := panel["type"].(string)
panel["autoMigrateFrom"] = panel["type"]
panel["type"] = targetType
// Use cached stat panel version
if m.statPanelVersion == "" {
return NewMigrationError("stat panel plugin not found when migrating dashboard to schema version 28", 28, LATEST_VERSION, "V28")
}
panel["pluginVersion"] = m.statPanelVersion
panel["pluginVersion"] = pluginVersionForAutoMigrate
// Migrate panel options and field config
m.migrateSinglestatOptions(panel, originalType)
migrateSinglestatOptions(panel, originalType)
return nil
}
// normalizeStatPanel ensures existing stat panels have all current default options
func (m *v28Migrator) normalizeStatPanel(panel map[string]interface{}) {
func normalizeStatPanel(panel map[string]interface{}) {
if panel["options"] == nil {
panel["options"] = map[string]interface{}{}
}
@@ -192,7 +165,7 @@ func (m *v28Migrator) normalizeStatPanel(panel map[string]interface{}) {
}
// migrateSinglestatOptions handles the complete migration of singlestat panel options and field config
func (m *v28Migrator) migrateSinglestatOptions(panel map[string]interface{}, originalType string) {
func migrateSinglestatOptions(panel map[string]interface{}, originalType string) {
// Initialize field config if not present
if panel["fieldConfig"] == nil {
panel["fieldConfig"] = map[string]interface{}{
@@ -206,20 +179,20 @@ func (m *v28Migrator) migrateSinglestatOptions(panel map[string]interface{}, ori
// Migrate from angular singlestat configuration using appropriate strategy
if originalType == "grafana-singlestat-panel" {
m.migrateGrafanaSinglestatPanel(panel, defaults)
migrateGrafanaSinglestatPanel(panel, defaults)
} else {
m.migratetSinglestat(panel, defaults)
migratetSinglestat(panel, defaults)
}
// Apply shared migration logic
m.applySharedSinglestatMigration(defaults)
applySharedSinglestatMigration(defaults)
// Clean up old angular properties after migration
m.cleanupAngularProperties(panel)
cleanupAngularProperties(panel)
}
// getDefaultStatOptions returns the default options structure for stat panels
func (m *v28Migrator) getDefaultStatOptions() map[string]interface{} {
func getDefaultStatOptions() map[string]interface{} {
return map[string]interface{}{
"reduceOptions": map[string]interface{}{
"calcs": []string{"mean"},
@@ -237,11 +210,11 @@ func (m *v28Migrator) getDefaultStatOptions() map[string]interface{} {
// migratetSinglestat handles explicit migration from 'singlestat' panels
// Based on explicit migration logic in DashboardMigrator.ts
func (m *v28Migrator) migratetSinglestat(panel map[string]interface{}, defaults map[string]interface{}) {
angularOpts := m.extractAngularOptions(panel)
func migratetSinglestat(panel map[string]interface{}, defaults map[string]interface{}) {
angularOpts := extractAngularOptions(panel)
// Explicit migration uses standard stat panel defaults
options := m.getDefaultStatOptions()
options := getDefaultStatOptions()
// Explicit migration: always set a reducer with fallback
var valueName string
@@ -249,7 +222,7 @@ func (m *v28Migrator) migratetSinglestat(panel map[string]interface{}, defaults
valueName = vn
}
if reducer := m.getReducerForValueName(valueName); reducer != "" {
if reducer := getReducerForValueName(valueName); reducer != "" {
options["reduceOptions"].(map[string]interface{})["calcs"] = []string{reducer}
} else {
// Explicit migration fallback: use mean for invalid reducers
@@ -257,7 +230,7 @@ func (m *v28Migrator) migratetSinglestat(panel map[string]interface{}, defaults
}
// Migrate thresholds FIRST (consolidated: both panel types create DEFAULT_THRESHOLDS for empty strings)
m.migrateThresholds(angularOpts, defaults)
migrateThresholds(angularOpts, defaults)
// If no thresholds were set from angular migration, add default stat panel thresholds
// This matches the behavior of frontend pluginLoaded which adds default thresholds
@@ -278,15 +251,15 @@ func (m *v28Migrator) migratetSinglestat(panel map[string]interface{}, defaults
}
// Apply common angular option migrations (value mappings can now use threshold colors)
m.applyCommonAngularMigration(panel, defaults, options, angularOpts)
applyCommonAngularMigration(panel, defaults, options, angularOpts)
panel["options"] = options
}
// migrateGrafanaSinglestatPanel handles auto-migration from 'grafana-singlestat-panel'
// Based on frontend changePlugin() and sharedSingleStatPanelChangedHandler logic
func (m *v28Migrator) migrateGrafanaSinglestatPanel(panel map[string]interface{}, defaults map[string]interface{}) {
angularOpts := m.extractAngularOptions(panel)
func migrateGrafanaSinglestatPanel(panel map[string]interface{}, defaults map[string]interface{}) {
angularOpts := extractAngularOptions(panel)
// Auto-migration uses different defaults (matches frontend changePlugin behavior)
options := map[string]interface{}{
@@ -309,13 +282,13 @@ func (m *v28Migrator) migrateGrafanaSinglestatPanel(panel map[string]interface{}
valueName = vn
}
if reducer := m.getReducerForValueName(valueName); reducer != "" {
if reducer := getReducerForValueName(valueName); reducer != "" {
options["reduceOptions"].(map[string]interface{})["calcs"] = []string{reducer}
}
// No fallback - keeps the auto-migration default "lastNotNull"
// Migrate thresholds FIRST (consolidated: both panel types create DEFAULT_THRESHOLDS for empty strings)
m.migrateThresholds(angularOpts, defaults)
migrateThresholds(angularOpts, defaults)
// If no thresholds were set from angular migration, add default stat panel thresholds
// This matches the behavior of frontend pluginLoaded which adds default thresholds
@@ -336,19 +309,19 @@ func (m *v28Migrator) migrateGrafanaSinglestatPanel(panel map[string]interface{}
}
// Apply common angular option migrations (value mappings can now use threshold colors)
m.applyCommonAngularMigration(panel, defaults, options, angularOpts)
applyCommonAngularMigration(panel, defaults, options, angularOpts)
panel["options"] = options
}
// migrateThresholds handles threshold migration for both singlestat panel types
// Both panel types now create DEFAULT_THRESHOLDS when threshold string is empty (consolidated behavior)
func (m *v28Migrator) migrateThresholds(angularOpts map[string]interface{}, defaults map[string]interface{}) {
func migrateThresholds(angularOpts map[string]interface{}, defaults map[string]interface{}) {
if thresholds, ok := angularOpts["thresholds"].(string); ok {
if colors, ok := angularOpts["colors"].([]interface{}); ok {
if thresholds != "" {
// Non-empty thresholds: use normal migration
m.migrateThresholdsAndColors(defaults, thresholds, colors)
migrateThresholdsAndColors(defaults, thresholds, colors)
} else {
// Empty thresholds: use frontend DEFAULT_THRESHOLDS fallback (both panel types)
defaults["thresholds"] = map[string]interface{}{
@@ -370,7 +343,7 @@ func (m *v28Migrator) migrateThresholds(angularOpts map[string]interface{}, defa
}
// applyCommonAngularMigration applies migrations common to both singlestat types
func (m *v28Migrator) applyCommonAngularMigration(panel map[string]interface{}, defaults map[string]interface{}, options map[string]interface{}, angularOpts map[string]interface{}) {
func applyCommonAngularMigration(panel map[string]interface{}, defaults map[string]interface{}, options map[string]interface{}, angularOpts map[string]interface{}) {
// Migrate table column
// Based on sharedSingleStatPanelChangedHandler line ~125: options.reduceOptions.fields = `/^${prevPanel.tableColumn}$/`
if tableColumn, ok := angularOpts["tableColumn"].(string); ok && tableColumn != "" {
@@ -400,7 +373,7 @@ func (m *v28Migrator) applyCommonAngularMigration(panel map[string]interface{},
// Migrate value mappings (thresholds should already be migrated)
valueMaps, _ := angularOpts["valueMaps"].([]interface{})
m.migrateValueMappings(angularOpts, defaults, valueMaps)
migrateValueMappings(angularOpts, defaults, valueMaps)
// Migrate sparkline configuration
// Based on statPanelChangedHandler lines ~25-35: sparkline migration logic
@@ -448,7 +421,7 @@ func (m *v28Migrator) applyCommonAngularMigration(panel map[string]interface{},
// applySharedSinglestatMigration applies shared migration logic for all singlestat panels
// Based on sharedSingleStatMigrationHandler in packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts
func (m *v28Migrator) applySharedSinglestatMigration(defaults map[string]interface{}) {
func applySharedSinglestatMigration(defaults map[string]interface{}) {
// Ensure thresholds have proper structure
if thresholds, ok := defaults["thresholds"].(map[string]interface{}); ok {
if steps, ok := thresholds["steps"].([]interface{}); ok {
@@ -487,7 +460,7 @@ func (m *v28Migrator) applySharedSinglestatMigration(defaults map[string]interfa
// Helper functions
func (m *v28Migrator) extractAngularOptions(panel map[string]interface{}) map[string]interface{} {
func extractAngularOptions(panel map[string]interface{}) map[string]interface{} {
// Some panels might have angular options directly in the root
// Check for common angular properties
angularProps := []string{
@@ -504,7 +477,7 @@ func (m *v28Migrator) extractAngularOptions(panel map[string]interface{}) map[st
}
// getReducerForValueName returns the mapped reducer or empty string for invalid values
func (m *v28Migrator) getReducerForValueName(valueName string) string {
func getReducerForValueName(valueName string) string {
reducerMap := map[string]string{
"min": "min",
"max": "max",
@@ -526,7 +499,7 @@ func (m *v28Migrator) getReducerForValueName(valueName string) string {
return ""
}
func (m *v28Migrator) migrateThresholdsAndColors(defaults map[string]interface{}, thresholdsStr string, colors []interface{}) {
func migrateThresholdsAndColors(defaults map[string]interface{}, thresholdsStr string, colors []interface{}) {
// Parse thresholds string (e.g., "10,20,30")
// Based on sharedSingleStatPanelChangedHandler lines ~145-165: Convert thresholds and color values
thresholds := []interface{}{}
@@ -555,7 +528,7 @@ func (m *v28Migrator) migrateThresholdsAndColors(defaults map[string]interface{}
}
}
func (m *v28Migrator) migrateValueMappings(panel map[string]interface{}, defaults map[string]interface{}, valueMappings []interface{}) {
func migrateValueMappings(panel map[string]interface{}, defaults map[string]interface{}, valueMappings []interface{}) {
mappings := []interface{}{}
mappingType := panel["mappingType"]
@@ -571,7 +544,7 @@ func (m *v28Migrator) migrateValueMappings(panel map[string]interface{}, default
case 1:
for _, valueMap := range valueMappings {
valueMapping := valueMap.(map[string]interface{})
upgradedMapping := m.upgradeOldAngularValueMapping(valueMapping, defaults["thresholds"])
upgradedMapping := upgradeOldAngularValueMapping(valueMapping, defaults["thresholds"])
if upgradedMapping != nil {
mappings = append(mappings, upgradedMapping)
}
@@ -581,7 +554,7 @@ func (m *v28Migrator) migrateValueMappings(panel map[string]interface{}, default
if rangeMaps, ok := panel["rangeMaps"].([]interface{}); ok {
for _, rangeMap := range rangeMaps {
rangeMapping := rangeMap.(map[string]interface{})
upgradedMapping := m.upgradeOldAngularValueMapping(rangeMapping, defaults["thresholds"])
upgradedMapping := upgradeOldAngularValueMapping(rangeMapping, defaults["thresholds"])
if upgradedMapping != nil {
mappings = append(mappings, upgradedMapping)
}
@@ -594,7 +567,7 @@ func (m *v28Migrator) migrateValueMappings(panel map[string]interface{}, default
// upgradeOldAngularValueMapping converts old angular value mappings to new format
// Based on upgradeOldAngularValueMapping in packages/grafana-data/src/utils/valueMappings.ts
func (m *v28Migrator) upgradeOldAngularValueMapping(old map[string]interface{}, thresholds interface{}) map[string]interface{} {
func upgradeOldAngularValueMapping(old map[string]interface{}, thresholds interface{}) map[string]interface{} {
valueMaps := map[string]interface{}{
"type": "value",
"options": map[string]interface{}{},
@@ -604,10 +577,10 @@ func (m *v28Migrator) upgradeOldAngularValueMapping(old map[string]interface{},
// Use the color we would have picked from thresholds
var color interface{}
if value, ok := old["value"]; ok {
if numeric, err := m.parseNumericValue(value); err == nil {
if numeric, err := parseNumericValue(value); err == nil {
if thresholdsMap, ok := thresholds.(map[string]interface{}); ok {
if steps, ok := thresholdsMap["steps"].([]interface{}); ok {
level := m.getActiveThreshold(numeric, steps)
level := getActiveThreshold(numeric, steps)
if level != nil {
if levelColor, ok := level["color"]; ok {
color = levelColor
@@ -704,7 +677,7 @@ func (m *v28Migrator) upgradeOldAngularValueMapping(old map[string]interface{},
// getActiveThreshold finds the active threshold for a given value
// Based on getActiveThreshold in packages/grafana-data/src/field/thresholds.ts
func (m *v28Migrator) getActiveThreshold(value float64, steps []interface{}) map[string]interface{} {
func getActiveThreshold(value float64, steps []interface{}) map[string]interface{} {
for i := len(steps) - 1; i >= 0; i-- {
if step, ok := steps[i].(map[string]interface{}); ok {
if stepValue, ok := step["value"]; ok {
@@ -722,7 +695,7 @@ func (m *v28Migrator) getActiveThreshold(value float64, steps []interface{}) map
}
// parseNumericValue converts various types to float64 for threshold calculations
func (m *v28Migrator) parseNumericValue(value interface{}) (float64, error) {
func parseNumericValue(value interface{}) (float64, error) {
switch v := value.(type) {
case string:
return strconv.ParseFloat(v, 64)
@@ -743,7 +716,7 @@ func (m *v28Migrator) parseNumericValue(value interface{}) (float64, error) {
// cleanupAngularProperties removes old angular properties after migration
// Based on PanelModel.clearPropertiesBeforePluginChange in public/app/features/dashboard/state/PanelModel.ts
func (m *v28Migrator) cleanupAngularProperties(panel map[string]interface{}) {
func cleanupAngularProperties(panel map[string]interface{}) {
// Remove PanelModel's autoMigrateFrom property
delete(panel, "autoMigrateFrom")
@@ -4,7 +4,6 @@ import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/testutil"
)
func TestV28(t *testing.T) {
@@ -88,7 +87,8 @@ func TestV28(t *testing.T) {
},
"overrides": []interface{}{},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
@@ -176,7 +176,7 @@ func TestV28(t *testing.T) {
},
"overrides": []interface{}{},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
@@ -257,7 +257,7 @@ func TestV28(t *testing.T) {
},
"overrides": []interface{}{},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
@@ -324,7 +324,7 @@ func TestV28(t *testing.T) {
},
"overrides": []interface{}{},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
@@ -391,7 +391,7 @@ func TestV28(t *testing.T) {
},
"overrides": []interface{}{},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
@@ -491,7 +491,7 @@ func TestV28(t *testing.T) {
},
"overrides": []interface{}{},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
@@ -556,7 +556,7 @@ func TestV28(t *testing.T) {
},
"overrides": []interface{}{},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
@@ -621,7 +621,7 @@ func TestV28(t *testing.T) {
},
"overrides": []interface{}{},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
@@ -696,7 +696,7 @@ func TestV28(t *testing.T) {
},
"overrides": []interface{}{},
},
"pluginVersion": "1.0.0",
"pluginVersion": pluginVersionForAutoMigrate,
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
@@ -746,46 +746,5 @@ func TestV28(t *testing.T) {
},
}
errorTests := []migrationTestCase{
{
name: "throw an error if stat panel plugin not found",
input: map[string]interface{}{
"schemaVersion": 27,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "singlestat",
"valueName": "avg",
"format": "ms",
"decimals": 2,
"thresholds": "10,20,30",
"colors": []interface{}{"green", "yellow", "red"},
"gauge": map[string]interface{}{
"show": false,
},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
},
},
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"name": "var1",
"tags": []interface{}{"tag1"},
"tagsQuery": "query",
"tagValuesQuery": "values",
"useTags": true,
},
},
},
},
expectedError: "schema migration from version 28 to 41 failed: stat panel plugin not found when migrating dashboard to schema version 28",
},
}
runMigrationTests(t, tests, schemaversion.V28(testutil.GetTestPanelProvider()))
runMigrationTests(t, errorTests, schemaversion.V28(testutil.GetTestPanelProviderWithCustomPanels([]schemaversion.PanelPluginInfo{
{ID: "fake-plugin", Version: "1.0.0"},
})))
runMigrationTests(t, tests, schemaversion.V28)
}