Dynamic dashboards: Persist conditional rendering (#102022)

* Dashboards: Add conditional rendering

* Updates

* Fixes

* Code improvements

* Code improvements

* limit condition choices, add delete and clean up ui

* add basic variable condition

* add conditional rendering based on time range interval

* adjust failing test

* remove deprecated pseudo locale file

* extract conditional rendering from behaviour to state property

* clean up behaviour initialisation

* clean up ts errors

* adjust data condition to account for RowItem

* persist-conditional-rendering

* fix group value name and kind type

* Fix types in base

* minor style fix

* Fix subscribes

* notify change when deleting condition

* fix hidden row item error

* Remove option to have groups in groups

* fix merge issue

* address comments

* subscribe to panel data change in data condition

* Remove loop labels

* only persist conditional rendering if root group has items

* update backend types

* Serialize variable conditional rendering operator as equals notEquals

---------

Co-authored-by: Bogdan Matei <bogdan.matei@grafana.com>
Co-authored-by: Sergej-Vlasov <sergej.s.vlasov@gmail.com>
This commit is contained in:
Oscar Kilhed
2025-03-13 11:56:20 +01:00
committed by GitHub
parent 8fd2a12670
commit d07b1851c7
15 changed files with 646 additions and 28 deletions
@@ -564,6 +564,7 @@ RowsLayoutRowKind: {
RowsLayoutRowSpec: {
title?: string
collapsed: bool
conditionalRendering?: ConditionalRenderingGroupKind
repeat?: RowRepeatOptions
layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind
}
@@ -587,6 +588,7 @@ ResponsiveGridLayoutItemKind: {
ResponsiveGridLayoutItemSpec: {
element: ElementReference
repeat?: ResponsiveGridRepeatOptions
conditionalRendering?: ConditionalRenderingGroupKind
}
TabsLayoutKind: {
@@ -921,3 +923,42 @@ AdhocVariableKind: {
kind: "AdhocVariable"
spec: AdhocVariableSpec
}
ConditionalRenderingGroupKind: {
kind: "ConditionalRenderingGroup"
spec: ConditionalRenderingGroupSpec
}
ConditionalRenderingGroupSpec: {
condition: "and" | "or"
items: [...ConditionalRenderingVariableKind | ConditionalRenderingDataKind | ConditionalRenderingTimeIntervalKind]
}
ConditionalRenderingVariableKind: {
kind: "ConditionalRenderingVariable"
spec: ConditionalRenderingVariableSpec
}
ConditionalRenderingVariableSpec: {
variable: string
operator: "equals" | "notEquals"
value: string
}
ConditionalRenderingDataKind: {
kind: "ConditionalRenderingData"
spec: ConditionalRenderingDataSpec
}
ConditionalRenderingDataSpec: {
value: bool
}
ConditionalRenderingTimeIntervalKind: {
kind: "ConditionalRenderingTimeInterval"
spec: ConditionalRenderingTimeIntervalSpec
}
ConditionalRenderingTimeIntervalSpec: {
value: string
}
@@ -833,10 +833,11 @@ func NewDashboardRowsLayoutRowKind() *DashboardRowsLayoutRowKind {
// +k8s:openapi-gen=true
type DashboardRowsLayoutRowSpec struct {
Title *string `json:"title,omitempty"`
Collapsed bool `json:"collapsed"`
Repeat *DashboardRowRepeatOptions `json:"repeat,omitempty"`
Layout DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind `json:"layout"`
Title *string `json:"title,omitempty"`
Collapsed bool `json:"collapsed"`
ConditionalRendering *DashboardConditionalRenderingGroupKind `json:"conditionalRendering,omitempty"`
Repeat *DashboardRowRepeatOptions `json:"repeat,omitempty"`
Layout DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind `json:"layout"`
}
// NewDashboardRowsLayoutRowSpec creates a new DashboardRowsLayoutRowSpec object.
@@ -846,6 +847,105 @@ func NewDashboardRowsLayoutRowSpec() *DashboardRowsLayoutRowSpec {
}
}
// +k8s:openapi-gen=true
type DashboardConditionalRenderingGroupKind struct {
Kind string `json:"kind"`
Spec DashboardConditionalRenderingGroupSpec `json:"spec"`
}
// NewDashboardConditionalRenderingGroupKind creates a new DashboardConditionalRenderingGroupKind object.
func NewDashboardConditionalRenderingGroupKind() *DashboardConditionalRenderingGroupKind {
return &DashboardConditionalRenderingGroupKind{
Kind: "ConditionalRenderingGroup",
Spec: *NewDashboardConditionalRenderingGroupSpec(),
}
}
// +k8s:openapi-gen=true
type DashboardConditionalRenderingGroupSpec struct {
Condition DashboardConditionalRenderingGroupSpecCondition `json:"condition"`
Items []DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind `json:"items"`
}
// NewDashboardConditionalRenderingGroupSpec creates a new DashboardConditionalRenderingGroupSpec object.
func NewDashboardConditionalRenderingGroupSpec() *DashboardConditionalRenderingGroupSpec {
return &DashboardConditionalRenderingGroupSpec{}
}
// +k8s:openapi-gen=true
type DashboardConditionalRenderingVariableKind struct {
Kind string `json:"kind"`
Spec DashboardConditionalRenderingVariableSpec `json:"spec"`
}
// NewDashboardConditionalRenderingVariableKind creates a new DashboardConditionalRenderingVariableKind object.
func NewDashboardConditionalRenderingVariableKind() *DashboardConditionalRenderingVariableKind {
return &DashboardConditionalRenderingVariableKind{
Kind: "ConditionalRenderingVariable",
Spec: *NewDashboardConditionalRenderingVariableSpec(),
}
}
// +k8s:openapi-gen=true
type DashboardConditionalRenderingVariableSpec struct {
Variable string `json:"variable"`
Operator DashboardConditionalRenderingVariableSpecOperator `json:"operator"`
Value string `json:"value"`
}
// NewDashboardConditionalRenderingVariableSpec creates a new DashboardConditionalRenderingVariableSpec object.
func NewDashboardConditionalRenderingVariableSpec() *DashboardConditionalRenderingVariableSpec {
return &DashboardConditionalRenderingVariableSpec{}
}
// +k8s:openapi-gen=true
type DashboardConditionalRenderingDataKind struct {
Kind string `json:"kind"`
Spec DashboardConditionalRenderingDataSpec `json:"spec"`
}
// NewDashboardConditionalRenderingDataKind creates a new DashboardConditionalRenderingDataKind object.
func NewDashboardConditionalRenderingDataKind() *DashboardConditionalRenderingDataKind {
return &DashboardConditionalRenderingDataKind{
Kind: "ConditionalRenderingData",
Spec: *NewDashboardConditionalRenderingDataSpec(),
}
}
// +k8s:openapi-gen=true
type DashboardConditionalRenderingDataSpec struct {
Value bool `json:"value"`
}
// NewDashboardConditionalRenderingDataSpec creates a new DashboardConditionalRenderingDataSpec object.
func NewDashboardConditionalRenderingDataSpec() *DashboardConditionalRenderingDataSpec {
return &DashboardConditionalRenderingDataSpec{}
}
// +k8s:openapi-gen=true
type DashboardConditionalRenderingTimeIntervalKind struct {
Kind string `json:"kind"`
Spec DashboardConditionalRenderingTimeIntervalSpec `json:"spec"`
}
// NewDashboardConditionalRenderingTimeIntervalKind creates a new DashboardConditionalRenderingTimeIntervalKind object.
func NewDashboardConditionalRenderingTimeIntervalKind() *DashboardConditionalRenderingTimeIntervalKind {
return &DashboardConditionalRenderingTimeIntervalKind{
Kind: "ConditionalRenderingTimeInterval",
Spec: *NewDashboardConditionalRenderingTimeIntervalSpec(),
}
}
// +k8s:openapi-gen=true
type DashboardConditionalRenderingTimeIntervalSpec struct {
Value string `json:"value"`
}
// NewDashboardConditionalRenderingTimeIntervalSpec creates a new DashboardConditionalRenderingTimeIntervalSpec object.
func NewDashboardConditionalRenderingTimeIntervalSpec() *DashboardConditionalRenderingTimeIntervalSpec {
return &DashboardConditionalRenderingTimeIntervalSpec{}
}
// +k8s:openapi-gen=true
type DashboardResponsiveGridLayoutKind struct {
Kind string `json:"kind"`
@@ -888,8 +988,9 @@ func NewDashboardResponsiveGridLayoutItemKind() *DashboardResponsiveGridLayoutIt
// +k8s:openapi-gen=true
type DashboardResponsiveGridLayoutItemSpec struct {
Element DashboardElementReference `json:"element"`
Repeat *DashboardResponsiveGridRepeatOptions `json:"repeat,omitempty"`
Element DashboardElementReference `json:"element"`
Repeat *DashboardResponsiveGridRepeatOptions `json:"repeat,omitempty"`
ConditionalRendering *DashboardConditionalRenderingGroupKind `json:"conditionalRendering,omitempty"`
}
// NewDashboardResponsiveGridLayoutItemSpec creates a new DashboardResponsiveGridLayoutItemSpec object.
@@ -1675,6 +1776,22 @@ const (
DashboardRepeatOptionsDirectionV DashboardRepeatOptionsDirection = "v"
)
// +k8s:openapi-gen=true
type DashboardConditionalRenderingGroupSpecCondition string
const (
DashboardConditionalRenderingGroupSpecConditionAnd DashboardConditionalRenderingGroupSpecCondition = "and"
DashboardConditionalRenderingGroupSpecConditionOr DashboardConditionalRenderingGroupSpecCondition = "or"
)
// +k8s:openapi-gen=true
type DashboardConditionalRenderingVariableSpecOperator string
const (
DashboardConditionalRenderingVariableSpecOperatorEquals DashboardConditionalRenderingVariableSpecOperator = "equals"
DashboardConditionalRenderingVariableSpecOperatorNotEquals DashboardConditionalRenderingVariableSpecOperator = "notEquals"
)
// +k8s:openapi-gen=true
type DashboardTimeSettingsSpecWeekStart string
@@ -1968,6 +2085,80 @@ func (resource *DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKin
return fmt.Errorf("could not unmarshal resource with `kind = %v`", discriminator)
}
// +k8s:openapi-gen=true
type DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind struct {
ConditionalRenderingVariableKind *DashboardConditionalRenderingVariableKind `json:"ConditionalRenderingVariableKind,omitempty"`
ConditionalRenderingDataKind *DashboardConditionalRenderingDataKind `json:"ConditionalRenderingDataKind,omitempty"`
ConditionalRenderingTimeIntervalKind *DashboardConditionalRenderingTimeIntervalKind `json:"ConditionalRenderingTimeIntervalKind,omitempty"`
}
// NewDashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind creates a new DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind object.
func NewDashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind() *DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind {
return &DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind{}
}
// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind` as JSON.
func (resource DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind) MarshalJSON() ([]byte, error) {
if resource.ConditionalRenderingVariableKind != nil {
return json.Marshal(resource.ConditionalRenderingVariableKind)
}
if resource.ConditionalRenderingDataKind != nil {
return json.Marshal(resource.ConditionalRenderingDataKind)
}
if resource.ConditionalRenderingTimeIntervalKind != nil {
return json.Marshal(resource.ConditionalRenderingTimeIntervalKind)
}
return nil, fmt.Errorf("no value for disjunction of refs")
}
// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind` from JSON.
func (resource *DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind) UnmarshalJSON(raw []byte) error {
if raw == nil {
return nil
}
// FIXME: this is wasteful, we need to find a more efficient way to unmarshal this.
parsedAsMap := make(map[string]interface{})
if err := json.Unmarshal(raw, &parsedAsMap); err != nil {
return err
}
discriminator, found := parsedAsMap["kind"]
if !found {
return errors.New("discriminator field 'kind' not found in payload")
}
switch discriminator {
case "ConditionalRenderingData":
var dashboardConditionalRenderingDataKind DashboardConditionalRenderingDataKind
if err := json.Unmarshal(raw, &dashboardConditionalRenderingDataKind); err != nil {
return err
}
resource.ConditionalRenderingDataKind = &dashboardConditionalRenderingDataKind
return nil
case "ConditionalRenderingTimeInterval":
var dashboardConditionalRenderingTimeIntervalKind DashboardConditionalRenderingTimeIntervalKind
if err := json.Unmarshal(raw, &dashboardConditionalRenderingTimeIntervalKind); err != nil {
return err
}
resource.ConditionalRenderingTimeIntervalKind = &dashboardConditionalRenderingTimeIntervalKind
return nil
case "ConditionalRenderingVariable":
var dashboardConditionalRenderingVariableKind DashboardConditionalRenderingVariableKind
if err := json.Unmarshal(raw, &dashboardConditionalRenderingVariableKind); err != nil {
return err
}
resource.ConditionalRenderingVariableKind = &dashboardConditionalRenderingVariableKind
return nil
}
return fmt.Errorf("could not unmarshal resource with `kind = %v`", discriminator)
}
// +k8s:openapi-gen=true
type DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind struct {
GridLayoutKind *DashboardGridLayoutKind `json:"GridLayoutKind,omitempty"`