Compare commits
1 Commits
sriram/SQL
...
iortega/po
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8edfdff1fa |
74
apps/dashboard/pkg/apis/dashboard/v2alpha1/defaults.go
Normal file
74
apps/dashboard/pkg/apis/dashboard/v2alpha1/defaults.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package v2alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// SetDefaults_Dashboard ensures all panel queries have unique refIds
|
||||
// This is called by the Kubernetes defaulting mechanism when dashboards are returned
|
||||
func SetDefaults_Dashboard(obj *Dashboard) {
|
||||
EnsureUniqueRefIds(&obj.Spec)
|
||||
}
|
||||
|
||||
// EnsureUniqueRefIds ensures all queries within each panel have unique refIds
|
||||
// This matches the frontend behavior in PanelModel.ensureQueryIds()
|
||||
func EnsureUniqueRefIds(spec *DashboardSpec) {
|
||||
for _, element := range spec.Elements {
|
||||
if element.PanelKind != nil {
|
||||
ensureUniqueRefIdsForPanel(element.PanelKind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ensureUniqueRefIdsForPanel(panel *DashboardPanelKind) {
|
||||
queries := panel.Spec.Data.Spec.Queries
|
||||
if len(queries) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// First pass: collect existing refIds
|
||||
existingRefIds := make(map[string]bool)
|
||||
for i := range queries {
|
||||
if queries[i].Spec.RefId != "" {
|
||||
existingRefIds[queries[i].Spec.RefId] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: assign unique refIds to queries without one
|
||||
for i := range queries {
|
||||
if queries[i].Spec.RefId == "" {
|
||||
queries[i].Spec.RefId = getNextRefId(existingRefIds)
|
||||
existingRefIds[queries[i].Spec.RefId] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getNextRefId generates the next available refId (A, B, C, ..., Z, AA, AB, etc.)
|
||||
// This matches the frontend behavior in packages/grafana-data/src/query/refId.ts
|
||||
func getNextRefId(existingRefIds map[string]bool) string {
|
||||
for num := 0; ; num++ {
|
||||
refId := getRefIdFromNumber(num)
|
||||
if !existingRefIds[refId] {
|
||||
return refId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getRefIdFromNumber converts a number to a refId (0=A, 1=B, ..., 25=Z, 26=AA, 27=AB, etc.)
|
||||
func getRefIdFromNumber(num int) string {
|
||||
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
if num < len(letters) {
|
||||
return string(letters[num])
|
||||
}
|
||||
return getRefIdFromNumber(num/len(letters)-1) + string(letters[num%len(letters)])
|
||||
}
|
||||
|
||||
// RegisterCustomDefaults registers custom defaulting functions for Dashboard types.
|
||||
// This should be called from RegisterDefaults in zz_generated.defaults.go
|
||||
// However, since that file is auto-generated, we provide this as a separate registration
|
||||
func RegisterCustomDefaults(scheme *runtime.Scheme) error {
|
||||
scheme.AddTypeDefaultingFunc(&Dashboard{}, func(obj interface{}) {
|
||||
SetDefaults_Dashboard(obj.(*Dashboard))
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -99,5 +99,9 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
}
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
return RegisterDefaults(scheme)
|
||||
if err := RegisterDefaults(scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
// Register custom defaults to ensure unique refIds in panel queries
|
||||
return RegisterCustomDefaults(scheme)
|
||||
}
|
||||
|
||||
74
apps/dashboard/pkg/apis/dashboard/v2beta1/defaults.go
Normal file
74
apps/dashboard/pkg/apis/dashboard/v2beta1/defaults.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package v2beta1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// SetDefaults_Dashboard ensures all panel queries have unique refIds
|
||||
// This is called by the Kubernetes defaulting mechanism when dashboards are returned
|
||||
func SetDefaults_Dashboard(obj *Dashboard) {
|
||||
EnsureUniqueRefIds(&obj.Spec)
|
||||
}
|
||||
|
||||
// EnsureUniqueRefIds ensures all queries within each panel have unique refIds
|
||||
// This matches the frontend behavior in PanelModel.ensureQueryIds()
|
||||
func EnsureUniqueRefIds(spec *DashboardSpec) {
|
||||
for _, element := range spec.Elements {
|
||||
if element.PanelKind != nil {
|
||||
ensureUniqueRefIdsForPanel(element.PanelKind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ensureUniqueRefIdsForPanel(panel *DashboardPanelKind) {
|
||||
queries := panel.Spec.Data.Spec.Queries
|
||||
if len(queries) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// First pass: collect existing refIds
|
||||
existingRefIds := make(map[string]bool)
|
||||
for i := range queries {
|
||||
if queries[i].Spec.RefId != "" {
|
||||
existingRefIds[queries[i].Spec.RefId] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: assign unique refIds to queries without one
|
||||
for i := range queries {
|
||||
if queries[i].Spec.RefId == "" {
|
||||
queries[i].Spec.RefId = getNextRefId(existingRefIds)
|
||||
existingRefIds[queries[i].Spec.RefId] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getNextRefId generates the next available refId (A, B, C, ..., Z, AA, AB, etc.)
|
||||
// This matches the frontend behavior in packages/grafana-data/src/query/refId.ts
|
||||
func getNextRefId(existingRefIds map[string]bool) string {
|
||||
for num := 0; ; num++ {
|
||||
refId := getRefIdFromNumber(num)
|
||||
if !existingRefIds[refId] {
|
||||
return refId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getRefIdFromNumber converts a number to a refId (0=A, 1=B, ..., 25=Z, 26=AA, 27=AB, etc.)
|
||||
func getRefIdFromNumber(num int) string {
|
||||
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
if num < len(letters) {
|
||||
return string(letters[num])
|
||||
}
|
||||
return getRefIdFromNumber(num/len(letters)-1) + string(letters[num%len(letters)])
|
||||
}
|
||||
|
||||
// RegisterCustomDefaults registers custom defaulting functions for Dashboard types.
|
||||
// This should be called from RegisterDefaults in zz_generated.defaults.go
|
||||
// However, since that file is auto-generated, we provide this as a separate registration
|
||||
func RegisterCustomDefaults(scheme *runtime.Scheme) error {
|
||||
scheme.AddTypeDefaultingFunc(&Dashboard{}, func(obj interface{}) {
|
||||
SetDefaults_Dashboard(obj.(*Dashboard))
|
||||
})
|
||||
return nil
|
||||
}
|
||||
219
apps/dashboard/pkg/apis/dashboard/v2beta1/defaults_test.go
Normal file
219
apps/dashboard/pkg/apis/dashboard/v2beta1/defaults_test.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package v2beta1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetRefIdFromNumber(t *testing.T) {
|
||||
testCases := []struct {
|
||||
num int
|
||||
expected string
|
||||
}{
|
||||
{0, "A"},
|
||||
{1, "B"},
|
||||
{25, "Z"},
|
||||
{26, "AA"},
|
||||
{27, "AB"},
|
||||
{51, "AZ"},
|
||||
{52, "BA"},
|
||||
{701, "ZZ"},
|
||||
{702, "AAA"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.expected, func(t *testing.T) {
|
||||
result := getRefIdFromNumber(tc.num)
|
||||
assert.Equal(t, tc.expected, result, "getRefIdFromNumber(%d) should return %s", tc.num, tc.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNextRefId(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
existing map[string]bool
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "empty map returns A",
|
||||
existing: map[string]bool{},
|
||||
expected: "A",
|
||||
},
|
||||
{
|
||||
name: "A exists returns B",
|
||||
existing: map[string]bool{"A": true},
|
||||
expected: "B",
|
||||
},
|
||||
{
|
||||
name: "A and B exist returns C",
|
||||
existing: map[string]bool{"A": true, "B": true},
|
||||
expected: "C",
|
||||
},
|
||||
{
|
||||
name: "gap in sequence returns first available",
|
||||
existing: map[string]bool{"A": true, "C": true, "D": true},
|
||||
expected: "B",
|
||||
},
|
||||
{
|
||||
name: "A-Z exist returns AA",
|
||||
existing: func() map[string]bool {
|
||||
m := make(map[string]bool)
|
||||
for i := 0; i < 26; i++ {
|
||||
m[string(rune('A'+i))] = true
|
||||
}
|
||||
return m
|
||||
}(),
|
||||
expected: "AA",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := getNextRefId(tc.existing)
|
||||
assert.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureUniqueRefIds(t *testing.T) {
|
||||
t.Run("assigns unique refIds to queries without refIds", func(t *testing.T) {
|
||||
spec := &DashboardSpec{
|
||||
Elements: map[string]DashboardElement{
|
||||
"panel-1": {
|
||||
PanelKind: &DashboardPanelKind{
|
||||
Kind: "Panel",
|
||||
Spec: DashboardPanelSpec{
|
||||
Data: DashboardQueryGroupKind{
|
||||
Spec: DashboardQueryGroupSpec{
|
||||
Queries: []DashboardPanelQueryKind{
|
||||
{Spec: DashboardPanelQuerySpec{RefId: ""}},
|
||||
{Spec: DashboardPanelQuerySpec{RefId: ""}},
|
||||
{Spec: DashboardPanelQuerySpec{RefId: ""}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
EnsureUniqueRefIds(spec)
|
||||
|
||||
panel := spec.Elements["panel-1"].PanelKind
|
||||
require.NotNil(t, panel)
|
||||
require.Len(t, panel.Spec.Data.Spec.Queries, 3)
|
||||
assert.Equal(t, "A", panel.Spec.Data.Spec.Queries[0].Spec.RefId)
|
||||
assert.Equal(t, "B", panel.Spec.Data.Spec.Queries[1].Spec.RefId)
|
||||
assert.Equal(t, "C", panel.Spec.Data.Spec.Queries[2].Spec.RefId)
|
||||
})
|
||||
|
||||
t.Run("preserves existing refIds and fills gaps", func(t *testing.T) {
|
||||
spec := &DashboardSpec{
|
||||
Elements: map[string]DashboardElement{
|
||||
"panel-1": {
|
||||
PanelKind: &DashboardPanelKind{
|
||||
Kind: "Panel",
|
||||
Spec: DashboardPanelSpec{
|
||||
Data: DashboardQueryGroupKind{
|
||||
Spec: DashboardQueryGroupSpec{
|
||||
Queries: []DashboardPanelQueryKind{
|
||||
{Spec: DashboardPanelQuerySpec{RefId: "A"}},
|
||||
{Spec: DashboardPanelQuerySpec{RefId: ""}},
|
||||
{Spec: DashboardPanelQuerySpec{RefId: "D"}},
|
||||
{Spec: DashboardPanelQuerySpec{RefId: ""}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
EnsureUniqueRefIds(spec)
|
||||
|
||||
panel := spec.Elements["panel-1"].PanelKind
|
||||
require.NotNil(t, panel)
|
||||
require.Len(t, panel.Spec.Data.Spec.Queries, 4)
|
||||
assert.Equal(t, "A", panel.Spec.Data.Spec.Queries[0].Spec.RefId)
|
||||
assert.Equal(t, "B", panel.Spec.Data.Spec.Queries[1].Spec.RefId)
|
||||
assert.Equal(t, "D", panel.Spec.Data.Spec.Queries[2].Spec.RefId)
|
||||
assert.Equal(t, "C", panel.Spec.Data.Spec.Queries[3].Spec.RefId)
|
||||
})
|
||||
|
||||
t.Run("handles library panels (no modification)", func(t *testing.T) {
|
||||
spec := &DashboardSpec{
|
||||
Elements: map[string]DashboardElement{
|
||||
"panel-1": {
|
||||
LibraryPanelKind: &DashboardLibraryPanelKind{
|
||||
Kind: "LibraryPanel",
|
||||
Spec: DashboardLibraryPanelKindSpec{
|
||||
LibraryPanel: DashboardLibraryPanelRef{
|
||||
Uid: "lib-uid",
|
||||
Name: "lib-name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Should not panic
|
||||
EnsureUniqueRefIds(spec)
|
||||
})
|
||||
|
||||
t.Run("handles multiple panels", func(t *testing.T) {
|
||||
spec := &DashboardSpec{
|
||||
Elements: map[string]DashboardElement{
|
||||
"panel-1": {
|
||||
PanelKind: &DashboardPanelKind{
|
||||
Kind: "Panel",
|
||||
Spec: DashboardPanelSpec{
|
||||
Data: DashboardQueryGroupKind{
|
||||
Spec: DashboardQueryGroupSpec{
|
||||
Queries: []DashboardPanelQueryKind{
|
||||
{Spec: DashboardPanelQuerySpec{RefId: ""}},
|
||||
{Spec: DashboardPanelQuerySpec{RefId: ""}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"panel-2": {
|
||||
PanelKind: &DashboardPanelKind{
|
||||
Kind: "Panel",
|
||||
Spec: DashboardPanelSpec{
|
||||
Data: DashboardQueryGroupKind{
|
||||
Spec: DashboardQueryGroupSpec{
|
||||
Queries: []DashboardPanelQueryKind{
|
||||
{Spec: DashboardPanelQuerySpec{RefId: ""}},
|
||||
{Spec: DashboardPanelQuerySpec{RefId: ""}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
EnsureUniqueRefIds(spec)
|
||||
|
||||
// Each panel should have unique refIds independently
|
||||
panel1 := spec.Elements["panel-1"].PanelKind
|
||||
panel2 := spec.Elements["panel-2"].PanelKind
|
||||
require.NotNil(t, panel1)
|
||||
require.NotNil(t, panel2)
|
||||
|
||||
assert.Equal(t, "A", panel1.Spec.Data.Spec.Queries[0].Spec.RefId)
|
||||
assert.Equal(t, "B", panel1.Spec.Data.Spec.Queries[1].Spec.RefId)
|
||||
|
||||
assert.Equal(t, "A", panel2.Spec.Data.Spec.Queries[0].Spec.RefId)
|
||||
assert.Equal(t, "B", panel2.Spec.Data.Spec.Queries[1].Spec.RefId)
|
||||
})
|
||||
}
|
||||
@@ -99,5 +99,9 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
}
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
return RegisterDefaults(scheme)
|
||||
if err := RegisterDefaults(scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
// Register custom defaults to ensure unique refIds in panel queries
|
||||
return RegisterCustomDefaults(scheme)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user