Schema: convert dashboards from v1beta1 to v2beta1 (#109037)

- Implement full conversion pipeline from v1beta1 → v2beta1
- Ensure frontend–backend parity for all dashboard serialization paths
- Add automatic data loss detection for conversions (panels, queries, annotations, links, variables)
- Extract atomic conversion functions for v0 → v1beta1 → v2alpha1 → v2beta1
- Introduce conversion metrics and detailed logging for loss tracking
- Normalize datasource resolution, defaults, and annotation processing
- Improve panel layout serialization and y-coordinate normalization
- Fix inconsistencies in nested panels and collapsed row behavior
- Refine variable handling:
  - Filter refId from variable query specs
  - Default variable refresh to 'never' (matches frontend)
  - Fix constant and interval variable handling for missing queries
- Unify schema defaults (enable, hide, iconColor, editable, liveNow)
- Fix pluginId usage (UID vs type) and datasource references
- Fix metrics.go bug swallowing errors (return nil → return err)
- Add tests for version-specific conversion error handling
- Add data loss detection tests using source/target version comparison
- Clean up lint issues, legacy code, and redundant files
- Update OpenAPI snapshots and migrated dashboards
- Improve backend migrator to reuse datasource provider and match frontend logic

Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com>
Co-authored-by: Oscar Kilhed <oscar.kilhed@grafana.com>
Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
This commit is contained in:
Ivan Ortega Alba
2025-11-12 11:43:46 +01:00
committed by GitHub
co-authored by Haris Rozajac Oscar Kilhed Stephanie Hingtgen
parent 09942c08db
commit e463781077
273 changed files with 75003 additions and 588 deletions
@@ -158,6 +158,8 @@ FieldConfigSource: {
defaults: FieldConfig
// Overrides are the options applied to specific fields overriding the defaults.
overrides: [...{
// Describes config override rules created when interacting with Grafana.
"__systemRef"?: string
matcher: MatcherConfig
properties: [...DynamicConfigValue]
}]
@@ -251,7 +253,8 @@ MatcherConfig: {
}
Threshold: {
value: number
// Value null means -Infinity
value: number | null
color: string
}
@@ -551,8 +551,9 @@ const (
// +k8s:openapi-gen=true
type DashboardThreshold struct {
Value float64 `json:"value"`
Color string `json:"color"`
// Value null means -Infinity
Value *float64 `json:"value"`
Color string `json:"color"`
}
// NewDashboardThreshold creates a new DashboardThreshold object.
@@ -1865,6 +1866,8 @@ func NewDashboardSpec() *DashboardSpec {
// +k8s:openapi-gen=true
type DashboardV2alpha1FieldConfigSourceOverrides struct {
// Describes config override rules created when interacting with Grafana.
SystemRef *string `json:"__systemRef,omitempty"`
Matcher DashboardMatcherConfig `json:"matcher"`
Properties []DashboardDynamicConfigValue `json:"properties"`
}
@@ -4430,9 +4430,9 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardThreshold(ref common.ReferenceC
Properties: map[string]spec.Schema{
"value": {
SchemaProps: spec.SchemaProps{
Default: 0,
Type: []string{"number"},
Format: "double",
Description: "Value null means -Infinity",
Type: []string{"number"},
Format: "double",
},
},
"color": {
@@ -4677,6 +4677,13 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1FieldConfigSourceOverri
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"__systemRef": {
SchemaProps: spec.SchemaProps{
Description: "Describes config override rules created when interacting with Grafana.",
Type: []string{"string"},
Format: "",
},
},
"matcher": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
@@ -67,6 +67,7 @@ API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/ap
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardStringOrArrayOfString,String
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardStringOrFloat64,Float64
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardStringOrFloat64,String
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardV2alpha1FieldConfigSourceOverrides,SystemRef
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,RangeMap
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,RegexMap
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,SpecialValueMap
@@ -160,6 +160,8 @@ FieldConfigSource: {
defaults: FieldConfig
// Overrides are the options applied to specific fields overriding the defaults.
overrides: [...{
// Describes config override rules created when interacting with Grafana.
"__systemRef"?: string
matcher: MatcherConfig
properties: [...DynamicConfigValue]
}]
@@ -253,7 +255,8 @@ MatcherConfig: {
}
Threshold: {
value: number
// Value null means -Infinity
value: number | null
color: string
}
@@ -479,7 +482,7 @@ DataQueryKind: {
PanelQuerySpec: {
query: DataQueryKind
refId: string
refId: string | *"A"
hidden: bool
}
@@ -880,7 +883,7 @@ IntervalVariableSpec: {
auto: bool | *false
auto_min: string | *""
auto_count: int | *0
refresh: VariableRefresh
refresh: "onTimeRangeChanged"
label?: string
hide: VariableHide
skipUrlSync: bool | *false
@@ -209,6 +209,7 @@ type DashboardPanelQuerySpec struct {
func NewDashboardPanelQuerySpec() *DashboardPanelQuerySpec {
return &DashboardPanelQuerySpec{
Query: *NewDashboardDataQueryKind(),
RefId: "A",
}
}
@@ -554,8 +555,9 @@ const (
// +k8s:openapi-gen=true
type DashboardThreshold struct {
Value float64 `json:"value"`
Color string `json:"color"`
// Value null means -Infinity
Value *float64 `json:"value"`
Color string `json:"color"`
}
// NewDashboardThreshold creates a new DashboardThreshold object.
@@ -1615,7 +1617,7 @@ type DashboardIntervalVariableSpec struct {
Auto bool `json:"auto"`
AutoMin string `json:"auto_min"`
AutoCount int64 `json:"auto_count"`
Refresh DashboardVariableRefresh `json:"refresh"`
Refresh string `json:"refresh"`
Label *string `json:"label,omitempty"`
Hide DashboardVariableHide `json:"hide"`
SkipUrlSync bool `json:"skipUrlSync"`
@@ -1639,7 +1641,7 @@ func NewDashboardIntervalVariableSpec() *DashboardIntervalVariableSpec {
Auto: false,
AutoMin: "",
AutoCount: 0,
Refresh: DashboardVariableRefreshNever,
Refresh: "onTimeRangeChanged",
Hide: DashboardVariableHideDontHide,
SkipUrlSync: false,
}
@@ -1926,6 +1928,8 @@ func NewDashboardV2beta1DataQueryKindDatasource() *DashboardV2beta1DataQueryKind
// +k8s:openapi-gen=true
type DashboardV2beta1FieldConfigSourceOverrides struct {
// Describes config override rules created when interacting with Grafana.
SystemRef *string `json:"__systemRef,omitempty"`
Matcher DashboardMatcherConfig `json:"matcher"`
Properties []DashboardDynamicConfigValue `json:"properties"`
}
@@ -4549,9 +4549,9 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardThreshold(ref common.ReferenceCa
Properties: map[string]spec.Schema{
"value": {
SchemaProps: spec.SchemaProps{
Default: 0,
Type: []string{"number"},
Format: "double",
Description: "Value null means -Infinity",
Type: []string{"number"},
Format: "double",
},
},
"color": {
@@ -4832,6 +4832,13 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardV2beta1FieldConfigSourceOverride
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"__systemRef": {
SchemaProps: spec.SchemaProps{
Description: "Describes config override rules created when interacting with Grafana.",
Type: []string{"string"},
Format: "",
},
},
"matcher": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
@@ -68,6 +68,7 @@ API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/ap
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardStringOrArrayOfString,String
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardStringOrFloat64,Float64
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardStringOrFloat64,String
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardV2beta1FieldConfigSourceOverrides,SystemRef
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,RangeMap
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,RegexMap
API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap,SpecialValueMap