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
@@ -31,7 +31,7 @@ import (
func TestConversionMatrixExist(t *testing.T) {
// Initialize the migrator with a test data source provider
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
versions := []metav1.Object{
&dashv0.Dashboard{Spec: common.Unstructured{Object: map[string]any{"title": "dashboardV0"}}},
@@ -82,7 +82,7 @@ func TestDeepCopyValid(t *testing.T) {
func TestDashboardConversionToAllVersions(t *testing.T) {
// Initialize the migrator with a test data source provider
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
// Set up conversion scheme
scheme := runtime.NewScheme()
@@ -234,7 +234,7 @@ func testConversion(t *testing.T, convertedDash metav1.Object, filename, outputD
// TestConversionMetrics tests that conversion-level metrics are recorded correctly
func TestConversionMetrics(t *testing.T) {
// Initialize migration with test providers
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
// Create a test registry for metrics
registry := prometheus.NewRegistry()
@@ -365,7 +365,7 @@ func TestConversionMetrics(t *testing.T) {
// TestConversionMetricsWrapper tests the withConversionMetrics wrapper function
func TestConversionMetricsWrapper(t *testing.T) {
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
// Create a test registry for metrics
registry := prometheus.NewRegistry()
@@ -521,7 +521,7 @@ func TestSchemaVersionExtraction(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test the schema version extraction logic by creating a wrapper and checking the metrics labels
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
// Create a test registry for metrics
registry := prometheus.NewRegistry()
@@ -564,7 +564,7 @@ func TestSchemaVersionExtraction(t *testing.T) {
// TestConversionLogging tests that conversion-level logging works correctly
func TestConversionLogging(t *testing.T) {
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
// Create a test registry for metrics
registry := prometheus.NewRegistry()
@@ -654,7 +654,7 @@ func TestConversionLogging(t *testing.T) {
// TestConversionLogLevels tests that appropriate log levels are used
func TestConversionLogLevels(t *testing.T) {
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
t.Run("log levels and structured fields verification", func(t *testing.T) {
// Create test wrapper to verify logging behavior
@@ -723,7 +723,7 @@ func TestConversionLogLevels(t *testing.T) {
// TestConversionLoggingFields tests that all expected fields are included in log messages
func TestConversionLoggingFields(t *testing.T) {
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
t.Run("verify all log fields are present", func(t *testing.T) {
// Test that the conversion wrapper includes all expected structured fields
+4 -4
View File
@@ -9,8 +9,8 @@ import (
)
// Initialize provides the migrator singleton with required dependencies and builds the map of migrations.
func Initialize(dsInfoProvider schemaversion.DataSourceInfoProvider, panelProvider schemaversion.PanelPluginInfoProvider) {
migratorInstance.init(dsInfoProvider, panelProvider)
func Initialize(dsInfoProvider schemaversion.DataSourceInfoProvider) {
migratorInstance.init(dsInfoProvider)
}
// Migrate migrates the given dashboard to the target version.
@@ -32,9 +32,9 @@ type migrator struct {
migrations map[int]schemaversion.SchemaVersionMigrationFunc
}
func (m *migrator) init(dsInfoProvider schemaversion.DataSourceInfoProvider, panelProvider schemaversion.PanelPluginInfoProvider) {
func (m *migrator) init(dsInfoProvider schemaversion.DataSourceInfoProvider) {
initOnce.Do(func() {
m.migrations = schemaversion.GetMigrations(dsInfoProvider, panelProvider)
m.migrations = schemaversion.GetMigrations(dsInfoProvider)
close(m.ready)
})
}
+4 -4
View File
@@ -27,7 +27,7 @@ func TestMigrate(t *testing.T) {
require.NoError(t, err)
// Use the same datasource provider as the frontend test to ensure consistency
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
t.Run("minimum version check", func(t *testing.T) {
err := migration.Migrate(context.Background(), map[string]interface{}{
@@ -122,7 +122,7 @@ func loadDashboard(t *testing.T, path string) map[string]interface{} {
// TestSchemaMigrationMetrics tests that schema migration metrics are recorded correctly
func TestSchemaMigrationMetrics(t *testing.T) {
// Initialize migration with test providers
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
// Create a test registry for metrics
registry := prometheus.NewRegistry()
@@ -206,7 +206,7 @@ func TestSchemaMigrationMetrics(t *testing.T) {
// TestSchemaMigrationLogging tests that schema migration logging works correctly
func TestSchemaMigrationLogging(t *testing.T) {
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
tests := []struct {
name string
@@ -286,7 +286,7 @@ func TestSchemaMigrationLogging(t *testing.T) {
// TestLogMessageStructure tests that log messages contain expected structured fields
func TestLogMessageStructure(t *testing.T) {
migration.Initialize(migrationtestutil.GetTestDataSourceProvider(), migrationtestutil.GetTestPanelProvider())
migration.Initialize(migrationtestutil.GetTestDataSourceProvider())
t.Run("log messages include all required fields", func(t *testing.T) {
// Test that migration functions execute successfully, ensuring log code paths are hit
@@ -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)
}
@@ -92,7 +92,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -75,7 +75,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -77,7 +77,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -185,7 +185,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -72,7 +72,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -90,4 +90,4 @@
"refresh": "",
"schemaVersion": 41,
"title": "V22 Table Panel Styles Test"
}
}
@@ -58,7 +58,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -218,7 +218,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -283,7 +283,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -362,7 +362,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -433,7 +433,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -503,7 +503,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -574,7 +574,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -664,7 +664,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -770,7 +770,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -870,7 +870,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -983,7 +983,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -1102,7 +1102,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -1215,7 +1215,7 @@
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -1353,7 +1353,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -1370,4 +1370,4 @@
],
"refresh": "",
"schemaVersion": 41
}
}
@@ -51,7 +51,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -123,7 +123,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -221,7 +221,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -52,7 +52,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -120,7 +120,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -193,7 +193,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -292,7 +292,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"targets": [
{
"datasource": {
@@ -379,7 +379,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"pluginVersion": "12.1.0",
"postfix": "b",
"postfixFontSize": "50%",
"prefix": "a",
@@ -8,10 +8,6 @@ import (
type TestDataSourceProvider struct{}
type TestPanelProvider struct {
customPanels []schemaversion.PanelPluginInfo
}
func (m *TestDataSourceProvider) GetDataSourceInfo(_ context.Context) []schemaversion.DataSourceInfo {
return []schemaversion.DataSourceInfo{
{
@@ -65,54 +61,7 @@ func (m *TestDataSourceProvider) GetDataSourceInfo(_ context.Context) []schemave
}
}
func (m *TestPanelProvider) GetPanels() []schemaversion.PanelPluginInfo {
if len(m.customPanels) > 0 {
return m.customPanels
}
// Default panels
return []schemaversion.PanelPluginInfo{
{
ID: "gauge",
Version: "1.0.0",
},
{
ID: "stat",
Version: "1.0.0",
},
{
ID: "table",
Version: "1.0.0",
},
// Note: grafana-singlestat-panel is not included to match frontend test environment
// This ensures both frontend and backend migrations produce the same result
}
}
func (m *TestPanelProvider) GetPanelPlugin(id string) schemaversion.PanelPluginInfo {
// check if it exists in the list of mocked panels
for _, panel := range m.GetPanels() {
if panel.ID == id {
return panel
}
}
return schemaversion.PanelPluginInfo{}
}
// GetTestDataSourceProvider returns a singleton instance of the test provider
func GetTestDataSourceProvider() *TestDataSourceProvider {
return &TestDataSourceProvider{}
}
// GetTestPanelProvider returns a singleton instance of the test panel provider
func GetTestPanelProvider() *TestPanelProvider {
return &TestPanelProvider{}
}
// GetTestPanelProviderWithCustomPanels returns a test panel provider with custom panels
func GetTestPanelProviderWithCustomPanels(customPanels []schemaversion.PanelPluginInfo) *TestPanelProvider {
return &TestPanelProvider{
customPanels: customPanels,
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ import (
)
func TestDashboardAPIBuilder_Mutate(t *testing.T) {
migration.Initialize(testutil.GetTestDataSourceProvider(), testutil.GetTestPanelProvider())
migration.Initialize(testutil.GetTestDataSourceProvider())
tests := []struct {
name string
inputObj runtime.Object
@@ -1,41 +0,0 @@
package dashboard
import (
"context"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
)
type PluginStorePanelProvider struct {
pluginStore pluginstore.Store
buildVersion string
}
func (p *PluginStorePanelProvider) GetPanels() []schemaversion.PanelPluginInfo {
panelPlugins := p.pluginStore.Plugins(context.Background(), plugins.TypePanel)
panels := make([]schemaversion.PanelPluginInfo, len(panelPlugins))
for i, plugin := range panelPlugins {
version := plugin.Info.Version
if version == "" {
version = p.buildVersion
}
panels[i] = schemaversion.PanelPluginInfo{
ID: plugin.ID,
Version: version,
}
}
return panels
}
func (p *PluginStorePanelProvider) GetPanelPlugin(id string) schemaversion.PanelPluginInfo {
for _, plugin := range p.GetPanels() {
if plugin.ID == id {
return plugin
}
}
return schemaversion.PanelPluginInfo{}
}
@@ -1,193 +0,0 @@
package dashboard
import (
"context"
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
)
func TestPluginStorePanelProvider_GetPanels(t *testing.T) {
tests := []struct {
name string
plugins []pluginstore.Plugin
buildVersion string
expectedPanels []schemaversion.PanelPluginInfo
}{
{
name: "should return all panel plugins with their versions",
plugins: []pluginstore.Plugin{
{
JSONData: plugins.JSONData{ID: "gauge", Info: plugins.Info{Version: "1.0.0"}},
},
{
JSONData: plugins.JSONData{ID: "stat", Info: plugins.Info{Version: "2.0.0"}},
},
{
JSONData: plugins.JSONData{ID: "timeseries", Info: plugins.Info{Version: "3.0.0"}},
},
},
buildVersion: "10.0.0",
expectedPanels: []schemaversion.PanelPluginInfo{
{ID: "gauge", Version: "1.0.0"},
{ID: "stat", Version: "2.0.0"},
{ID: "timeseries", Version: "3.0.0"},
},
},
{
name: "should use build version when plugin version is empty",
plugins: []pluginstore.Plugin{
{
JSONData: plugins.JSONData{ID: "gauge", Info: plugins.Info{Version: ""}},
},
{
JSONData: plugins.JSONData{ID: "stat", Info: plugins.Info{Version: "2.0.0"}},
},
},
buildVersion: "10.0.0",
expectedPanels: []schemaversion.PanelPluginInfo{
{ID: "gauge", Version: "10.0.0"},
{ID: "stat", Version: "2.0.0"},
},
},
{
name: "should return empty slice when no plugins",
plugins: []pluginstore.Plugin{},
buildVersion: "10.0.0",
expectedPanels: []schemaversion.PanelPluginInfo{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create mock plugin store
mockStore := &mockPluginStore{
plugins: tt.plugins,
}
// Create mock setting
mockSetting := &setting.Cfg{
BuildVersion: tt.buildVersion,
}
// Create provider
provider := &PluginStorePanelProvider{
pluginStore: mockStore,
buildVersion: mockSetting.BuildVersion,
}
// Call the function
result := provider.GetPanels()
// Assert results
assert.Len(t, result, len(tt.expectedPanels))
for i, expected := range tt.expectedPanels {
assert.Equal(t, expected.ID, result[i].ID)
assert.Equal(t, expected.Version, result[i].Version)
}
})
}
}
func TestPluginStorePanelProvider_GetPanelPlugin(t *testing.T) {
tests := []struct {
name string
plugins []pluginstore.Plugin
buildVersion string
searchID string
expectedPanel schemaversion.PanelPluginInfo
}{
{
name: "should return panel plugin when found",
plugins: []pluginstore.Plugin{
{
JSONData: plugins.JSONData{ID: "gauge", Info: plugins.Info{Version: "1.0.0"}},
},
{
JSONData: plugins.JSONData{ID: "stat", Info: plugins.Info{Version: "2.0.0"}},
},
},
buildVersion: "10.0.0",
searchID: "stat",
expectedPanel: schemaversion.PanelPluginInfo{
ID: "stat",
Version: "2.0.0",
},
},
{
name: "should return panel plugin with build version when plugin version is empty",
plugins: []pluginstore.Plugin{
{
JSONData: plugins.JSONData{ID: "gauge", Info: plugins.Info{Version: ""}},
},
},
buildVersion: "10.0.0",
searchID: "gauge",
expectedPanel: schemaversion.PanelPluginInfo{
ID: "gauge",
Version: "10.0.0",
},
},
{
name: "should return empty panel plugin when not found",
plugins: []pluginstore.Plugin{
{
JSONData: plugins.JSONData{ID: "gauge", Info: plugins.Info{Version: "1.0.0"}},
},
},
buildVersion: "10.0.0",
searchID: "nonexistent",
expectedPanel: schemaversion.PanelPluginInfo{},
},
{
name: "should return empty panel plugin when no plugins exist",
plugins: []pluginstore.Plugin{},
buildVersion: "10.0.0",
searchID: "gauge",
expectedPanel: schemaversion.PanelPluginInfo{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockStore := &mockPluginStore{
plugins: tt.plugins,
}
mockSetting := &setting.Cfg{
BuildVersion: tt.buildVersion,
}
provider := &PluginStorePanelProvider{
pluginStore: mockStore,
buildVersion: mockSetting.BuildVersion,
}
result := provider.GetPanelPlugin(tt.searchID)
assert.Equal(t, tt.expectedPanel.ID, result.ID)
assert.Equal(t, tt.expectedPanel.Version, result.Version)
})
}
}
type mockPluginStore struct {
plugins []pluginstore.Plugin
}
func (m *mockPluginStore) Plugin(ctx context.Context, pluginID string) (pluginstore.Plugin, bool) {
for _, p := range m.plugins {
if p.ID == pluginID {
return p, true
}
}
return pluginstore.Plugin{}, false
}
func (m *mockPluginStore) Plugins(ctx context.Context, pluginTypes ...plugins.Type) []pluginstore.Plugin {
return m.plugins
}
+5 -12
View File
@@ -6,7 +6,6 @@ import (
"fmt"
"maps"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
"github.com/prometheus/client_golang/prometheus"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -28,6 +27,8 @@ import (
dashv2beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1"
"github.com/grafana/grafana/apps/dashboard/pkg/migration"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/conversion"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
@@ -37,8 +38,10 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver"
authsvc "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/client"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/dashboards"
dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service"
@@ -56,10 +59,6 @@ import (
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/client"
)
var (
@@ -167,9 +166,6 @@ func RegisterAPIService(
migration.RegisterMetrics(reg)
migration.Initialize(&datasourceInfoProvider{
datasourceService: datasourceService,
}, &PluginStorePanelProvider{
pluginStore: pluginStore,
buildVersion: cfg.BuildVersion,
})
apiregistration.RegisterAPI(builder)
return builder
@@ -184,10 +180,7 @@ func NewAPIService(ac claims.AccessClient, features featuremgmt.FeatureToggles,
logger := log.New("grafana-apiserver.dashboards")
migration.Initialize(datasourceProvider, &PluginStorePanelProvider{
pluginStore: pluginStore,
buildVersion: "unknown",
})
migration.Initialize(datasourceProvider)
return &DashboardsAPIBuilder{
log: logger,
@@ -75,6 +75,8 @@ const dataSources = {
}),
};
const pluginVersionForAutoMigrate = '12.1.0';
describe('Backend / Frontend result comparison', () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -157,11 +159,11 @@ describe('Backend / Frontend result comparison', () => {
logos: { small: 'small/logo', large: 'large/logo' },
screenshots: [],
updated: '2024-01-01',
version: '1.0.0',
version: pluginVersionForAutoMigrate,
};
}
if (!statPanelPlugin.meta.info.version) {
statPanelPlugin.meta.info.version = '1.0.0';
statPanelPlugin.meta.info.version = pluginVersionForAutoMigrate;
}
await panel.pluginLoaded(statPanelPlugin);
@@ -179,11 +181,11 @@ describe('Backend / Frontend result comparison', () => {
logos: { small: 'small/logo', large: 'large/logo' },
screenshots: [],
updated: '2024-01-01',
version: '1.0.0',
version: pluginVersionForAutoMigrate,
};
}
if (!tablePanelPlugin.meta.info.version) {
tablePanelPlugin.meta.info.version = '1.0.0';
tablePanelPlugin.meta.info.version = pluginVersionForAutoMigrate;
}
await panel.pluginLoaded(tablePanelPlugin as any);