fix: remove dsIndexProvider from Convert_V2alpha1_to_V0 (#115017)

This commit is contained in:
Rafael Bortolon Paulovic
2025-12-09 16:33:43 +01:00
committed by GitHub
parent 8602ec7924
commit 297e886e1b
4 changed files with 77 additions and 85 deletions
@@ -62,13 +62,13 @@ func RegisterConversions(s *runtime.Scheme, dsIndexProvider schemaversion.DataSo
// v2alpha1 conversions
if err := s.AddConversionFunc((*dashv2alpha1.Dashboard)(nil), (*dashv0.Dashboard)(nil),
withConversionMetrics(dashv2alpha1.APIVERSION, dashv0.APIVERSION, func(a, b interface{}, scope conversion.Scope) error {
return Convert_V2alpha1_to_V0(a.(*dashv2alpha1.Dashboard), b.(*dashv0.Dashboard), scope, dsIndexProvider)
return Convert_V2alpha1_to_V0(a.(*dashv2alpha1.Dashboard), b.(*dashv0.Dashboard), scope)
})); err != nil {
return err
}
if err := s.AddConversionFunc((*dashv2alpha1.Dashboard)(nil), (*dashv1.Dashboard)(nil),
withConversionMetrics(dashv2alpha1.APIVERSION, dashv1.APIVERSION, func(a, b interface{}, scope conversion.Scope) error {
return Convert_V2alpha1_to_V1beta1(a.(*dashv2alpha1.Dashboard), b.(*dashv1.Dashboard), scope, dsIndexProvider)
return Convert_V2alpha1_to_V1beta1(a.(*dashv2alpha1.Dashboard), b.(*dashv1.Dashboard), scope)
})); err != nil {
return err
}
@@ -11,10 +11,10 @@ import (
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func Convert_V2alpha1_to_V0(in *dashv2alpha1.Dashboard, out *dashv0.Dashboard, scope conversion.Scope, dsIndexProvider schemaversion.DataSourceIndexProvider) error {
func Convert_V2alpha1_to_V0(in *dashv2alpha1.Dashboard, out *dashv0.Dashboard, scope conversion.Scope) error {
// Convert v2alpha1 → v1beta1 first, then v1beta1 → v0
v1beta1 := &dashv1.Dashboard{}
if err := ConvertDashboard_V2alpha1_to_V1beta1(in, v1beta1, scope, dsIndexProvider); err != nil {
if err := ConvertDashboard_V2alpha1_to_V1beta1(in, v1beta1, scope); err != nil {
out.ObjectMeta = in.ObjectMeta
out.APIVersion = dashv0.APIVERSION
out.Kind = in.Kind
@@ -53,13 +53,13 @@ func Convert_V2alpha1_to_V0(in *dashv2alpha1.Dashboard, out *dashv0.Dashboard, s
return nil
}
func Convert_V2alpha1_to_V1beta1(in *dashv2alpha1.Dashboard, out *dashv1.Dashboard, scope conversion.Scope, dsIndexProvider schemaversion.DataSourceIndexProvider) error {
func Convert_V2alpha1_to_V1beta1(in *dashv2alpha1.Dashboard, out *dashv1.Dashboard, scope conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
out.APIVersion = dashv1.APIVERSION
out.Kind = in.Kind
// Convert the spec
if err := ConvertDashboard_V2alpha1_to_V1beta1(in, out, scope, dsIndexProvider); err != nil {
if err := ConvertDashboard_V2alpha1_to_V1beta1(in, out, scope); err != nil {
out.Status = dashv1.DashboardStatus{
Conversion: &dashv1.DashboardConversionStatus{
StoredVersion: ptr.To(dashv2alpha1.VERSION),
@@ -179,7 +179,7 @@ func Convert_V2beta1_to_V1beta1(in *dashv2beta1.Dashboard, out *dashv1.Dashboard
// Convert v2alpha1 → v1beta1
// Note: ConvertDashboard_V2alpha1_to_V1beta1 will set out.ObjectMeta from v2alpha1,
// but we've already set it from the original input, so it will be preserved
if err := ConvertDashboard_V2alpha1_to_V1beta1(v2alpha1, out, scope, dsIndexProvider); err != nil {
if err := ConvertDashboard_V2alpha1_to_V1beta1(v2alpha1, out, scope); err != nil {
out.Status = dashv1.DashboardStatus{
Conversion: &dashv1.DashboardConversionStatus{
StoredVersion: ptr.To(dashv2beta1.VERSION),
@@ -39,7 +39,7 @@ func TestV2alpha1ConversionErrorHandling(t *testing.T) {
}
target := &dashv1.Dashboard{}
err := Convert_V2alpha1_to_V1beta1(source, target, nil, dsProvider)
err := Convert_V2alpha1_to_V1beta1(source, target, nil)
// Convert_V2alpha1_to_V1beta1 doesn't return error, just sets status
require.NoError(t, err, "Convert_V2alpha1_to_V1beta1 doesn't return error")
@@ -1,14 +1,12 @@
package conversion
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/conversion"
dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
dashv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
"k8s.io/apimachinery/pkg/conversion"
)
// ConvertDashboard_V2alpha1_to_V1beta1 converts a v2alpha1 dashboard to v1beta1 format.
@@ -16,19 +14,13 @@ import (
// that represents the v1 dashboard JSON format.
// The dsIndexProvider is used to resolve default datasources when queries/variables/annotations
// don't have explicit datasource references.
func ConvertDashboard_V2alpha1_to_V1beta1(in *dashv2alpha1.Dashboard, out *dashv1.Dashboard, scope conversion.Scope, dsIndexProvider schemaversion.DataSourceIndexProvider) error {
func ConvertDashboard_V2alpha1_to_V1beta1(in *dashv2alpha1.Dashboard, out *dashv1.Dashboard, scope conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
out.APIVersion = dashv1.APIVERSION
out.Kind = in.Kind // Preserve the Kind from input (should be "Dashboard")
// Get datasource index for resolving default datasources
var dsIndex *schemaversion.DatasourceIndex
if dsIndexProvider != nil {
dsIndex = dsIndexProvider.Index(context.Background())
}
// Convert the spec to v1beta1 unstructured format
dashboardJSON, err := convertDashboardSpec_V2alpha1_to_V1beta1(&in.Spec, dsIndex)
dashboardJSON, err := convertDashboardSpec_V2alpha1_to_V1beta1(&in.Spec)
if err != nil {
return fmt.Errorf("failed to convert dashboard spec: %w", err)
}
@@ -39,7 +31,7 @@ func ConvertDashboard_V2alpha1_to_V1beta1(in *dashv2alpha1.Dashboard, out *dashv
return nil
}
func convertDashboardSpec_V2alpha1_to_V1beta1(in *dashv2alpha1.DashboardSpec, dsIndex *schemaversion.DatasourceIndex) (map[string]interface{}, error) {
func convertDashboardSpec_V2alpha1_to_V1beta1(in *dashv2alpha1.DashboardSpec) (map[string]interface{}, error) {
dashboard := make(map[string]interface{})
// Convert basic fields
@@ -75,7 +67,7 @@ func convertDashboardSpec_V2alpha1_to_V1beta1(in *dashv2alpha1.DashboardSpec, ds
}
// Convert panels from elements and layout
panels, err := convertPanelsFromElementsAndLayout(in.Elements, in.Layout, dsIndex)
panels, err := convertPanelsFromElementsAndLayout(in.Elements, in.Layout)
if err != nil {
return nil, fmt.Errorf("failed to convert panels: %w", err)
}
@@ -90,7 +82,7 @@ func convertDashboardSpec_V2alpha1_to_V1beta1(in *dashv2alpha1.DashboardSpec, ds
}
// Convert variables
variables := convertVariablesToV1(in.Variables, dsIndex)
variables := convertVariablesToV1(in.Variables)
if len(variables) > 0 {
dashboard["templating"] = map[string]interface{}{
"list": variables,
@@ -98,7 +90,7 @@ func convertDashboardSpec_V2alpha1_to_V1beta1(in *dashv2alpha1.DashboardSpec, ds
}
// Convert annotations - always include even if empty to prevent DashboardModel from adding built-in
annotations := convertAnnotationsToV1(in.Annotations, dsIndex)
annotations := convertAnnotationsToV1(in.Annotations)
dashboard["annotations"] = map[string]interface{}{
"list": annotations,
}
@@ -236,28 +228,28 @@ func countTotalPanels(panels []interface{}) int {
// - RowsLayout: Rows become row panels; nested structures are flattened
// - AutoGridLayout: Calculates gridPos based on column count and row height
// - TabsLayout: Tabs become expanded row panels; content is flattened
func convertPanelsFromElementsAndLayout(elements map[string]dashv2alpha1.DashboardElement, layout dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind, dsIndex *schemaversion.DatasourceIndex) ([]interface{}, error) {
func convertPanelsFromElementsAndLayout(elements map[string]dashv2alpha1.DashboardElement, layout dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind) ([]interface{}, error) {
if layout.GridLayoutKind != nil {
return convertGridLayoutToPanels(elements, layout.GridLayoutKind, dsIndex)
return convertGridLayoutToPanels(elements, layout.GridLayoutKind)
}
if layout.RowsLayoutKind != nil {
return convertRowsLayoutToPanels(elements, layout.RowsLayoutKind, dsIndex)
return convertRowsLayoutToPanels(elements, layout.RowsLayoutKind)
}
if layout.AutoGridLayoutKind != nil {
return convertAutoGridLayoutToPanels(elements, layout.AutoGridLayoutKind, dsIndex)
return convertAutoGridLayoutToPanels(elements, layout.AutoGridLayoutKind)
}
if layout.TabsLayoutKind != nil {
return convertTabsLayoutToPanels(elements, layout.TabsLayoutKind, dsIndex)
return convertTabsLayoutToPanels(elements, layout.TabsLayoutKind)
}
// No layout specified, return empty panels
return []interface{}{}, nil
}
func convertGridLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement, gridLayout *dashv2alpha1.DashboardGridLayoutKind, dsIndex *schemaversion.DatasourceIndex) ([]interface{}, error) {
func convertGridLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement, gridLayout *dashv2alpha1.DashboardGridLayoutKind) ([]interface{}, error) {
panels := make([]interface{}, 0, len(gridLayout.Spec.Items))
for _, item := range gridLayout.Spec.Items {
@@ -266,7 +258,7 @@ func convertGridLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement
return nil, fmt.Errorf("panel with uid %s not found in the dashboard elements", item.Spec.Element.Name)
}
panel, err := convertPanelFromElement(&element, &item, dsIndex)
panel, err := convertPanelFromElement(&element, &item)
if err != nil {
return nil, fmt.Errorf("failed to convert panel %s: %w", item.Spec.Element.Name, err)
}
@@ -279,21 +271,21 @@ func convertGridLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement
// convertRowsLayoutToPanels converts a RowsLayout to V1 panels.
// All nested structures (rows within rows, tabs within rows) are flattened to the root level.
// Each row becomes a row panel, and nested content is added sequentially after it.
func convertRowsLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement, rowsLayout *dashv2alpha1.DashboardRowsLayoutKind, dsIndex *schemaversion.DatasourceIndex) ([]interface{}, error) {
return convertNestedLayoutToPanels(elements, rowsLayout, nil, dsIndex, 0)
func convertRowsLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement, rowsLayout *dashv2alpha1.DashboardRowsLayoutKind) ([]interface{}, error) {
return convertNestedLayoutToPanels(elements, rowsLayout, nil, 0)
}
// convertNestedLayoutToPanels handles arbitrary nesting of RowsLayout and TabsLayout.
// It processes each row/tab in order, tracking Y position to ensure panels don't overlap.
// The function recursively flattens nested structures to produce a flat V1 panel array.
func convertNestedLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement, rowsLayout *dashv2alpha1.DashboardRowsLayoutKind, tabsLayout *dashv2alpha1.DashboardTabsLayoutKind, dsIndex *schemaversion.DatasourceIndex, yOffset int64) ([]interface{}, error) {
func convertNestedLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement, rowsLayout *dashv2alpha1.DashboardRowsLayoutKind, tabsLayout *dashv2alpha1.DashboardTabsLayoutKind, yOffset int64) ([]interface{}, error) {
panels := make([]interface{}, 0)
currentY := yOffset
// Process RowsLayout
if rowsLayout != nil {
for _, row := range rowsLayout.Spec.Rows {
rowPanels, newY, err := processRowItem(elements, &row, dsIndex, currentY)
rowPanels, newY, err := processRowItem(elements, &row, currentY)
if err != nil {
return nil, err
}
@@ -305,7 +297,7 @@ func convertNestedLayoutToPanels(elements map[string]dashv2alpha1.DashboardEleme
// Process TabsLayout (tabs are converted to rows)
if tabsLayout != nil {
for _, tab := range tabsLayout.Spec.Tabs {
tabPanels, newY, err := processTabItem(elements, &tab, dsIndex, currentY)
tabPanels, newY, err := processTabItem(elements, &tab, currentY)
if err != nil {
return nil, err
}
@@ -324,7 +316,7 @@ func convertNestedLayoutToPanels(elements map[string]dashv2alpha1.DashboardEleme
// - Collapsed row: Panels stored inside row.panels with absolute Y positions
// - Expanded row: Panels added to top level after the row panel
// - Nested layouts: Parent row is preserved; nested content is flattened after it
func processRowItem(elements map[string]dashv2alpha1.DashboardElement, row *dashv2alpha1.DashboardRowsLayoutRowKind, dsIndex *schemaversion.DatasourceIndex, startY int64) ([]interface{}, int64, error) {
func processRowItem(elements map[string]dashv2alpha1.DashboardElement, row *dashv2alpha1.DashboardRowsLayoutRowKind, startY int64) ([]interface{}, int64, error) {
panels := make([]interface{}, 0)
currentY := startY
@@ -354,7 +346,7 @@ func processRowItem(elements map[string]dashv2alpha1.DashboardElement, row *dash
}
// Then process nested rows
nestedPanels, err := convertNestedLayoutToPanels(elements, row.Spec.Layout.RowsLayoutKind, nil, dsIndex, currentY)
nestedPanels, err := convertNestedLayoutToPanels(elements, row.Spec.Layout.RowsLayoutKind, nil, currentY)
if err != nil {
return nil, 0, err
}
@@ -387,7 +379,7 @@ func processRowItem(elements map[string]dashv2alpha1.DashboardElement, row *dash
}
// Then process nested tabs
nestedPanels, err := convertNestedLayoutToPanels(elements, nil, row.Spec.Layout.TabsLayoutKind, dsIndex, currentY)
nestedPanels, err := convertNestedLayoutToPanels(elements, nil, row.Spec.Layout.TabsLayoutKind, currentY)
if err != nil {
return nil, 0, err
}
@@ -429,7 +421,7 @@ func processRowItem(elements map[string]dashv2alpha1.DashboardElement, row *dash
// Add collapsed panels if row is collapsed (panels use absolute Y positions)
if isCollapsed {
collapsedPanels, err := extractCollapsedPanelsWithAbsoluteY(elements, &row.Spec.Layout, dsIndex, currentY+1)
collapsedPanels, err := extractCollapsedPanelsWithAbsoluteY(elements, &row.Spec.Layout, currentY+1)
if err != nil {
return nil, 0, err
}
@@ -444,7 +436,7 @@ func processRowItem(elements map[string]dashv2alpha1.DashboardElement, row *dash
// Add panels from row layout (only for expanded rows or hidden header rows)
if !isCollapsed || isHiddenHeader {
rowPanels, newY, err := extractExpandedPanels(elements, &row.Spec.Layout, dsIndex, currentY, isHiddenHeader, startY)
rowPanels, newY, err := extractExpandedPanels(elements, &row.Spec.Layout, currentY, isHiddenHeader, startY)
if err != nil {
return nil, 0, err
}
@@ -459,7 +451,7 @@ func processRowItem(elements map[string]dashv2alpha1.DashboardElement, row *dash
// Each tab becomes an expanded row panel (collapsed=false) with an empty panels array.
// The tab's content is flattened and added to the top level after the row panel.
// Nested layouts within the tab are recursively processed.
func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dashv2alpha1.DashboardTabsLayoutTabKind, dsIndex *schemaversion.DatasourceIndex, startY int64) ([]interface{}, int64, error) {
func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dashv2alpha1.DashboardTabsLayoutTabKind, startY int64) ([]interface{}, int64, error) {
panels := make([]interface{}, 0)
currentY := startY
@@ -487,7 +479,7 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash
// Handle nested layouts inside the tab
if tab.Spec.Layout.RowsLayoutKind != nil {
// Nested RowsLayout inside tab
nestedPanels, err := convertNestedLayoutToPanels(elements, tab.Spec.Layout.RowsLayoutKind, nil, dsIndex, currentY)
nestedPanels, err := convertNestedLayoutToPanels(elements, tab.Spec.Layout.RowsLayoutKind, nil, currentY)
if err != nil {
return nil, 0, err
}
@@ -495,7 +487,7 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash
currentY = getMaxYFromPanels(nestedPanels, currentY)
} else if tab.Spec.Layout.TabsLayoutKind != nil {
// Nested TabsLayout inside tab
nestedPanels, err := convertNestedLayoutToPanels(elements, nil, tab.Spec.Layout.TabsLayoutKind, dsIndex, currentY)
nestedPanels, err := convertNestedLayoutToPanels(elements, nil, tab.Spec.Layout.TabsLayoutKind, currentY)
if err != nil {
return nil, 0, err
}
@@ -512,7 +504,7 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash
adjustedItem := item
adjustedItem.Spec.Y = item.Spec.Y + currentY
panel, err := convertPanelFromElement(&element, &adjustedItem, dsIndex)
panel, err := convertPanelFromElement(&element, &adjustedItem)
if err != nil {
return nil, 0, fmt.Errorf("failed to convert panel %s: %w", item.Spec.Element.Name, err)
}
@@ -525,7 +517,7 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash
}
} else if tab.Spec.Layout.AutoGridLayoutKind != nil {
// AutoGridLayout inside tab - convert with Y offset
autoGridPanels, err := convertAutoGridLayoutToPanelsWithOffset(elements, tab.Spec.Layout.AutoGridLayoutKind, dsIndex, currentY)
autoGridPanels, err := convertAutoGridLayoutToPanelsWithOffset(elements, tab.Spec.Layout.AutoGridLayoutKind, currentY)
if err != nil {
return nil, 0, err
}
@@ -540,7 +532,7 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash
// Panels are positioned with absolute Y coordinates (baseY + relative Y).
// This matches V1 behavior where collapsed row panels store their children
// with Y positions as if the row were expanded at that location.
func extractCollapsedPanelsWithAbsoluteY(elements map[string]dashv2alpha1.DashboardElement, layout *dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind, dsIndex *schemaversion.DatasourceIndex, baseY int64) ([]interface{}, error) {
func extractCollapsedPanelsWithAbsoluteY(elements map[string]dashv2alpha1.DashboardElement, layout *dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind, baseY int64) ([]interface{}, error) {
panels := make([]interface{}, 0)
if layout.GridLayoutKind != nil {
@@ -552,7 +544,7 @@ func extractCollapsedPanelsWithAbsoluteY(elements map[string]dashv2alpha1.Dashbo
// Create a copy with adjusted Y position
adjustedItem := item
adjustedItem.Spec.Y = item.Spec.Y + baseY
panel, err := convertPanelFromElement(&element, &adjustedItem, dsIndex)
panel, err := convertPanelFromElement(&element, &adjustedItem)
if err != nil {
return nil, fmt.Errorf("failed to convert panel %s: %w", item.Spec.Element.Name, err)
}
@@ -561,7 +553,7 @@ func extractCollapsedPanelsWithAbsoluteY(elements map[string]dashv2alpha1.Dashbo
}
// Handle AutoGridLayout for collapsed rows with Y offset
if layout.AutoGridLayoutKind != nil {
autoGridPanels, err := convertAutoGridLayoutToPanelsWithOffset(elements, layout.AutoGridLayoutKind, dsIndex, baseY)
autoGridPanels, err := convertAutoGridLayoutToPanelsWithOffset(elements, layout.AutoGridLayoutKind, baseY)
if err != nil {
return nil, err
}
@@ -571,7 +563,7 @@ func extractCollapsedPanelsWithAbsoluteY(elements map[string]dashv2alpha1.Dashbo
if layout.RowsLayoutKind != nil {
currentY := baseY
for _, row := range layout.RowsLayoutKind.Spec.Rows {
nestedPanels, err := extractCollapsedPanelsWithAbsoluteY(elements, &row.Spec.Layout, dsIndex, currentY)
nestedPanels, err := extractCollapsedPanelsWithAbsoluteY(elements, &row.Spec.Layout, currentY)
if err != nil {
return nil, err
}
@@ -582,7 +574,7 @@ func extractCollapsedPanelsWithAbsoluteY(elements map[string]dashv2alpha1.Dashbo
if layout.TabsLayoutKind != nil {
currentY := baseY
for _, tab := range layout.TabsLayoutKind.Spec.Tabs {
nestedPanels, err := extractCollapsedPanelsFromTabLayoutWithAbsoluteY(elements, &tab.Spec.Layout, dsIndex, currentY)
nestedPanels, err := extractCollapsedPanelsFromTabLayoutWithAbsoluteY(elements, &tab.Spec.Layout, currentY)
if err != nil {
return nil, err
}
@@ -596,7 +588,7 @@ func extractCollapsedPanelsWithAbsoluteY(elements map[string]dashv2alpha1.Dashbo
// extractCollapsedPanelsFromTabLayoutWithAbsoluteY extracts panels from a tab layout with absolute Y.
// Similar to extractCollapsedPanelsWithAbsoluteY but handles the tab-specific layout type.
func extractCollapsedPanelsFromTabLayoutWithAbsoluteY(elements map[string]dashv2alpha1.DashboardElement, layout *dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind, dsIndex *schemaversion.DatasourceIndex, baseY int64) ([]interface{}, error) {
func extractCollapsedPanelsFromTabLayoutWithAbsoluteY(elements map[string]dashv2alpha1.DashboardElement, layout *dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind, baseY int64) ([]interface{}, error) {
panels := make([]interface{}, 0)
if layout.GridLayoutKind != nil {
@@ -607,7 +599,7 @@ func extractCollapsedPanelsFromTabLayoutWithAbsoluteY(elements map[string]dashv2
}
adjustedItem := item
adjustedItem.Spec.Y = item.Spec.Y + baseY
panel, err := convertPanelFromElement(&element, &adjustedItem, dsIndex)
panel, err := convertPanelFromElement(&element, &adjustedItem)
if err != nil {
return nil, fmt.Errorf("failed to convert panel %s: %w", item.Spec.Element.Name, err)
}
@@ -615,7 +607,7 @@ func extractCollapsedPanelsFromTabLayoutWithAbsoluteY(elements map[string]dashv2
}
}
if layout.AutoGridLayoutKind != nil {
autoGridPanels, err := convertAutoGridLayoutToPanelsWithOffset(elements, layout.AutoGridLayoutKind, dsIndex, baseY)
autoGridPanels, err := convertAutoGridLayoutToPanelsWithOffset(elements, layout.AutoGridLayoutKind, baseY)
if err != nil {
return nil, err
}
@@ -624,7 +616,7 @@ func extractCollapsedPanelsFromTabLayoutWithAbsoluteY(elements map[string]dashv2
if layout.RowsLayoutKind != nil {
currentY := baseY
for _, row := range layout.RowsLayoutKind.Spec.Rows {
nestedPanels, err := extractCollapsedPanelsWithAbsoluteY(elements, &row.Spec.Layout, dsIndex, currentY)
nestedPanels, err := extractCollapsedPanelsWithAbsoluteY(elements, &row.Spec.Layout, currentY)
if err != nil {
return nil, err
}
@@ -635,7 +627,7 @@ func extractCollapsedPanelsFromTabLayoutWithAbsoluteY(elements map[string]dashv2
if layout.TabsLayoutKind != nil {
currentY := baseY
for _, tab := range layout.TabsLayoutKind.Spec.Tabs {
nestedPanels, err := extractCollapsedPanelsFromTabLayoutWithAbsoluteY(elements, &tab.Spec.Layout, dsIndex, currentY)
nestedPanels, err := extractCollapsedPanelsFromTabLayoutWithAbsoluteY(elements, &tab.Spec.Layout, currentY)
if err != nil {
return nil, err
}
@@ -679,7 +671,7 @@ func getLayoutHeightFromTab(layout *dashv2alpha1.DashboardGridLayoutKindOrRowsLa
// - Explicit row: Add (currentY - 1) to relative Y for absolute positioning
//
// Returns the panels and the new Y position for the next row.
func extractExpandedPanels(elements map[string]dashv2alpha1.DashboardElement, layout *dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind, dsIndex *schemaversion.DatasourceIndex, currentY int64, isHiddenHeader bool, startY int64) ([]interface{}, int64, error) {
func extractExpandedPanels(elements map[string]dashv2alpha1.DashboardElement, layout *dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind, currentY int64, isHiddenHeader bool, startY int64) ([]interface{}, int64, error) {
panels := make([]interface{}, 0)
// For hidden headers, don't track Y changes (matches original behavior)
maxY := startY
@@ -700,7 +692,7 @@ func extractExpandedPanels(elements map[string]dashv2alpha1.DashboardElement, la
}
// For hidden headers: don't adjust Y, keep item.Spec.Y as-is
panel, err := convertPanelFromElement(&element, &adjustedItem, dsIndex)
panel, err := convertPanelFromElement(&element, &adjustedItem)
if err != nil {
return nil, 0, fmt.Errorf("failed to convert panel %s: %w", item.Spec.Element.Name, err)
}
@@ -725,7 +717,7 @@ func extractExpandedPanels(elements map[string]dashv2alpha1.DashboardElement, la
yOffset = currentY - 1
}
autoGridPanels, err := convertAutoGridLayoutToPanelsWithOffset(elements, layout.AutoGridLayoutKind, dsIndex, yOffset)
autoGridPanels, err := convertAutoGridLayoutToPanelsWithOffset(elements, layout.AutoGridLayoutKind, yOffset)
if err != nil {
return nil, 0, err
}
@@ -788,7 +780,7 @@ func getLayoutHeight(layout *dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayou
// convertAutoGridLayoutToPanelsWithOffset converts AutoGridLayout with a Y offset.
// Same as convertAutoGridLayoutToPanels but starts at yOffset instead of 0.
// Used when AutoGridLayout appears inside rows or tabs.
func convertAutoGridLayoutToPanelsWithOffset(elements map[string]dashv2alpha1.DashboardElement, autoGridLayout *dashv2alpha1.DashboardAutoGridLayoutKind, dsIndex *schemaversion.DatasourceIndex, yOffset int64) ([]interface{}, error) {
func convertAutoGridLayoutToPanelsWithOffset(elements map[string]dashv2alpha1.DashboardElement, autoGridLayout *dashv2alpha1.DashboardAutoGridLayoutKind, yOffset int64) ([]interface{}, error) {
panels := make([]interface{}, 0, len(autoGridLayout.Spec.Items))
const (
@@ -850,7 +842,7 @@ func convertAutoGridLayoutToPanelsWithOffset(elements map[string]dashv2alpha1.Da
},
}
panel, err := convertPanelFromElement(&element, &gridItem, dsIndex)
panel, err := convertPanelFromElement(&element, &gridItem)
if err != nil {
return nil, fmt.Errorf("failed to convert panel %s: %w", item.Spec.Element.Name, err)
}
@@ -876,7 +868,7 @@ func convertAutoGridLayoutToPanelsWithOffset(elements map[string]dashv2alpha1.Da
//
// Width: 24 / maxColumnCount (default 3 columns = 8 units wide)
// Height: Predefined grid units per mode (see pixelsToGridUnits for custom)
func convertAutoGridLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement, autoGridLayout *dashv2alpha1.DashboardAutoGridLayoutKind, dsIndex *schemaversion.DatasourceIndex) ([]interface{}, error) {
func convertAutoGridLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement, autoGridLayout *dashv2alpha1.DashboardAutoGridLayoutKind) ([]interface{}, error) {
panels := make([]interface{}, 0, len(autoGridLayout.Spec.Items))
const (
@@ -963,7 +955,7 @@ func convertAutoGridLayoutToPanels(elements map[string]dashv2alpha1.DashboardEle
}
}
panel, err := convertPanelFromElement(&element, &gridItem, dsIndex)
panel, err := convertPanelFromElement(&element, &gridItem)
if err != nil {
return nil, fmt.Errorf("failed to convert panel %s: %w", item.Spec.Element.Name, err)
}
@@ -984,11 +976,11 @@ func convertAutoGridLayoutToPanels(elements map[string]dashv2alpha1.DashboardEle
// V1 has no native tab concept, so tabs are converted to expanded row panels.
// Each tab becomes a row panel (collapsed=false, panels=[]) with its content
// flattened to the top level. Tab order is preserved in the output.
func convertTabsLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement, tabsLayout *dashv2alpha1.DashboardTabsLayoutKind, dsIndex *schemaversion.DatasourceIndex) ([]interface{}, error) {
return convertNestedLayoutToPanels(elements, nil, tabsLayout, dsIndex, 0)
func convertTabsLayoutToPanels(elements map[string]dashv2alpha1.DashboardElement, tabsLayout *dashv2alpha1.DashboardTabsLayoutKind) ([]interface{}, error) {
return convertNestedLayoutToPanels(elements, nil, tabsLayout, 0)
}
func convertPanelFromElement(element *dashv2alpha1.DashboardElement, layoutItem *dashv2alpha1.DashboardGridLayoutItemKind, dsIndex *schemaversion.DatasourceIndex) (map[string]interface{}, error) {
func convertPanelFromElement(element *dashv2alpha1.DashboardElement, layoutItem *dashv2alpha1.DashboardGridLayoutItemKind) (map[string]interface{}, error) {
panel := make(map[string]interface{})
// Set grid position
@@ -1017,7 +1009,7 @@ func convertPanelFromElement(element *dashv2alpha1.DashboardElement, layoutItem
}
if element.PanelKind != nil {
return convertPanelKindToV1(element.PanelKind, panel, dsIndex)
return convertPanelKindToV1(element.PanelKind, panel)
}
if element.LibraryPanelKind != nil {
@@ -1027,7 +1019,7 @@ func convertPanelFromElement(element *dashv2alpha1.DashboardElement, layoutItem
return nil, fmt.Errorf("element has neither PanelKind nor LibraryPanelKind")
}
func convertPanelKindToV1(panelKind *dashv2alpha1.DashboardPanelKind, panel map[string]interface{}, dsIndex *schemaversion.DatasourceIndex) (map[string]interface{}, error) {
func convertPanelKindToV1(panelKind *dashv2alpha1.DashboardPanelKind, panel map[string]interface{}) (map[string]interface{}, error) {
spec := panelKind.Spec
panel["id"] = int(spec.Id)
@@ -1069,14 +1061,14 @@ func convertPanelKindToV1(panelKind *dashv2alpha1.DashboardPanelKind, panel map[
// Convert queries (targets)
targets := make([]map[string]interface{}, 0, len(spec.Data.Spec.Queries))
for _, query := range spec.Data.Spec.Queries {
target := convertPanelQueryToV1(&query, dsIndex)
target := convertPanelQueryToV1(&query)
targets = append(targets, target)
}
panel["targets"] = targets
// Detect mixed datasource - set panel.datasource to "mixed" if queries use different datasources
// This matches the frontend behavior in getPanelDataSource (layoutSerializers/utils.ts)
if mixedDS := detectMixedDatasource(spec.Data.Spec.Queries, dsIndex); mixedDS != nil {
if mixedDS := detectMixedDatasource(spec.Data.Spec.Queries); mixedDS != nil {
panel["datasource"] = mixedDS
}
@@ -1125,7 +1117,7 @@ func convertPanelKindToV1(panelKind *dashv2alpha1.DashboardPanelKind, panel map[
return panel, nil
}
func convertPanelQueryToV1(query *dashv2alpha1.DashboardPanelQueryKind, dsIndex *schemaversion.DatasourceIndex) map[string]interface{} {
func convertPanelQueryToV1(query *dashv2alpha1.DashboardPanelQueryKind) map[string]interface{} {
target := make(map[string]interface{})
// Copy query spec (excluding refId, hide, datasource which are handled separately)
@@ -1150,7 +1142,7 @@ func convertPanelQueryToV1(query *dashv2alpha1.DashboardPanelQueryKind, dsIndex
}
// Resolve datasource based on V2 input (reuse shared function)
datasource := getDataSourceForQuery(query.Spec.Datasource, query.Spec.Query.Kind, nil)
datasource := getDataSourceForQuery(query.Spec.Datasource, query.Spec.Query.Kind)
if datasource != nil {
target["datasource"] = datasource
}
@@ -1164,7 +1156,7 @@ func convertPanelQueryToV1(query *dashv2alpha1.DashboardPanelQueryKind, dsIndex
// - Else if queryKind (type) is non-empty → return {type} only
// - Else → return nil (no datasource)
// Used for variables and annotations. Panel queries use convertPanelQueryToV1Target.
func getDataSourceForQuery(explicitDS *dashv2alpha1.DashboardDataSourceRef, queryKind string, _ *schemaversion.DatasourceIndex) map[string]interface{} {
func getDataSourceForQuery(explicitDS *dashv2alpha1.DashboardDataSourceRef, queryKind string) map[string]interface{} {
// Case 1: Explicit datasource with UID provided
if explicitDS != nil && explicitDS.Uid != nil && *explicitDS.Uid != "" {
datasource := map[string]interface{}{
@@ -1195,7 +1187,7 @@ func getDataSourceForQuery(explicitDS *dashv2alpha1.DashboardDataSourceRef, quer
// Compares based on V2 input without runtime resolution:
// - If query has explicit datasource.uid → use that UID and type
// - Else → use query.Kind as type (empty UID)
func detectMixedDatasource(queries []dashv2alpha1.DashboardPanelQueryKind, _ *schemaversion.DatasourceIndex) map[string]interface{} {
func detectMixedDatasource(queries []dashv2alpha1.DashboardPanelQueryKind) map[string]interface{} {
if len(queries) == 0 {
return nil
}
@@ -1254,7 +1246,7 @@ func convertLibraryPanelKindToV1(libPanelKind *dashv2alpha1.DashboardLibraryPane
return panel, nil
}
func convertVariablesToV1(variables []dashv2alpha1.DashboardVariableKind, dsIndex *schemaversion.DatasourceIndex) []map[string]interface{} {
func convertVariablesToV1(variables []dashv2alpha1.DashboardVariableKind) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(variables))
for _, variable := range variables {
@@ -1262,7 +1254,7 @@ func convertVariablesToV1(variables []dashv2alpha1.DashboardVariableKind, dsInde
var err error
if variable.QueryVariableKind != nil {
varMap, err = convertQueryVariableToV1(variable.QueryVariableKind, dsIndex)
varMap, err = convertQueryVariableToV1(variable.QueryVariableKind)
} else if variable.DatasourceVariableKind != nil {
varMap, err = convertDatasourceVariableToV1(variable.DatasourceVariableKind)
} else if variable.CustomVariableKind != nil {
@@ -1274,9 +1266,9 @@ func convertVariablesToV1(variables []dashv2alpha1.DashboardVariableKind, dsInde
} else if variable.TextVariableKind != nil {
varMap, err = convertTextVariableToV1(variable.TextVariableKind)
} else if variable.GroupByVariableKind != nil {
varMap, err = convertGroupByVariableToV1(variable.GroupByVariableKind, dsIndex)
varMap, err = convertGroupByVariableToV1(variable.GroupByVariableKind)
} else if variable.AdhocVariableKind != nil {
varMap, err = convertAdhocVariableToV1(variable.AdhocVariableKind, dsIndex)
varMap, err = convertAdhocVariableToV1(variable.AdhocVariableKind)
} else if variable.SwitchVariableKind != nil {
varMap, err = convertSwitchVariableToV1(variable.SwitchVariableKind)
}
@@ -1289,7 +1281,7 @@ func convertVariablesToV1(variables []dashv2alpha1.DashboardVariableKind, dsInde
return result
}
func convertQueryVariableToV1(variable *dashv2alpha1.DashboardQueryVariableKind, dsIndex *schemaversion.DatasourceIndex) (map[string]interface{}, error) {
func convertQueryVariableToV1(variable *dashv2alpha1.DashboardQueryVariableKind) (map[string]interface{}, error) {
spec := variable.Spec
varMap := map[string]interface{}{
"name": spec.Name,
@@ -1336,7 +1328,7 @@ func convertQueryVariableToV1(variable *dashv2alpha1.DashboardQueryVariableKind,
}
// Resolve datasource - use explicit datasource or resolve from query kind (datasource type)/default
datasource := getDataSourceForQuery(spec.Datasource, spec.Query.Kind, dsIndex)
datasource := getDataSourceForQuery(spec.Datasource, spec.Query.Kind)
if datasource != nil {
varMap["datasource"] = datasource
}
@@ -1486,7 +1478,7 @@ func convertTextVariableToV1(variable *dashv2alpha1.DashboardTextVariableKind) (
return varMap, nil
}
func convertGroupByVariableToV1(variable *dashv2alpha1.DashboardGroupByVariableKind, dsIndex *schemaversion.DatasourceIndex) (map[string]interface{}, error) {
func convertGroupByVariableToV1(variable *dashv2alpha1.DashboardGroupByVariableKind) (map[string]interface{}, error) {
spec := variable.Spec
varMap := map[string]interface{}{
"name": spec.Name,
@@ -1509,7 +1501,7 @@ func convertGroupByVariableToV1(variable *dashv2alpha1.DashboardGroupByVariableK
}
// Resolve datasource - GroupBy variables don't have a query kind, so use empty string (will fall back to default)
datasource := getDataSourceForQuery(spec.Datasource, "", dsIndex)
datasource := getDataSourceForQuery(spec.Datasource, "")
if datasource != nil {
varMap["datasource"] = datasource
}
@@ -1517,7 +1509,7 @@ func convertGroupByVariableToV1(variable *dashv2alpha1.DashboardGroupByVariableK
return varMap, nil
}
func convertAdhocVariableToV1(variable *dashv2alpha1.DashboardAdhocVariableKind, dsIndex *schemaversion.DatasourceIndex) (map[string]interface{}, error) {
func convertAdhocVariableToV1(variable *dashv2alpha1.DashboardAdhocVariableKind) (map[string]interface{}, error) {
spec := variable.Spec
varMap := map[string]interface{}{
"name": spec.Name,
@@ -1536,7 +1528,7 @@ func convertAdhocVariableToV1(variable *dashv2alpha1.DashboardAdhocVariableKind,
varMap["allowCustomValue"] = spec.AllowCustomValue
// Resolve datasource - Adhoc variables don't have a query kind, so use empty string (will fall back to default)
datasource := getDataSourceForQuery(spec.Datasource, "", dsIndex)
datasource := getDataSourceForQuery(spec.Datasource, "")
if datasource != nil {
varMap["datasource"] = datasource
}
@@ -1663,7 +1655,7 @@ func convertSwitchVariableToV1(variable *dashv2alpha1.DashboardSwitchVariableKin
return varMap, nil
}
func convertAnnotationsToV1(annotations []dashv2alpha1.DashboardAnnotationQueryKind, dsIndex *schemaversion.DatasourceIndex) []map[string]interface{} {
func convertAnnotationsToV1(annotations []dashv2alpha1.DashboardAnnotationQueryKind) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(annotations))
for _, annotation := range annotations {
@@ -1686,7 +1678,7 @@ func convertAnnotationsToV1(annotations []dashv2alpha1.DashboardAnnotationQueryK
if annotation.Spec.Query != nil {
queryKind = annotation.Spec.Query.Kind
}
datasource := getDataSourceForQuery(annotation.Spec.Datasource, queryKind, dsIndex)
datasource := getDataSourceForQuery(annotation.Spec.Datasource, queryKind)
if datasource != nil {
annotationMap["datasource"] = datasource
}