Dashboard Controls: Fix schema transformation for link placement (#114630)

* fix: add missing transformation for scenes -> save model v2

* fix: link placement transformation on the backend between schemas

* fix: update the openapi spec in the tests

* tes: add tests for `transformSceneToSaveModelSchemaV2`

* tests: extend conversion_test.go to cover link placements
This commit is contained in:
Levente Balogh
2025-12-02 13:12:01 +00:00
committed by GitHub
parent 09883ab4ee
commit 0e6166a753
17 changed files with 120 additions and 2 deletions
@@ -109,8 +109,14 @@ DashboardLink: {
includeVars: bool | *false
// If true, includes current time range in the link as query params
keepTime: bool | *false
// Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.
placement?: DashboardLinkPlacement
}
// Dashboard Link placement. Defines where the link should be displayed.
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
DashboardLinkPlacement: "inControlsMenu"
DataSourceRef: {
// The plugin type-id
type?: string
@@ -113,8 +113,14 @@ DashboardLink: {
includeVars: bool | *false
// If true, includes current time range in the link as query params
keepTime: bool | *false
// Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.
placement?: DashboardLinkPlacement
}
// Dashboard Link placement. Defines where the link should be displayed.
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
DashboardLinkPlacement: "inControlsMenu"
DataSourceRef: {
// The plugin type-id
type?: string
@@ -1216,6 +1216,8 @@ type DashboardDashboardLink struct {
IncludeVars bool `json:"includeVars"`
// If true, includes current time range in the link as query params
KeepTime bool `json:"keepTime"`
// Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.
Placement *string `json:"placement,omitempty"`
}
// NewDashboardDashboardLink creates a new DashboardDashboardLink object.
@@ -1226,6 +1228,7 @@ func NewDashboardDashboardLink() *DashboardDashboardLink {
TargetBlank: false,
IncludeVars: false,
KeepTime: false,
Placement: (func(input string) *string { return &input })(DashboardDashboardLinkPlacement),
}
}
@@ -1238,6 +1241,11 @@ const (
DashboardDashboardLinkTypeDashboards DashboardDashboardLinkType = "dashboards"
)
// Dashboard Link placement. Defines where the link should be displayed.
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
// +k8s:openapi-gen=true
const DashboardDashboardLinkPlacement = "inControlsMenu"
// Time configuration
// It defines the default time config for the time picker, the refresh picker for the specific dashboard.
// +k8s:openapi-gen=true
@@ -1600,6 +1600,13 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardDashboardLink(ref common.Refere
Format: "",
},
},
"placement": {
SchemaProps: spec.SchemaProps{
Description: "Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"title", "type", "icon", "tooltip", "tags", "asDropdown", "targetBlank", "includeVars", "keepTime"},
},
@@ -70,6 +70,7 @@
"title": "Documentation",
"tooltip": "View documentation",
"type": "link",
"placement": "inControlsMenu",
"url": "https://docs.example.com/dashboard-guide"
},
{
@@ -69,6 +69,7 @@
"title": "Documentation",
"tooltip": "View documentation",
"type": "link",
"placement": "inControlsMenu",
"url": "https://docs.example.com/dashboard-guide"
},
{
@@ -132,7 +132,8 @@
"asDropdown": false,
"targetBlank": true,
"includeVars": false,
"keepTime": false
"keepTime": false,
"placement": "inControlsMenu"
},
{
"title": "Tag-based Link",
@@ -137,7 +137,8 @@
"asDropdown": false,
"targetBlank": true,
"includeVars": false,
"keepTime": false
"keepTime": false,
"placement": "inControlsMenu"
},
{
"title": "Tag-based Link",
@@ -376,6 +376,13 @@ func transformLinks(dashboard map[string]interface{}) []dashv2alpha1.DashboardDa
}
}
// Optional placement field - only set if present
if placement, exists := linkMap["placement"]; exists {
if placementStr, ok := placement.(string); ok {
dashLink.Placement = &placementStr
}
}
result = append(result, dashLink)
}
}
@@ -548,6 +548,7 @@ func convertDashboardLink_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardDashboar
out.TargetBlank = in.TargetBlank
out.IncludeVars = in.IncludeVars
out.KeepTime = in.KeepTime
out.Placement = in.Placement
}
func convertDataLink_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardDataLink, out *dashv2beta1.DashboardDataLink) {
@@ -565,6 +565,7 @@ func convertDashboardLink_V2beta1_to_V2alpha1(in *dashv2beta1.DashboardDashboard
out.TargetBlank = in.TargetBlank
out.IncludeVars = in.IncludeVars
out.KeepTime = in.KeepTime
out.Placement = in.Placement
}
func convertDataLink_V2beta1_to_V2alpha1(in *dashv2beta1.DashboardDataLink, out *dashv2alpha1.DashboardDataLink) {
@@ -971,6 +971,8 @@ export interface DashboardLink {
includeVars: boolean;
// If true, includes current time range in the link as query params
keepTime: boolean;
// Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.
placement?: "inControlsMenu";
}
export const defaultDashboardLink = (): DashboardLink => ({
@@ -983,6 +985,7 @@ export const defaultDashboardLink = (): DashboardLink => ({
targetBlank: false,
includeVars: false,
keepTime: false,
placement: DashboardLinkPlacement,
});
// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
@@ -990,6 +993,10 @@ export type DashboardLinkType = "link" | "dashboards";
export const defaultDashboardLinkType = (): DashboardLinkType => ("link");
// Dashboard Link placement. Defines where the link should be displayed.
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
export const DashboardLinkPlacement = "inControlsMenu";
// Time configuration
// It defines the default time config for the time picker, the refresh picker for the specific dashboard.
export interface TimeSettingsSpec {
@@ -1877,6 +1877,10 @@
"type": "boolean",
"default": false
},
"placement": {
"description": "Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.",
"type": "string"
},
"tags": {
"description": "List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards",
"type": "array",
@@ -411,6 +411,45 @@ describe('transformSceneToSaveModelSchemaV2', () => {
expect(result.annotations).toHaveLength(2);
});
it('should transform links with placement property', () => {
const sceneWithPlacementLink = new DashboardScene({
links: [
{
title: 'Link in Controls Menu',
url: 'http://test.com',
type: 'link',
placement: 'inControlsMenu',
asDropdown: false,
icon: '',
includeVars: false,
keepTime: false,
tags: [],
targetBlank: false,
tooltip: '',
},
{
title: 'Link without placement',
url: 'http://test2.com',
type: 'link',
asDropdown: false,
icon: '',
includeVars: false,
keepTime: false,
tags: [],
targetBlank: false,
tooltip: '',
},
],
});
const result = transformSceneToSaveModelSchemaV2(sceneWithPlacementLink);
expect(result.links).toBeDefined();
expect(result.links).toHaveLength(2);
expect(result.links![0]).toHaveProperty('placement', 'inControlsMenu');
expect(result.links![1]).not.toHaveProperty('placement');
});
it('should transform the minimum scene to save model schema v2', () => {
const minimalScene = new DashboardScene({});
@@ -97,6 +97,7 @@ export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnaps
keepTime: link.keepTime ?? defaultDashboardLink().keepTime,
includeVars: link.includeVars ?? defaultDashboardLink().includeVars,
targetBlank: link.targetBlank ?? defaultDashboardLink().targetBlank,
...(link.placement !== undefined && { placement: link.placement }),
})),
tags: sceneDash.tags ?? defaultDashboardV2Spec().tags,
// EOF dashboard settings
@@ -142,6 +142,19 @@ describe('ResponseTransformers', () => {
type: 'link',
tooltip: 'Link 1 Tooltip',
},
{
title: 'Link 2',
url: 'https://grafana.com',
asDropdown: false,
targetBlank: true,
includeVars: true,
keepTime: true,
tags: ['tag3', 'tag4'],
icon: 'external link',
type: 'link',
tooltip: 'Link 2 Tooltip',
placement: 'inControlsMenu',
},
],
annotations: {
list: [],
@@ -886,6 +899,19 @@ describe('ResponseTransformers', () => {
type: 'link',
tooltip: 'Link 1 Tooltip',
},
{
title: 'Link 2',
url: 'https://grafana.com',
asDropdown: false,
targetBlank: true,
includeVars: true,
keepTime: true,
tags: ['tag3', 'tag4'],
icon: 'external link',
type: 'link',
tooltip: 'Link 2 Tooltip',
placement: 'inControlsMenu',
},
],
annotations: handyTestingSchema.annotations,
variables: handyTestingSchema.variables,
@@ -216,6 +216,7 @@ export function ensureV2Response(
keepTime: link.keepTime ?? defaultDashboardLink().keepTime,
includeVars: link.includeVars ?? defaultDashboardLink().includeVars,
targetBlank: link.targetBlank ?? defaultDashboardLink().targetBlank,
...(link.placement !== undefined && { placement: link.placement }),
})),
annotations,
variables,