Files
grafana/apps/dashboard/pkg/migration/schemaversion/v36.go
T
Ivan Ortega Alba 59c2f15433 Dashboard V0->V1 Migration: Schema migration v36 (#100757)
---------

Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com>
Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com>
Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
2025-06-16 15:53:41 +02:00

221 lines
6.6 KiB
Go

package schemaversion
// V36 migrates dashboard datasource references from string names to UIDs.
// This migration converts datasource references in annotations, template variables, and panels
// from the old format (string name or UID) to the new format (object with uid, type, apiVersion).
// This matches the frontend migration in DashboardMigrator.ts.
func V36(dsInfo DataSourceInfoProvider) SchemaVersionMigrationFunc {
datasources := dsInfo.GetDataSourceInfo()
return func(dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = int(36)
migrateAnnotations(dashboard, datasources)
migrateTemplateVariables(dashboard, datasources)
migratePanels(dashboard, datasources)
return nil
}
}
// getDataSourceRef creates a datasource reference object with uid, type and optional apiVersion
func getDataSourceRef(ds *DataSourceInfo) map[string]interface{} {
if ds == nil {
return nil
}
ref := map[string]interface{}{
"uid": ds.UID,
"type": ds.Type,
}
if ds.APIVersion != "" {
ref["apiVersion"] = ds.APIVersion
}
return ref
}
// getDefaultDSInstanceSettings returns the default datasource if one exists
func getDefaultDSInstanceSettings(datasources []DataSourceInfo) *DataSourceInfo {
for _, ds := range datasources {
if ds.Default {
return &DataSourceInfo{
UID: ds.UID,
Type: ds.Type,
Name: ds.Name,
APIVersion: ds.APIVersion,
}
}
}
return nil
}
// getInstanceSettings looks up a datasource by name or uid reference
func getInstanceSettings(nameOrRef interface{}, datasources []DataSourceInfo) *DataSourceInfo {
if nameOrRef == nil || nameOrRef == "default" {
return getDefaultDSInstanceSettings(datasources)
}
for _, ds := range datasources {
if str, ok := nameOrRef.(string); ok {
if str == ds.Name || str == ds.UID {
return &DataSourceInfo{
UID: ds.UID,
Type: ds.Type,
Name: ds.Name,
APIVersion: ds.APIVersion,
}
}
}
if ref, ok := nameOrRef.(map[string]interface{}); ok {
if uid, hasUID := ref["uid"]; hasUID {
if uid == ds.UID {
return &DataSourceInfo{
UID: ds.UID,
Type: ds.Type,
Name: ds.Name,
APIVersion: ds.APIVersion,
}
}
}
}
}
return nil
}
// migrateDatasourceNameToRef converts a datasource name/uid string to a reference object
// Matches the frontend migrateDatasourceNameToRef function in DashboardMigrator.ts
func migrateDatasourceNameToRef(nameOrRef interface{}, options map[string]bool, datasources []DataSourceInfo) map[string]interface{} {
if options["returnDefaultAsNull"] && (nameOrRef == nil || nameOrRef == "default") {
return nil
}
if dsRef, ok := nameOrRef.(map[string]interface{}); ok {
if _, hasUID := dsRef["uid"]; hasUID {
return dsRef
}
}
ds := getInstanceSettings(nameOrRef, datasources)
if ds != nil {
return getDataSourceRef(ds)
}
if dsName, ok := nameOrRef.(string); ok && dsName != "" {
return map[string]interface{}{
"uid": dsName,
}
}
return nil
}
// migrateAnnotations updates datasource references in dashboard annotations
func migrateAnnotations(dashboard map[string]interface{}, datasources []DataSourceInfo) {
annotations, ok := dashboard["annotations"].(map[string]interface{})
if !ok {
return
}
list, ok := annotations["list"].([]interface{})
if !ok {
return
}
for _, query := range list {
queryMap, ok := query.(map[string]interface{})
if !ok {
continue
}
if ds, exists := queryMap["datasource"]; exists {
queryMap["datasource"] = migrateDatasourceNameToRef(ds, map[string]bool{"returnDefaultAsNull": false}, datasources)
}
}
}
// migrateTemplateVariables updates datasource references in dashboard variables
func migrateTemplateVariables(dashboard map[string]interface{}, datasources []DataSourceInfo) {
templating, ok := dashboard["templating"].(map[string]interface{})
if !ok {
return
}
list, ok := templating["list"].([]interface{})
if !ok {
return
}
for _, variable := range list {
varMap, ok := variable.(map[string]interface{})
if !ok {
continue
}
if varType, ok := varMap["type"].(string); ok && varType == "query" {
if ds, exists := varMap["datasource"]; exists {
varMap["datasource"] = migrateDatasourceNameToRef(ds, map[string]bool{"returnDefaultAsNull": false}, datasources)
}
}
}
}
// migratePanels updates datasource references in dashboard panels
func migratePanels(dashboard map[string]interface{}, datasources []DataSourceInfo) {
if panels, ok := dashboard["panels"].([]interface{}); ok {
for _, panel := range panels {
if panelMap, ok := panel.(map[string]interface{}); ok {
migratePanelDatasources(panelMap, datasources)
}
}
}
}
// migratePanelDatasources updates datasource references in a single panel and its targets
func migratePanelDatasources(panelMap map[string]interface{}, datasources []DataSourceInfo) {
if targets, hasTargets := panelMap["targets"].([]interface{}); hasTargets && len(targets) > 0 {
panelDataSourceWasDefault := false
// Handle panel datasource
if ds, exists := panelMap["datasource"]; exists {
if ds == nil {
defaultDS := getDefaultDSInstanceSettings(datasources)
panelMap["datasource"] = getDataSourceRef(defaultDS)
panelDataSourceWasDefault = true
} else {
panelMap["datasource"] = migrateDatasourceNameToRef(ds, map[string]bool{"returnDefaultAsNull": true}, datasources)
}
}
// Handle target datasources
for _, target := range targets {
if targetMap, ok := target.(map[string]interface{}); ok {
ds, exists := targetMap["datasource"]
// Check if target datasource is null or has no uid
isNullOrNoUID := !exists || ds == nil
if !isNullOrNoUID {
if dsMap, ok := ds.(map[string]interface{}); ok {
if uid, hasUID := dsMap["uid"]; !hasUID || uid == nil {
isNullOrNoUID = true
}
}
}
if isNullOrNoUID {
// If panel doesn't have mixed datasource, use panel's datasource
if panelDS, ok := panelMap["datasource"].(map[string]interface{}); ok {
if uid, hasUID := panelDS["uid"].(string); hasUID && uid != "-- Mixed --" {
targetMap["datasource"] = panelDS
}
}
} else {
// Migrate existing target datasource
targetDS := migrateDatasourceNameToRef(ds, map[string]bool{"returnDefaultAsNull": false}, datasources)
targetMap["datasource"] = targetDS
}
// Update panel datasource if it was default and target is not an expression
if panelDataSourceWasDefault {
if targetDS, ok := targetMap["datasource"].(map[string]interface{}); ok {
if uid, ok := targetDS["uid"].(string); ok && uid != "__expr__" {
panelMap["datasource"] = targetDS
}
}
}
}
}
}
}