Dashboard Migrations: V28 singlestat panel and deprecated variable properties (#108416)

Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com>
This commit is contained in:
Haris Rozajac
2025-08-06 17:51:55 +02:00
committed by GitHub
co-authored by Ivan Ortega
parent 3dcda77462
commit 5ad751ea28
22 changed files with 3067 additions and 134 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ import (
)
func TestDashboardAPIBuilder_Mutate(t *testing.T) {
migration.Initialize(testutil.GetTestProvider())
migration.Initialize(testutil.GetTestDataSourceProvider(), testutil.GetTestPanelProvider())
tests := []struct {
name string
inputObj runtime.Object
@@ -0,0 +1,41 @@
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 {
plugins := p.pluginStore.Plugins(context.Background(), plugins.TypePanel)
panels := make([]schemaversion.PanelPluginInfo, len(plugins))
for i, plugin := range plugins {
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{}
}
@@ -0,0 +1,193 @@
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
View File
@@ -43,6 +43,7 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/librarypanels"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/search/sort"
@@ -101,6 +102,7 @@ func RegisterAPIService(
apiregistration builder.APIRegistrar,
dashboardService dashboards.DashboardService,
provisioningDashboardService dashboards.DashboardProvisioningService,
pluginStore pluginstore.Store,
datasourceService datasources.DataSourceService,
dashboardPermissions dashboards.PermissionsRegistrationService,
accessControl accesscontrol.AccessControl,
@@ -150,6 +152,9 @@ func RegisterAPIService(
}
migration.Initialize(&datasourceInfoProvider{
datasourceService: datasourceService,
}, &PluginStorePanelProvider{
pluginStore: pluginStore,
buildVersion: cfg.BuildVersion,
})
apiregistration.RegisterAPI(builder)
return builder
+2 -2
View File
@@ -732,7 +732,7 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser
identitySynchronizer := authnimpl.ProvideIdentitySynchronizer(authnimplService)
ldapImpl := service10.ProvideService(cfg, featureToggles, ssosettingsimplService)
apiService := api4.ProvideService(cfg, routeRegisterImpl, accessControl, userService, authinfoimplService, ossGroups, identitySynchronizer, orgService, ldapImpl, userAuthTokenService, bundleregistryService)
dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, dashboardFolderStoreImpl, libraryPanelService, eventualRestConfigProvider, userService)
dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, pluginstoreService, service15, dashboardServiceImpl, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, dashboardFolderStoreImpl, libraryPanelService, eventualRestConfigProvider, userService)
snapshotsAPIBuilder := dashboardsnapshot.RegisterAPIService(serviceImpl, apiserverService, cfg, featureToggles, sqlStore, registerer)
featureFlagAPIBuilder := featuretoggle.RegisterAPIService(featureManager, accessControl, apiserverService, cfg, registerer)
dataSourceAPIBuilder, err := datasource.RegisterAPIService(featureToggles, apiserverService, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, accessControl, registerer)
@@ -1297,7 +1297,7 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface {
identitySynchronizer := authnimpl.ProvideIdentitySynchronizer(authnimplService)
ldapImpl := service10.ProvideService(cfg, featureToggles, ssosettingsimplService)
apiService := api4.ProvideService(cfg, routeRegisterImpl, accessControl, userService, authinfoimplService, ossGroups, identitySynchronizer, orgService, ldapImpl, userAuthTokenService, bundleregistryService)
dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, dashboardFolderStoreImpl, libraryPanelService, eventualRestConfigProvider, userService)
dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, pluginstoreService, service15, dashboardServiceImpl, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, dashboardFolderStoreImpl, libraryPanelService, eventualRestConfigProvider, userService)
snapshotsAPIBuilder := dashboardsnapshot.RegisterAPIService(serviceImpl, apiserverService, cfg, featureToggles, sqlStore, registerer)
featureFlagAPIBuilder := featuretoggle.RegisterAPIService(featureManager, accessControl, apiserverService, cfg, registerer)
dataSourceAPIBuilder, err := datasource.RegisterAPIService(featureToggles, apiserverService, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, accessControl, registerer)