diff --git a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue index ecdef0767f6..15568f28e1b 100644 --- a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue @@ -600,8 +600,9 @@ TabsLayoutTabKind: { } TabsLayoutTabSpec: { - title?: string - layout: GridLayoutKind | RowsLayoutKind | AutoGridLayoutKind | TabsLayoutKind + title?: string + layout: GridLayoutKind | RowsLayoutKind | AutoGridLayoutKind | TabsLayoutKind + conditionalRendering?: ConditionalRenderingGroupKind } PanelSpec: { @@ -924,8 +925,9 @@ ConditionalRenderingGroupKind: { } ConditionalRenderingGroupSpec: { + visibility: "show" | "hide" condition: "and" | "or" - items: [...ConditionalRenderingVariableKind | ConditionalRenderingDataKind | ConditionalRenderingTimeIntervalKind] + items: [...ConditionalRenderingVariableKind | ConditionalRenderingDataKind | ConditionalRenderingTimeRangeSizeKind] } ConditionalRenderingVariableKind: { @@ -948,11 +950,11 @@ ConditionalRenderingDataSpec: { value: bool } -ConditionalRenderingTimeIntervalKind: { - kind: "ConditionalRenderingTimeInterval" - spec: ConditionalRenderingTimeIntervalSpec +ConditionalRenderingTimeRangeSizeKind: { + kind: "ConditionalRenderingTimeRangeSize" + spec: ConditionalRenderingTimeRangeSizeSpec } -ConditionalRenderingTimeIntervalSpec: { +ConditionalRenderingTimeRangeSizeSpec: { value: string } diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go index 908822146a2..83f1c5ae38a 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go @@ -868,8 +868,9 @@ func NewDashboardConditionalRenderingGroupKind() *DashboardConditionalRenderingG // +k8s:openapi-gen=true type DashboardConditionalRenderingGroupSpec struct { - Condition DashboardConditionalRenderingGroupSpecCondition `json:"condition"` - Items []DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind `json:"items"` + Visibility DashboardConditionalRenderingGroupSpecVisibility `json:"visibility"` + Condition DashboardConditionalRenderingGroupSpecCondition `json:"condition"` + Items []DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind `json:"items"` } // NewDashboardConditionalRenderingGroupSpec creates a new DashboardConditionalRenderingGroupSpec object. @@ -928,27 +929,27 @@ func NewDashboardConditionalRenderingDataSpec() *DashboardConditionalRenderingDa } // +k8s:openapi-gen=true -type DashboardConditionalRenderingTimeIntervalKind struct { - Kind string `json:"kind"` - Spec DashboardConditionalRenderingTimeIntervalSpec `json:"spec"` +type DashboardConditionalRenderingTimeRangeSizeKind struct { + Kind string `json:"kind"` + Spec DashboardConditionalRenderingTimeRangeSizeSpec `json:"spec"` } -// NewDashboardConditionalRenderingTimeIntervalKind creates a new DashboardConditionalRenderingTimeIntervalKind object. -func NewDashboardConditionalRenderingTimeIntervalKind() *DashboardConditionalRenderingTimeIntervalKind { - return &DashboardConditionalRenderingTimeIntervalKind{ - Kind: "ConditionalRenderingTimeInterval", - Spec: *NewDashboardConditionalRenderingTimeIntervalSpec(), +// NewDashboardConditionalRenderingTimeRangeSizeKind creates a new DashboardConditionalRenderingTimeRangeSizeKind object. +func NewDashboardConditionalRenderingTimeRangeSizeKind() *DashboardConditionalRenderingTimeRangeSizeKind { + return &DashboardConditionalRenderingTimeRangeSizeKind{ + Kind: "ConditionalRenderingTimeRangeSize", + Spec: *NewDashboardConditionalRenderingTimeRangeSizeSpec(), } } // +k8s:openapi-gen=true -type DashboardConditionalRenderingTimeIntervalSpec struct { +type DashboardConditionalRenderingTimeRangeSizeSpec struct { Value string `json:"value"` } -// NewDashboardConditionalRenderingTimeIntervalSpec creates a new DashboardConditionalRenderingTimeIntervalSpec object. -func NewDashboardConditionalRenderingTimeIntervalSpec() *DashboardConditionalRenderingTimeIntervalSpec { - return &DashboardConditionalRenderingTimeIntervalSpec{} +// NewDashboardConditionalRenderingTimeRangeSizeSpec creates a new DashboardConditionalRenderingTimeRangeSizeSpec object. +func NewDashboardConditionalRenderingTimeRangeSizeSpec() *DashboardConditionalRenderingTimeRangeSizeSpec { + return &DashboardConditionalRenderingTimeRangeSizeSpec{} } // +k8s:openapi-gen=true @@ -1063,8 +1064,9 @@ func NewDashboardTabsLayoutTabKind() *DashboardTabsLayoutTabKind { // +k8s:openapi-gen=true type DashboardTabsLayoutTabSpec struct { - Title *string `json:"title,omitempty"` - Layout DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind `json:"layout"` + Title *string `json:"title,omitempty"` + Layout DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind `json:"layout"` + ConditionalRendering *DashboardConditionalRenderingGroupKind `json:"conditionalRendering,omitempty"` } // NewDashboardTabsLayoutTabSpec creates a new DashboardTabsLayoutTabSpec object. @@ -1788,6 +1790,14 @@ const ( DashboardRepeatOptionsDirectionV DashboardRepeatOptionsDirection = "v" ) +// +k8s:openapi-gen=true +type DashboardConditionalRenderingGroupSpecVisibility string + +const ( + DashboardConditionalRenderingGroupSpecVisibilityShow DashboardConditionalRenderingGroupSpecVisibility = "show" + DashboardConditionalRenderingGroupSpecVisibilityHide DashboardConditionalRenderingGroupSpecVisibility = "hide" +) + // +k8s:openapi-gen=true type DashboardConditionalRenderingGroupSpecCondition string @@ -2126,33 +2136,33 @@ func (resource *DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRow } // +k8s:openapi-gen=true -type DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind struct { - ConditionalRenderingVariableKind *DashboardConditionalRenderingVariableKind `json:"ConditionalRenderingVariableKind,omitempty"` - ConditionalRenderingDataKind *DashboardConditionalRenderingDataKind `json:"ConditionalRenderingDataKind,omitempty"` - ConditionalRenderingTimeIntervalKind *DashboardConditionalRenderingTimeIntervalKind `json:"ConditionalRenderingTimeIntervalKind,omitempty"` +type DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind struct { + ConditionalRenderingVariableKind *DashboardConditionalRenderingVariableKind `json:"ConditionalRenderingVariableKind,omitempty"` + ConditionalRenderingDataKind *DashboardConditionalRenderingDataKind `json:"ConditionalRenderingDataKind,omitempty"` + ConditionalRenderingTimeRangeSizeKind *DashboardConditionalRenderingTimeRangeSizeKind `json:"ConditionalRenderingTimeRangeSizeKind,omitempty"` } -// NewDashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind creates a new DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind object. -func NewDashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind() *DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind { - return &DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind{} +// NewDashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind creates a new DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind object. +func NewDashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind() *DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind { + return &DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind{} } -// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind` as JSON. -func (resource DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind) MarshalJSON() ([]byte, error) { +// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind` as JSON. +func (resource DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind) 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) + if resource.ConditionalRenderingTimeRangeSizeKind != nil { + return json.Marshal(resource.ConditionalRenderingTimeRangeSizeKind) } return []byte("null"), nil } -// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind` from JSON. -func (resource *DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind) UnmarshalJSON(raw []byte) error { +// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind` from JSON. +func (resource *DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind) UnmarshalJSON(raw []byte) error { if raw == nil { return nil } @@ -2177,13 +2187,13 @@ func (resource *DashboardConditionalRenderingVariableKindOrConditionalRenderingD resource.ConditionalRenderingDataKind = &dashboardConditionalRenderingDataKind return nil - case "ConditionalRenderingTimeInterval": - var dashboardConditionalRenderingTimeIntervalKind DashboardConditionalRenderingTimeIntervalKind - if err := json.Unmarshal(raw, &dashboardConditionalRenderingTimeIntervalKind); err != nil { + case "ConditionalRenderingTimeRangeSize": + var dashboardConditionalRenderingTimeRangeSizeKind DashboardConditionalRenderingTimeRangeSizeKind + if err := json.Unmarshal(raw, &dashboardConditionalRenderingTimeRangeSizeKind); err != nil { return err } - resource.ConditionalRenderingTimeIntervalKind = &dashboardConditionalRenderingTimeIntervalKind + resource.ConditionalRenderingTimeRangeSizeKind = &dashboardConditionalRenderingTimeRangeSizeKind return nil case "ConditionalRenderingVariable": var dashboardConditionalRenderingVariableKind DashboardConditionalRenderingVariableKind diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go index c4c4b4ad5ad..56b3b2d3d84 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go @@ -33,10 +33,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingDataSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingDataSpec(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingGroupKind": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingGroupKind(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingGroupSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingGroupSpec(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeIntervalKind": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingTimeIntervalKind(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeIntervalSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingTimeIntervalSpec(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeRangeSizeKind": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingTimeRangeSizeKind(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeRangeSizeSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingTimeRangeSizeSpec(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingVariableKind": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingVariableKind(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingVariableSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingVariableSpec(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConstantVariableKind": schema_pkg_apis_dashboard_v2alpha1_DashboardConstantVariableKind(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConstantVariableSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardConstantVariableSpec(ref), @@ -904,6 +904,13 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingGroupSpec(r SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ + "visibility": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, "condition": { SchemaProps: spec.SchemaProps{ Default: "", @@ -917,22 +924,22 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingGroupSpec(r Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind"), }, }, }, }, }, }, - Required: []string{"condition", "items"}, + Required: []string{"visibility", "condition", "items"}, }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind"}, } } -func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingTimeIntervalKind(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingTimeRangeSizeKind(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -948,7 +955,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingTimeInterva "spec": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeIntervalSpec"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeRangeSizeSpec"), }, }, }, @@ -956,11 +963,11 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingTimeInterva }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeIntervalSpec"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeRangeSizeSpec"}, } } -func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingTimeIntervalSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingTimeRangeSizeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -1008,7 +1015,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingVariableKin } } -func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -1024,16 +1031,16 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingVariableKin Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingDataKind"), }, }, - "ConditionalRenderingTimeIntervalKind": { + "ConditionalRenderingTimeRangeSizeKind": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeIntervalKind"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeRangeSizeKind"), }, }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingDataKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeIntervalKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingVariableKind"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingDataKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingTimeRangeSizeKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingVariableKind"}, } } @@ -4000,12 +4007,17 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardTabsLayoutTabSpec(ref common.Re Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind"), }, }, + "conditionalRendering": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingGroupKind"), + }, + }, }, Required: []string{"layout"}, }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingGroupKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind"}, } } diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list index d4442d6288a..17477f9db13 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list @@ -32,9 +32,9 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/ API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardTimeSettingsSpec,QuickRanges API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardV2alpha1FieldConfigSourceOverrides,Properties API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,LibraryPanelStatus,Warnings -API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind,ConditionalRenderingDataKind -API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind,ConditionalRenderingTimeIntervalKind -API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeIntervalKind,ConditionalRenderingVariableKind +API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind,ConditionalRenderingDataKind +API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind,ConditionalRenderingTimeRangeSizeKind +API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind,ConditionalRenderingVariableKind API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutItemKindOrGridLayoutRowKind,GridLayoutItemKind API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutItemKindOrGridLayoutRowKind,GridLayoutRowKind API rule violation: names_match,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind,AutoGridLayoutKind diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index b3c735694b4..946a744c66f 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -515,7 +515,7 @@ GridLayoutItemKind: { GridLayoutRowKind: { kind: "GridLayoutRow" - spec: GridLayoutRowSpec + spec: GridLayoutRowSpec } GridLayoutRowSpec: { @@ -602,6 +602,7 @@ TabsLayoutTabKind: { TabsLayoutTabSpec: { title?: string layout: GridLayoutKind | RowsLayoutKind | AutoGridLayoutKind | TabsLayoutKind + conditionalRendering?: ConditionalRenderingGroupKind } PanelSpec: { @@ -928,8 +929,9 @@ ConditionalRenderingGroupKind: { } ConditionalRenderingGroupSpec: { + visibility: "show" | "hide" condition: "and" | "or" - items: [...ConditionalRenderingVariableKind | ConditionalRenderingDataKind | ConditionalRenderingTimeIntervalKind] + items: [...ConditionalRenderingVariableKind | ConditionalRenderingDataKind | ConditionalRenderingTimeRangeSizeKind] } ConditionalRenderingVariableKind: { @@ -952,11 +954,11 @@ ConditionalRenderingDataSpec: { value: bool } -ConditionalRenderingTimeIntervalKind: { - kind: "ConditionalRenderingTimeInterval" - spec: ConditionalRenderingTimeIntervalSpec -} +ConditionalRenderingTimeRangeSizeKind: { + kind: "ConditionalRenderingTimeRangeSize" + spec: ConditionalRenderingTimeRangeSizeSpec +} -ConditionalRenderingTimeIntervalSpec: { +ConditionalRenderingTimeRangeSizeSpec: { value: string } \ No newline at end of file diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts index 4b5059f8dc7..6e375d41a9f 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts @@ -756,11 +756,13 @@ export const defaultConditionalRenderingGroupKind = (): ConditionalRenderingGrou }); export interface ConditionalRenderingGroupSpec { + visibility: "show" | "hide"; condition: "and" | "or"; - items: (ConditionalRenderingVariableKind | ConditionalRenderingDataKind | ConditionalRenderingTimeIntervalKind)[]; + items: (ConditionalRenderingVariableKind | ConditionalRenderingDataKind | ConditionalRenderingTimeRangeSizeKind)[]; } export const defaultConditionalRenderingGroupSpec = (): ConditionalRenderingGroupSpec => ({ + visibility: "show", condition: "and", items: [], }); @@ -805,21 +807,21 @@ export const defaultConditionalRenderingDataSpec = (): ConditionalRenderingDataS value: false, }); -export interface ConditionalRenderingTimeIntervalKind { - kind: "ConditionalRenderingTimeInterval"; - spec: ConditionalRenderingTimeIntervalSpec; +export interface ConditionalRenderingTimeRangeSizeKind { + kind: "ConditionalRenderingTimeRangeSize"; + spec: ConditionalRenderingTimeRangeSizeSpec; } -export const defaultConditionalRenderingTimeIntervalKind = (): ConditionalRenderingTimeIntervalKind => ({ - kind: "ConditionalRenderingTimeInterval", - spec: defaultConditionalRenderingTimeIntervalSpec(), +export const defaultConditionalRenderingTimeRangeSizeKind = (): ConditionalRenderingTimeRangeSizeKind => ({ + kind: "ConditionalRenderingTimeRangeSize", + spec: defaultConditionalRenderingTimeRangeSizeSpec(), }); -export interface ConditionalRenderingTimeIntervalSpec { +export interface ConditionalRenderingTimeRangeSizeSpec { value: string; } -export const defaultConditionalRenderingTimeIntervalSpec = (): ConditionalRenderingTimeIntervalSpec => ({ +export const defaultConditionalRenderingTimeRangeSizeSpec = (): ConditionalRenderingTimeRangeSizeSpec => ({ value: "", }); @@ -912,6 +914,7 @@ export const defaultTabsLayoutTabKind = (): TabsLayoutTabKind => ({ export interface TabsLayoutTabSpec { title?: string; layout: GridLayoutKind | RowsLayoutKind | AutoGridLayoutKind | TabsLayoutKind; + conditionalRendering?: ConditionalRenderingGroupKind; } export const defaultTabsLayoutTabSpec = (): TabsLayoutTabSpec => ({ diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts index 42caac5d7a1..d59b35c3dbe 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts @@ -711,11 +711,13 @@ export const defaultConditionalRenderingGroupKind = (): ConditionalRenderingGrou }); export interface ConditionalRenderingGroupSpec { + visibility: "show" | "hide"; condition: "and" | "or"; - items: (ConditionalRenderingVariableKind | ConditionalRenderingDataKind | ConditionalRenderingTimeIntervalKind)[]; + items: (ConditionalRenderingVariableKind | ConditionalRenderingDataKind | ConditionalRenderingTimeRangeSizeKind)[]; } export const defaultConditionalRenderingGroupSpec = (): ConditionalRenderingGroupSpec => ({ + visibility: "show", condition: "and", items: [], }); @@ -760,21 +762,21 @@ export const defaultConditionalRenderingDataSpec = (): ConditionalRenderingDataS value: false, }); -export interface ConditionalRenderingTimeIntervalKind { - kind: "ConditionalRenderingTimeInterval"; - spec: ConditionalRenderingTimeIntervalSpec; +export interface ConditionalRenderingTimeRangeSizeKind { + kind: "ConditionalRenderingTimeRangeSize"; + spec: ConditionalRenderingTimeRangeSizeSpec; } -export const defaultConditionalRenderingTimeIntervalKind = (): ConditionalRenderingTimeIntervalKind => ({ - kind: "ConditionalRenderingTimeInterval", - spec: defaultConditionalRenderingTimeIntervalSpec(), +export const defaultConditionalRenderingTimeRangeSizeKind = (): ConditionalRenderingTimeRangeSizeKind => ({ + kind: "ConditionalRenderingTimeRangeSize", + spec: defaultConditionalRenderingTimeRangeSizeSpec(), }); -export interface ConditionalRenderingTimeIntervalSpec { +export interface ConditionalRenderingTimeRangeSizeSpec { value: string; } -export const defaultConditionalRenderingTimeIntervalSpec = (): ConditionalRenderingTimeIntervalSpec => ({ +export const defaultConditionalRenderingTimeRangeSizeSpec = (): ConditionalRenderingTimeRangeSizeSpec => ({ value: "", }); @@ -867,6 +869,7 @@ export const defaultTabsLayoutTabKind = (): TabsLayoutTabKind => ({ export interface TabsLayoutTabSpec { title?: string; layout: GridLayoutKind | RowsLayoutKind | AutoGridLayoutKind | TabsLayoutKind; + conditionalRendering?: ConditionalRenderingGroupKind; } export const defaultTabsLayoutTabSpec = (): TabsLayoutTabSpec => ({ diff --git a/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts b/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts index 74d196540cc..fc234397333 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts @@ -102,11 +102,7 @@ export function getDashboardGridStyles(theme: GrafanaTheme2) { }, '.dashboard-visible-hidden-element': { - opacity: 0.6, - - '&:hover': { - opacity: 1, - }, + position: 'relative', }, // Universal style for marking drop targets when dragging between layouts diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionHeader.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionHeader.tsx deleted file mode 100644 index 0112e96334e..00000000000 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionHeader.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { IconButton, Stack, Text } from '@grafana/ui'; -import { t } from 'app/core/internationalization'; - -type Props = { - title: string; - onDelete: () => void; -}; - -export const ConditionHeader = ({ title, onDelete }: Props) => { - return ( - - {title} - onDelete()} - /> - - ); -}; diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRendering.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRendering.tsx index 8b733f815d5..5203dda1d19 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRendering.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRendering.tsx @@ -1,7 +1,13 @@ -import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes'; +import { SceneComponentProps, sceneGraph, SceneObject, SceneObjectBase, SceneObjectState } from '@grafana/scenes'; import { ConditionalRenderingGroupKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; +import { t } from 'app/core/internationalization'; +import { ConditionalRenderingChangedEvent } from '../edit-pane/shared'; + +import { ConditionalRenderingBase } from './ConditionalRenderingBase'; import { ConditionalRenderingGroup } from './ConditionalRenderingGroup'; +import { ItemsWithConditionalRendering } from './types'; +import { getItemType } from './utils'; export interface ConditionalRenderingState extends SceneObjectState { rootGroup: ConditionalRenderingGroup; @@ -10,6 +16,14 @@ export interface ConditionalRenderingState extends SceneObjectState { export class ConditionalRendering extends SceneObjectBase { public static Component = ConditionalRenderingRenderer; + public get info(): string { + return t( + 'dashboard.conditional-rendering.root.info', + 'Set rules to control {{type}} visibility by matching any or all rules.', + { type: this.getItemType() } + ); + } + public constructor(state: ConditionalRenderingState) { super(state); @@ -18,7 +32,7 @@ export class ConditionalRendering extends SceneObjectBase { if (!child.isActive) { this._subs.add(child.activate()); @@ -32,19 +46,32 @@ export class ConditionalRendering extends SceneObjectBase(item: T) { + sceneGraph.getAncestor(item, ConditionalRenderingGroup).removeItem(item.state.key!); } public serialize(): ConditionalRenderingGroupKind { return this.state.rootGroup.serialize(); } + + public getItem(): SceneObject { + return this.parent!; + } + + public getItemType(): ItemsWithConditionalRendering { + return getItemType(this.getItem()); + } + + public static createEmpty(): ConditionalRendering { + return new ConditionalRendering({ rootGroup: ConditionalRenderingGroup.createEmpty() }); + } } function ConditionalRenderingRenderer({ model }: SceneComponentProps) { const { rootGroup } = model.useState(); - return ; + return rootGroup.render(false); } diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingBase.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingBase.tsx index 265372e54ec..f71e4be541b 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingBase.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingBase.tsx @@ -1,21 +1,19 @@ -import { ReactNode } from 'react'; +import { ReactElement } from 'react'; -import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectState } from '@grafana/scenes'; +import { SceneComponentProps, sceneGraph, SceneObject, SceneObjectBase, SceneObjectState } from '@grafana/scenes'; +import { Alert, Icon, IconButton, Stack, Text, Tooltip } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; import { ConditionalRendering } from './ConditionalRendering'; -import { ConditionalRenderingGroup } from './ConditionalRenderingGroup'; -import { ConditionalRenderingKindTypes } from './serializers'; -import { ConditionValues } from './shared'; +import { ConditionalRenderingKindTypes, ConditionValues, ItemsWithConditionalRendering } from './types'; export interface ConditionalRenderingBaseState extends SceneObjectState { value: V; } export abstract class ConditionalRenderingBase< - S extends ConditionalRenderingBaseState, + S extends ConditionalRenderingBaseState = ConditionalRenderingBaseState, > extends SceneObjectBase { - public static Component = ConditionalRenderingBaseRenderer; - public constructor(state: S) { super(state); @@ -25,7 +23,7 @@ export abstract class ConditionalRenderingBase< private _baseActivationHandler() { // Similarly to the ConditionalRendering activation handler, // this ensures that all children are activated when conditional rendering is activated - // We need this in order to allow children to subscribe to variable changes etc. + // We need this to allow children to subscribe to variable changes, etc. this.forEachChild((child) => { if (!child.isActive) { this._subs.add(child.activate()); @@ -33,32 +31,93 @@ export abstract class ConditionalRenderingBase< }); } + public readonly supportedItemTypes: ItemsWithConditionalRendering[] | undefined = undefined; + public abstract readonly title: string; + public abstract get info(): string | undefined; + public abstract serialize(): ConditionalRenderingKindTypes; public abstract evaluate(): boolean; - public abstract render(): ReactNode; - - public abstract onDelete(): void; - - public getConditionalLogicRoot(): ConditionalRendering { - return sceneGraph.getAncestor(this, ConditionalRendering); + public onDelete() { + this._getConditionalLogicRoot().deleteItem(this); } - public getRootGroup(): ConditionalRenderingGroup { - return this.getConditionalLogicRoot().state.rootGroup; + public render(withWrapper = true): ReactElement { + return ; + } + + public getItem(): SceneObject { + return this._getConditionalLogicRoot().getItem(); + } + + public getItemType(): ItemsWithConditionalRendering { + return this._getConditionalLogicRoot().getItemType(); + } + + public isItemSupported(): boolean { + if (!this.supportedItemTypes) { + return true; + } + + return this.supportedItemTypes.includes(this.getItemType()); + } + + public notifyChange() { + this._getConditionalLogicRoot().notifyChange(); } public setStateAndNotify(state: Partial) { this.setState(state); - this.getConditionalLogicRoot().notifyChange(); + this.notifyChange(); + } + + private _getConditionalLogicRoot(): ConditionalRendering { + return sceneGraph.getAncestor(this, ConditionalRendering); } } -function ConditionalRenderingBaseRenderer({ +function ConditionalRenderingBaseRenderer({ model, -}: SceneComponentProps>) { - return model.render(); + withWrapper, +}: SceneComponentProps & { withWrapper: boolean }) { + const comp = ; + + if (!withWrapper) { + return comp; + } + + return ( + + + {model.title} + {model.info && ( + + + + )} + + + + + {comp} + {!model.isItemSupported() && ( + + + This condition is not supported by the element, hence it will be ignored. + + + )} + + + model.onDelete()} + /> + + + ); } diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingData.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingData.tsx index 782c63b6101..b31ab780401 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingData.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingData.tsx @@ -1,25 +1,39 @@ -import { ReactNode, useMemo } from 'react'; +import { useMemo } from 'react'; -import { PanelData, SelectableValue } from '@grafana/data'; -import { SceneComponentProps, SceneDataProvider, sceneGraph } from '@grafana/scenes'; +import { PanelData } from '@grafana/data'; +import { SceneComponentProps, sceneGraph } from '@grafana/scenes'; import { ConditionalRenderingDataKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; -import { RadioButtonGroup, Stack } from '@grafana/ui'; +import { Combobox, ComboboxOption } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { AutoGridItem } from '../scene/layout-responsive-grid/ResponsiveGridItem'; -import { RowItem } from '../scene/layout-rows/RowItem'; -import { ConditionHeader } from './ConditionHeader'; import { ConditionalRenderingBase, ConditionalRenderingBaseState } from './ConditionalRenderingBase'; -import { handleDeleteNonGroupCondition } from './shared'; - -export type DataConditionValue = boolean; +import { ConditionalRenderingSerializerRegistryItem, DataConditionValue, ItemsWithConditionalRendering } from './types'; type ConditionalRenderingDataState = ConditionalRenderingBaseState; export class ConditionalRenderingData extends ConditionalRenderingBase { + public static Component = ConditionalRenderingDataRenderer; + + public static serializer: ConditionalRenderingSerializerRegistryItem = { + id: 'ConditionalRenderingData', + name: 'Data', + deserialize: this.deserialize, + }; + + public readonly supportedItemTypes: ItemsWithConditionalRendering[] = ['panel']; + public get title(): string { - return t('dashboard.conditional-rendering.data.label', 'Data'); + return t('dashboard.conditional-rendering.conditions.data.label', 'Query result'); + } + + public get info(): string { + return t( + 'dashboard.conditional-rendering.conditions.data.info', + 'Show or hide the {{type}} based on query results.', + { type: this.getItemType() } + ); } public constructor(state: ConditionalRenderingDataState) { @@ -29,122 +43,90 @@ export class ConditionalRenderingData extends ConditionalRenderingBase { - this._subs.add( - d.subscribeToState(() => { - this.getConditionalLogicRoot().notifyChange(); - }) - ); - }); + const item = this.getItem(); + + if (item instanceof AutoGridItem) { + const dataProvider = sceneGraph.getData(item.state.body); + + if (!dataProvider) { + return; + } + + this._subs.add(dataProvider.subscribeToState(() => this.notifyChange())); + } } public evaluate(): boolean { - const { value } = this.state; - - // enable/disable condition - if (!value) { + if (!this.isItemSupported()) { return true; } - let data: PanelData[] = []; + const hasData = this._hasData(); + return (this.state.value && hasData) || (!this.state.value && !hasData); + } - // get ResponsiveGridItem or RowItem - const item = this.getConditionalLogicRoot().parent; + public serialize(): ConditionalRenderingDataKind { + return { kind: 'ConditionalRenderingData', spec: { value: this.state.value } }; + } + + private _hasData(): boolean { + const item = this.getItem(); + + let data: PanelData | undefined; - // extract single panel data from ResponsiveGridItem if (item instanceof AutoGridItem) { - const panelData = sceneGraph.getData(item.state.body).state.data; - if (panelData) { - data.push(panelData); - } + data = sceneGraph.getData(item.state.body).state.data; } - // extract multiple panel data from RowItem - if (item instanceof RowItem) { - const panels = item.getLayout().getVizPanels(); - for (const panel of panels) { - const panelData = sceneGraph.getData(panel).state.data; - if (panelData) { - data.push(panelData); - } - } - } - - // early return if no panel data - if (!data.length) { + if (!data) { return false; } - for (let panelDataIdx = 0; panelDataIdx < data.length; panelDataIdx++) { - const series = data[panelDataIdx]?.series ?? []; + const series = data?.series ?? []; - for (let seriesIdx = 0; seriesIdx < series.length; seriesIdx++) { - if (series[seriesIdx].length > 0) { - return true; - } + for (let seriesIdx = 0; seriesIdx < series.length; seriesIdx++) { + if (series[seriesIdx].length > 0) { + return true; } } return false; } - public render(): ReactNode { - return ; + public static deserialize(model: ConditionalRenderingDataKind): ConditionalRenderingData { + return new ConditionalRenderingData({ value: model.spec.value }); } - public onDelete() { - handleDeleteNonGroupCondition(this); - } - - public serialize(): ConditionalRenderingDataKind { - return { - kind: 'ConditionalRenderingData', - spec: { - value: this.state.value, - }, - }; + public static createEmpty(): ConditionalRenderingData { + return new ConditionalRenderingData({ value: true }); } } function ConditionalRenderingDataRenderer({ model }: SceneComponentProps) { const { value } = model.useState(); - const enableConditionOptions: Array> = useMemo( + const enableConditionOptions: Array> = useMemo( () => [ - { label: t('dashboard.conditional-rendering.data.enable', 'Enable'), value: true }, - { label: t('dashboard.conditional-rendering.data.disable', 'Disable'), value: false }, + { label: t('dashboard.conditional-rendering.conditions.data.enable', 'Has data'), value: 1 }, + { label: t('dashboard.conditional-rendering.conditions.data.disable', 'No data'), value: 0 }, ], [] ); + const enableConditionOption = useMemo( + () => enableConditionOptions.find((option) => Boolean(option.value) === value) ?? enableConditionOptions[0], + [enableConditionOptions, value] + ); + return ( - - model.onDelete()} /> - model.setStateAndNotify({ value: value })} - /> - + model.setStateAndNotify({ value: Boolean(value) })} + /> ); } diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingEditor.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingEditor.tsx index 510f3c50d0d..6f235361019 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingEditor.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingEditor.tsx @@ -1,3 +1,4 @@ +import { Icon, Stack, Tooltip } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; @@ -11,13 +12,23 @@ export function useConditionalRenderingEditor( return null; } + const title = t('dashboard.conditional-rendering.root.title', 'Show / hide rules'); + return new OptionsPaneCategoryDescriptor({ - title: t('dashboard.conditional-rendering.title', 'Conditional rendering options'), + title, id: 'conditional-rendering-options', + renderTitle: () => ( + +
{title}
+ + + +
+ ), isOpenDefault: true, }).addItem( new OptionsPaneItemDescriptor({ - title: t('dashboard.conditional-rendering.title', 'Conditional rendering options'), + title, render: () => , }) ); diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroup.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroup.tsx index 5ef4f175282..695073ab4b0 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroup.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroup.tsx @@ -1,27 +1,45 @@ -import { css } from '@emotion/css'; -import { Fragment, ReactNode, useMemo } from 'react'; - -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { SceneComponentProps } from '@grafana/scenes'; +import { SceneComponentProps, sceneGraph } from '@grafana/scenes'; import { ConditionalRenderingGroupKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; -import { Divider, Dropdown, Field, Menu, RadioButtonGroup, Stack, ToolbarButton, useStyles2 } from '@grafana/ui'; -import { t, Trans } from 'app/core/internationalization'; +import { Stack } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; -import { ConditionHeader } from './ConditionHeader'; import { ConditionalRenderingBase, ConditionalRenderingBaseState } from './ConditionalRenderingBase'; import { ConditionalRenderingData } from './ConditionalRenderingData'; -import { ConditionalRenderingInterval } from './ConditionalRenderingInterval'; +import { ConditionalRenderingGroupAdd } from './ConditionalRenderingGroupAdd'; +import { ConditionalRenderingGroupCondition } from './ConditionalRenderingGroupCondition'; +import { ConditionalRenderingGroupVisibility } from './ConditionalRenderingGroupVisibility'; +import { ConditionalRenderingTimeRangeSize } from './ConditionalRenderingTimeRangeSize'; import { ConditionalRenderingVariable } from './ConditionalRenderingVariable'; -import { ConditionalRenderingConditions } from './shared'; +import { conditionalRenderingSerializerRegistry } from './serializers'; +import { + ConditionalRenderingKindTypes, + ConditionalRenderingSerializerRegistryItem, + GroupConditionCondition, + GroupConditionItemType, + GroupConditionVisibility, + GroupConditionValue, +} from './types'; -export type GroupConditionValue = ConditionalRenderingConditions[]; export interface ConditionalRenderingGroupState extends ConditionalRenderingBaseState { - condition: 'and' | 'or'; + visibility: GroupConditionVisibility; + condition: GroupConditionCondition; } export class ConditionalRenderingGroup extends ConditionalRenderingBase { + public static Component = ConditionalRenderingGroupRenderer; + + public static serializer: ConditionalRenderingSerializerRegistryItem = { + id: 'ConditionalRenderingGroup', + name: 'Group', + deserialize: this.deserialize, + }; + public get title(): string { - return t('dashboard.conditional-rendering.group.label', 'Group'); + return t('dashboard.conditional-rendering.conditions.group.label', 'Group'); + } + + public get info(): undefined { + return undefined; } public evaluate(): boolean { @@ -29,22 +47,30 @@ export class ConditionalRenderingGroup extends ConditionalRenderingBase entry.evaluate()); - } + const value = + this.state.condition === 'and' + ? this.state.value.every((entry) => entry.evaluate()) + : this.state.value.some((entry) => entry.evaluate()); - return this.state.value.some((entry) => entry.evaluate()); + return this.state.visibility === 'show' ? value : !value; } - public render(): ReactNode { - return ; + public changeVisibility(visibility: GroupConditionVisibility) { + this.setStateAndNotify({ visibility }); } - public changeCondition(condition: 'and' | 'or') { + public changeCondition(condition: GroupConditionCondition) { this.setStateAndNotify({ condition }); } - public addItem(item: ConditionalRenderingConditions) { + public addItem(itemType: GroupConditionItemType) { + const item = + itemType === 'data' + ? ConditionalRenderingData.createEmpty() + : itemType === 'variable' + ? ConditionalRenderingVariable.createEmpty(sceneGraph.getVariables(this).state.variables[0].state.name) + : ConditionalRenderingTimeRangeSize.createEmpty(); + // We don't use `setStateAndNotify` here because // We need to set a parent and activate the new condition before notifying the root this.setState({ value: [...this.state.value, item] }); @@ -53,30 +79,22 @@ export class ConditionalRenderingGroup extends ConditionalRenderingBase condition !== this) }); - } - this.getConditionalLogicRoot().notifyChange(); + public removeItem(key: string) { + this.setStateAndNotify({ value: this.state.value.filter((condition) => condition.state.key !== key) }); } public serialize(): ConditionalRenderingGroupKind { if (this.state.value.some((item) => item instanceof ConditionalRenderingGroup)) { throw new Error('ConditionalRenderingGroup cannot contain nested ConditionalRenderingGroups'); } + return { kind: 'ConditionalRenderingGroup', spec: { + visibility: this.state.visibility, condition: this.state.condition, items: this.state.value .map((condition) => condition.serialize()) @@ -84,92 +102,48 @@ export class ConditionalRenderingGroup extends ConditionalRenderingBase { + const serializerRegistryItem = conditionalRenderingSerializerRegistry.getIfExists(item.kind); + + if (!serializerRegistryItem) { + throw new Error(`No serializer found for conditional rendering kind: ${item.kind}`); + } + + return serializerRegistryItem.deserialize(item); + }), + }); + } + + public static createEmpty(): ConditionalRenderingGroup { + return new ConditionalRenderingGroup({ condition: 'and', visibility: 'show', value: [] }); + } } function ConditionalRenderingGroupRenderer({ model }: SceneComponentProps) { - const styles = useStyles2(getStyles); - const { condition, value } = model.useState(); - - const conditionsOptions: Array> = useMemo( - () => [ - { label: t('dashboard.conditional-rendering.group.condition.meet-all', 'Meet all'), value: 'and' }, - { label: t('dashboard.conditional-rendering.group.condition.meet-any', 'Meet any'), value: 'or' }, - ], - [] - ); + const { condition, visibility, value } = model.useState(); + const { variables } = sceneGraph.getVariables(model).useState(); return ( - - model.onDelete()} /> - - model.changeCondition(value!)} - /> - - - - - {value.map((entry) => ( - - {/* @ts-expect-error */} - - -
- -

{condition}

- -
-
- ))} - -
- - model.addItem(new ConditionalRenderingData({ value: true }))} - /> - model.addItem(new ConditionalRenderingInterval({ value: '7d' }))} - /> - - model.addItem(new ConditionalRenderingVariable({ value: { name: '', operator: '=', value: '' } })) - } - /> - - } - > - - Add condition based on - - -
+ + model.changeVisibility(value)} + /> + {value.length > 1 && ( + model.changeCondition(value)} /> + )} + {value.map((entry) => entry.render())} + 0} + onAdd={(itemType) => model.addItem(itemType)} + /> ); } - -const getStyles = (theme: GrafanaTheme2) => ({ - entryDivider: css({ - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - }), - entryDividerText: css({ - margin: 0, - padding: theme.spacing(0, 2), - textTransform: 'capitalize', - }), - addButtonContainer: css({ - display: 'flex', - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - }), -}); diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupAdd.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupAdd.tsx new file mode 100644 index 00000000000..68cf0db044f --- /dev/null +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupAdd.tsx @@ -0,0 +1,48 @@ +import { useMemo } from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { ValuePicker } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +import { GroupConditionItemType, ItemsWithConditionalRendering } from './types'; + +interface Props { + itemType: ItemsWithConditionalRendering; + hasVariables: boolean; + onAdd: (itemType: GroupConditionItemType) => void; +} + +export const ConditionalRenderingGroupAdd = ({ itemType, hasVariables, onAdd }: Props) => { + const options = useMemo>>(() => { + const allOptions: Array> = [ + { label: t('dashboard.conditional-rendering.conditions.group.add.data', 'Query result'), value: 'data' }, + { + label: t('dashboard.conditional-rendering.conditions.group.add.variable', 'Template variable'), + value: 'variable', + isDisabled: !hasVariables, + }, + { + label: t('dashboard.conditional-rendering.conditions.group.add.time-range-size', 'Time range less than'), + value: 'timeRangeSize', + }, + ]; + + if (itemType !== 'panel') { + allOptions.shift(); + } + + return allOptions; + }, [itemType, hasVariables]); + + return ( + onAdd(value!)} + /> + ); +}; diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupCondition.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupCondition.tsx new file mode 100644 index 00000000000..642c4a22b9b --- /dev/null +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupCondition.tsx @@ -0,0 +1,40 @@ +import { css } from '@emotion/css'; +import { useMemo } from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { Field, RadioButtonGroup, useStyles2 } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +import { GroupConditionCondition } from './types'; + +interface Props { + value: GroupConditionCondition; + onChange: (value: GroupConditionCondition) => void; +} + +export const ConditionalRenderingGroupCondition = ({ value, onChange }: Props) => { + const styles = useStyles2(getStyles); + + const options: Array> = useMemo( + () => [ + { label: t('dashboard.conditional-rendering.conditions.group.condition.all', 'Match all (AND)'), value: 'and' }, + { label: t('dashboard.conditional-rendering.conditions.group.condition.any', 'Match any (OR)'), value: 'or' }, + ], + [] + ); + + return ( + + + + ); +}; + +const getStyles = () => ({ + container: css({ + margin: 0, + }), +}); diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupVisibility.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupVisibility.tsx new file mode 100644 index 00000000000..0b6cd05e3d8 --- /dev/null +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupVisibility.tsx @@ -0,0 +1,44 @@ +import { css } from '@emotion/css'; +import { capitalize } from 'lodash'; +import { useMemo } from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { Field, RadioButtonGroup, useStyles2 } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +import { GroupConditionVisibility, ItemsWithConditionalRendering } from './types'; + +interface Props { + itemType: ItemsWithConditionalRendering; + value: GroupConditionVisibility; + onChange: (value: GroupConditionVisibility) => void; +} + +export const ConditionalRenderingGroupVisibility = ({ itemType, value, onChange }: Props) => { + const styles = useStyles2(getStyles); + + const options: Array> = useMemo( + () => [ + { label: t('dashboard.conditional-rendering.conditions.group.visibility.show', 'Show'), value: 'show' }, + { label: t('dashboard.conditional-rendering.conditions.group.visibility.hide', 'Hide'), value: 'hide' }, + ], + [] + ); + + return ( + + + + ); +}; + +const getStyles = () => ({ + container: css({ + margin: 0, + }), +}); diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingInterval.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingInterval.tsx deleted file mode 100644 index a4a02c32a76..00000000000 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingInterval.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { ReactNode, useState } from 'react'; - -import { rangeUtil } from '@grafana/data'; -import { SceneComponentProps, sceneGraph } from '@grafana/scenes'; -import { ConditionalRenderingTimeIntervalKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; -import { Field, Input, Stack } from '@grafana/ui'; -import { t } from 'app/core/internationalization'; - -import { ConditionHeader } from './ConditionHeader'; -import { ConditionalRenderingBase, ConditionalRenderingBaseState } from './ConditionalRenderingBase'; -import { handleDeleteNonGroupCondition } from './shared'; - -export type IntervalConditionValue = string; -type ConditionalRenderingIntervalState = ConditionalRenderingBaseState; - -export class ConditionalRenderingInterval extends ConditionalRenderingBase { - public get title(): string { - return t('dashboard.conditional-rendering.interval.label', 'Time range interval'); - } - - public constructor(state: ConditionalRenderingIntervalState) { - super(state); - - this.addActivationHandler(() => this._activationHandler()); - } - - private _activationHandler() { - this._subs.add( - sceneGraph.getTimeRange(this).subscribeToState(() => { - this.getConditionalLogicRoot().notifyChange(); - }) - ); - } - - public evaluate(): boolean { - try { - const interval = rangeUtil.intervalToSeconds(this.state.value); - - const timeRange = sceneGraph.getTimeRange(this); - - if (timeRange.state.value.to.unix() - timeRange.state.value.from.unix() <= interval) { - return true; - } - } catch { - return false; - } - - return false; - } - - public render(): ReactNode { - return ; - } - - public onDelete() { - handleDeleteNonGroupCondition(this); - } - - public serialize(): ConditionalRenderingTimeIntervalKind { - return { - kind: 'ConditionalRenderingTimeInterval', - spec: { - value: this.state.value, - }, - }; - } -} - -function ConditionalRenderingIntervalRenderer({ model }: SceneComponentProps) { - const { value } = model.useState(); - const [isValid, setIsValid] = useState(validateIntervalRegex.test(value)); - - return ( - - model.onDelete()} /> - - { - setIsValid(validateIntervalRegex.test(e.currentTarget.value)); - model.setStateAndNotify({ value: e.currentTarget.value }); - }} - /> - - - ); -} - -export const validateIntervalRegex = /^(-?\d+(?:\.\d+)?)(ms|[Mwdhmsy])?$/; diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingOverlay.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingOverlay.tsx new file mode 100644 index 00000000000..872d02e00c5 --- /dev/null +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingOverlay.tsx @@ -0,0 +1,67 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, Tooltip, useStyles2 } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +export const ConditionalRenderingOverlay = () => { + const styles = useStyles2(getStyles); + + return ( +
+ +
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + container: css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + position: 'absolute', + width: '100%', + height: '100%', + bottom: 0, + right: 0, + + [theme.transitions.handleMotion('no-preference', 'reduce')]: { + transition: 'all 0.2s ease', + }, + + '&:before': css({ + content: '""', + opacity: 0.6, + position: 'absolute', + width: '100%', + height: '100%', + top: 0, + left: 0, + backgroundColor: theme.colors.background.canvas, + pointerEvents: 'none', + }), + + '& > svg': css({ + height: '48px', + width: '48px', + maxWidth: '75%', + maxHeight: '75%', + }), + + '.dashboard-visible-hidden-element:hover > &': css({ + width: '30px', + height: '30px', + top: 'unset', + left: 'unset', + right: 0, + bottom: 0, + }), + }), +}); diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingTimeRangeSize.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingTimeRangeSize.tsx new file mode 100644 index 00000000000..e11fc1fad79 --- /dev/null +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingTimeRangeSize.tsx @@ -0,0 +1,219 @@ +import { css } from '@emotion/css'; +import { useEffect, useMemo, useState } from 'react'; + +import { rangeUtil, SelectableValue } from '@grafana/data'; +import { SceneComponentProps, sceneGraph } from '@grafana/scenes'; +import { ConditionalRenderingTimeRangeSizeKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; +import { Field, Select, useStyles2 } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +import { ConditionalRenderingBase, ConditionalRenderingBaseState } from './ConditionalRenderingBase'; +import { ConditionalRenderingSerializerRegistryItem, TimeRangeSizeConditionValue } from './types'; + +type ConditionalRenderingTimeRangeSizeState = ConditionalRenderingBaseState; + +export class ConditionalRenderingTimeRangeSize extends ConditionalRenderingBase { + public static Component = ConditionalRenderingTimeRangeSizeRenderer; + + public static serializer: ConditionalRenderingSerializerRegistryItem = { + id: 'ConditionalRenderingTimeRangeSize', + name: 'Time Range Size', + deserialize: this.deserialize, + }; + + public get title(): string { + return t('dashboard.conditional-rendering.conditions.time-range-size.label', 'Time range less than'); + } + + public get info(): string { + return t( + 'dashboard.conditional-rendering.conditions.time-range-size.info', + 'Show or hide the {{type}} if the dashboard time range is shorter than the selected time frame.', + { type: this.getItemType() } + ); + } + + public constructor(state: ConditionalRenderingTimeRangeSizeState) { + super(state); + + this.addActivationHandler(() => this._activationHandler()); + } + + private _activationHandler() { + this._subs.add(sceneGraph.getTimeRange(this).subscribeToState(() => this.notifyChange())); + } + + public evaluate(): boolean { + try { + if (!validateIntervalRegex.test(this.state.value)) { + return true; + } + + const interval = rangeUtil.intervalToSeconds(this.state.value); + const timeRange = sceneGraph.getTimeRange(this); + + if (timeRange.state.value.to.unix() - timeRange.state.value.from.unix() <= interval) { + return true; + } + } catch { + return false; + } + + return false; + } + + public serialize(): ConditionalRenderingTimeRangeSizeKind { + return { kind: 'ConditionalRenderingTimeRangeSize', spec: { value: this.state.value } }; + } + + public static deserialize(model: ConditionalRenderingTimeRangeSizeKind): ConditionalRenderingTimeRangeSize { + return new ConditionalRenderingTimeRangeSize({ value: model.spec.value }); + } + + public static createEmpty(): ConditionalRenderingTimeRangeSize { + return new ConditionalRenderingTimeRangeSize({ value: '7d' }); + } +} + +function ConditionalRenderingTimeRangeSizeRenderer({ model }: SceneComponentProps) { + const { value } = model.useState(); + const [isValid, setIsValid] = useState(validateIntervalRegex.test(value)); + const styles = useStyles2(getStyles); + + useEffect(() => setIsValid(validateIntervalRegex.test(value)), [value]); + + const staticOptions = useMemo>>( + () => [ + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.minutes', '{{value}} minutes', { + value: 5, + }), + value: '5m', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.minutes', '{{value}} minutes', { + value: 15, + }), + value: '15m', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.minutes', '{{value}} minutes', { + value: 30, + }), + value: '30m', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.hour', '{{value}} hour', { + value: 1, + }), + value: '1h', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.hours', '{{value}} hours', { + value: 3, + }), + value: '3h', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.hours', '{{value}} hours', { + value: 6, + }), + value: '6h', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.hours', '{{value}} hours', { + value: 12, + }), + value: '12h', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.hours', '{{value}} hours', { + value: 24, + }), + value: '24h', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.days', '{{value}} days', { + value: 2, + }), + value: '2d', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.days', '{{value}} days', { + value: 7, + }), + value: '7d', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.days', '{{value}} days', { + value: 30, + }), + value: '30d', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.days', '{{value}} days', { + value: 90, + }), + value: '90d', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.months', '{{value}} months', { + value: 6, + }), + value: '6M', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.year', '{{value}} year', { + value: 1, + }), + value: '1y', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.years', '{{value}} years', { + value: 2, + }), + value: '2y', + }, + { + label: t('dashboard.conditional-rendering.conditions.time-range-size.values.years', '{{value}} years', { + value: 5, + }), + value: '5y', + }, + ], + [] + ); + + const options = useMemo(() => { + if (staticOptions.find((option) => option.value === value)) { + return staticOptions; + } + + return [{ label: value, value }, ...staticOptions]; + }, [staticOptions, value]); + + return ( + + model.setStateAndNotify({ value: { ...value, value: e.currentTarget.value } })} + + + + model.setStateAndNotify({ value: { ...value, name: option.value } })} /> - + + + model.setStateAndNotify({ value: { ...value, operator: option.value } })} + />
+ model.setStateAndNotify({ value: { ...value, value: e.currentTarget.value } })} + /> ); } - -const getStyles = (theme: GrafanaTheme2) => ({ - variableNameSelect: css({ - flexGrow: 1, - }), - operatorSelect: css({ - width: theme.spacing(12), - }), -}); diff --git a/public/app/features/dashboard-scene/conditional-rendering/serializers.ts b/public/app/features/dashboard-scene/conditional-rendering/serializers.ts index 4371e601b45..77a858d2201 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/serializers.ts +++ b/public/app/features/dashboard-scene/conditional-rendering/serializers.ts @@ -1,96 +1,16 @@ -import { Registry, RegistryItem } from '@grafana/data'; -import { - ConditionalRenderingGroupKind, - ConditionalRenderingVariableKind, - ConditionalRenderingDataKind, - ConditionalRenderingTimeIntervalKind, -} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; +import { Registry } from '@grafana/data'; import { ConditionalRenderingData } from './ConditionalRenderingData'; import { ConditionalRenderingGroup } from './ConditionalRenderingGroup'; -import { ConditionalRenderingInterval } from './ConditionalRenderingInterval'; +import { ConditionalRenderingTimeRangeSize } from './ConditionalRenderingTimeRangeSize'; import { ConditionalRenderingVariable } from './ConditionalRenderingVariable'; - -export type ConditionalRenderingKindTypes = - | ConditionalRenderingGroupKind - | ConditionalRenderingVariableKind - | ConditionalRenderingDataKind - | ConditionalRenderingTimeIntervalKind; - -export interface ConditionalRenderingSerializer { - deserialize( - model: ConditionalRenderingKindTypes - ): ConditionalRenderingGroup | ConditionalRenderingVariable | ConditionalRenderingData | ConditionalRenderingInterval; -} - -interface ConditionalRenderingSerializerRegistryItem extends RegistryItem { - serializer: ConditionalRenderingSerializer; -} - -export class ConditionalRenderingGroupSerializer implements ConditionalRenderingSerializer { - deserialize(model: ConditionalRenderingGroupKind): ConditionalRenderingGroup { - return new ConditionalRenderingGroup({ - condition: model.spec.condition, - value: model.spec.items.map((item: ConditionalRenderingKindTypes) => { - const serializerRegistryItem = conditionalRenderingSerializerRegistry.getIfExists(item.kind); - if (!serializerRegistryItem) { - throw new Error(`No serializer found for conditional rendering kind: ${item.kind}`); - } - return serializerRegistryItem.serializer.deserialize(item); - }), - }); - } -} - -export class ConditionalRenderingVariableSerializer implements ConditionalRenderingSerializer { - deserialize(model: ConditionalRenderingVariableKind): ConditionalRenderingVariable { - return new ConditionalRenderingVariable({ - value: { - name: model.spec.variable, - operator: model.spec.operator === 'equals' ? '=' : '!=', - value: model.spec.value, - }, - }); - } -} - -export class ConditionalRenderingDataSerializer implements ConditionalRenderingSerializer { - deserialize(model: ConditionalRenderingDataKind): ConditionalRenderingData { - return new ConditionalRenderingData({ - value: model.spec.value, - }); - } -} - -export class ConditionalRenderingIntervalSerializer implements ConditionalRenderingSerializer { - deserialize(model: ConditionalRenderingTimeIntervalKind): ConditionalRenderingInterval { - return new ConditionalRenderingInterval({ - value: model.spec.value, - }); - } -} +import { ConditionalRenderingSerializerRegistryItem } from './types'; export const conditionalRenderingSerializerRegistry = new Registry(() => { return [ - { - id: 'ConditionalRenderingGroup', - name: 'Group', - serializer: new ConditionalRenderingGroupSerializer(), - }, - { - id: 'ConditionalRenderingVariable', - name: 'Variable', - serializer: new ConditionalRenderingVariableSerializer(), - }, - { - id: 'ConditionalRenderingData', - name: 'Data', - serializer: new ConditionalRenderingDataSerializer(), - }, - { - id: 'ConditionalRenderingTimeInterval', - name: 'Time Interval', - serializer: new ConditionalRenderingIntervalSerializer(), - }, + ConditionalRenderingGroup.serializer, + ConditionalRenderingVariable.serializer, + ConditionalRenderingData.serializer, + ConditionalRenderingTimeRangeSize.serializer, ]; }); diff --git a/public/app/features/dashboard-scene/conditional-rendering/shared.ts b/public/app/features/dashboard-scene/conditional-rendering/shared.ts deleted file mode 100644 index 2a88ffc62b6..00000000000 --- a/public/app/features/dashboard-scene/conditional-rendering/shared.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ConditionalRenderingData, DataConditionValue } from './ConditionalRenderingData'; -import { ConditionalRenderingGroup, GroupConditionValue } from './ConditionalRenderingGroup'; -import { ConditionalRenderingInterval, IntervalConditionValue } from './ConditionalRenderingInterval'; -import { ConditionalRenderingVariable, VariableConditionValue } from './ConditionalRenderingVariable'; - -export type ConditionValues = - | DataConditionValue - | VariableConditionValue - | GroupConditionValue - | IntervalConditionValue; - -export type ConditionalRenderingConditions = - | ConditionalRenderingData - | ConditionalRenderingVariable - | ConditionalRenderingInterval - | ConditionalRenderingGroup; - -type NonGroupConditions = Exclude; - -export const handleDeleteNonGroupCondition = (model: NonGroupConditions) => { - if (model.parent instanceof ConditionalRenderingGroup) { - model.parent.setState({ value: model.parent.state.value.filter((condition) => condition !== model) }); - model.getConditionalLogicRoot().notifyChange(); - } -}; diff --git a/public/app/features/dashboard-scene/conditional-rendering/types.ts b/public/app/features/dashboard-scene/conditional-rendering/types.ts new file mode 100644 index 00000000000..9330812b55b --- /dev/null +++ b/public/app/features/dashboard-scene/conditional-rendering/types.ts @@ -0,0 +1,53 @@ +import { RegistryItem } from '@grafana/data'; +import { + ConditionalRenderingDataKind, + ConditionalRenderingGroupKind, + ConditionalRenderingTimeRangeSizeKind, + ConditionalRenderingVariableKind, +} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; + +import { ConditionalRenderingData } from './ConditionalRenderingData'; +import { ConditionalRenderingGroup } from './ConditionalRenderingGroup'; +import { ConditionalRenderingTimeRangeSize } from './ConditionalRenderingTimeRangeSize'; +import { ConditionalRenderingVariable } from './ConditionalRenderingVariable'; + +export type ItemsWithConditionalRendering = 'panel' | 'row' | 'tab' | 'element'; + +export type DataConditionValue = boolean; + +export type GroupConditionVisibility = 'show' | 'hide'; +export type GroupConditionCondition = 'and' | 'or'; +export type GroupConditionItemType = 'data' | 'timeRangeSize' | 'variable'; +export type GroupConditionValue = ConditionalRenderingConditions[]; + +export type TimeRangeSizeConditionValue = string; + +export type VariableConditionValueOperator = '=' | '!='; + +export type VariableConditionValue = { + name: string; + operator: VariableConditionValueOperator; + value: string; +}; + +export type ConditionValues = + | DataConditionValue + | VariableConditionValue + | GroupConditionValue + | TimeRangeSizeConditionValue; + +export type ConditionalRenderingConditions = + | ConditionalRenderingData + | ConditionalRenderingVariable + | ConditionalRenderingTimeRangeSize + | ConditionalRenderingGroup; + +export type ConditionalRenderingKindTypes = + | ConditionalRenderingGroupKind + | ConditionalRenderingVariableKind + | ConditionalRenderingDataKind + | ConditionalRenderingTimeRangeSizeKind; + +export interface ConditionalRenderingSerializerRegistryItem extends RegistryItem { + deserialize(model: ConditionalRenderingKindTypes): ConditionalRenderingConditions; +} diff --git a/public/app/features/dashboard-scene/conditional-rendering/useIsConditionallyHidden.tsx b/public/app/features/dashboard-scene/conditional-rendering/useIsConditionallyHidden.tsx new file mode 100644 index 00000000000..6c285e1f852 --- /dev/null +++ b/public/app/features/dashboard-scene/conditional-rendering/useIsConditionallyHidden.tsx @@ -0,0 +1,22 @@ +import { ReactNode } from 'react'; + +import { SceneObject } from '@grafana/scenes'; + +import { ConditionalRendering } from './ConditionalRendering'; +import { ConditionalRenderingOverlay } from './ConditionalRenderingOverlay'; + +export function useIsConditionallyHidden(scene: SceneObject): [boolean, string | undefined, ReactNode | null] { + const state = scene.useState(); + + if (!('conditionalRendering' in state) || !(state.conditionalRendering instanceof ConditionalRendering)) { + return [false, undefined, null]; + } + + const value = state.conditionalRendering.evaluate() ?? true; + + return [ + !value, + value ? undefined : 'dashboard-visible-hidden-element', + value ? null : , + ]; +} diff --git a/public/app/features/dashboard-scene/conditional-rendering/utils.ts b/public/app/features/dashboard-scene/conditional-rendering/utils.ts new file mode 100644 index 00000000000..9ef497e7230 --- /dev/null +++ b/public/app/features/dashboard-scene/conditional-rendering/utils.ts @@ -0,0 +1,19 @@ +import { SceneObject } from '@grafana/scenes'; + +import { AutoGridItem } from '../scene/layout-responsive-grid/ResponsiveGridItem'; +import { RowItem } from '../scene/layout-rows/RowItem'; +import { TabItem } from '../scene/layout-tabs/TabItem'; + +import { ItemsWithConditionalRendering } from './types'; + +export function getItemType(object: SceneObject): ItemsWithConditionalRendering { + if (object instanceof AutoGridItem) { + return 'panel'; + } else if (object instanceof RowItem) { + return 'row'; + } else if (object instanceof TabItem) { + return 'tab'; + } + + return 'element'; +} diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx index c8393493ef2..cb470c80b47 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx @@ -24,7 +24,12 @@ import { getDashboardSceneFor } from '../utils/utils'; import { DashboardOutline } from './DashboardOutline'; import { ElementEditPane } from './ElementEditPane'; import { ElementSelection } from './ElementSelection'; -import { NewObjectAddedToCanvasEvent, ObjectRemovedFromCanvasEvent, ObjectsReorderedOnCanvasEvent } from './shared'; +import { + ConditionalRenderingChangedEvent, + NewObjectAddedToCanvasEvent, + ObjectRemovedFromCanvasEvent, + ObjectsReorderedOnCanvasEvent, +} from './shared'; import { useEditableElement } from './useEditableElement'; export interface DashboardEditPaneState extends SceneObjectState { @@ -66,6 +71,12 @@ export class DashboardEditPane extends SceneObjectBase { this.forceRender(); }) ); + + this._subs.add( + dashboard.subscribeToEvent(ConditionalRenderingChangedEvent, ({ payload }) => { + this.forceRender(); + }) + ); } public enableSelection() { diff --git a/public/app/features/dashboard-scene/edit-pane/shared.ts b/public/app/features/dashboard-scene/edit-pane/shared.ts index c6d1a6208aa..82b3176ee91 100644 --- a/public/app/features/dashboard-scene/edit-pane/shared.ts +++ b/public/app/features/dashboard-scene/edit-pane/shared.ts @@ -66,3 +66,7 @@ export class ObjectRemovedFromCanvasEvent extends BusEventWithPayload { static type = 'objects-reordered-on-canvas'; } + +export class ConditionalRenderingChangedEvent extends BusEventWithPayload { + static type = 'conditional-rendering-changed'; +} diff --git a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts index 8ce24fced8a..89313c4e164 100644 --- a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts +++ b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts @@ -17,7 +17,7 @@ import { createWorker } from 'app/features/dashboard-scene/saving/createDetectCh import { ConditionalRenderingData } from '../conditional-rendering/ConditionalRenderingData'; import { ConditionalRenderingGroup } from '../conditional-rendering/ConditionalRenderingGroup'; -import { ConditionalRenderingInterval } from '../conditional-rendering/ConditionalRenderingInterval'; +import { ConditionalRenderingTimeRangeSize } from '../conditional-rendering/ConditionalRenderingTimeRangeSize'; import { ConditionalRenderingVariable } from '../conditional-rendering/ConditionalRenderingVariable'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; @@ -150,7 +150,7 @@ export class DashboardSceneChangeTracker { if (payload.changedObject instanceof ConditionalRenderingVariable) { return true; } - if (payload.changedObject instanceof ConditionalRenderingInterval) { + if (payload.changedObject instanceof ConditionalRenderingTimeRangeSize) { return true; } if (payload.changedObject instanceof ConditionalRenderingGroup) { diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemRenderer.tsx index 43566124f97..dd6c6a7916a 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemRenderer.tsx @@ -4,7 +4,8 @@ import { GrafanaTheme2 } from '@grafana/data/'; import { SceneComponentProps } from '@grafana/scenes'; import { useStyles2 } from '@grafana/ui'; -import { useDashboardState, useIsConditionallyHidden } from '../../utils/utils'; +import { useIsConditionallyHidden } from '../../conditional-rendering/useIsConditionallyHidden'; +import { useDashboardState } from '../../utils/utils'; import { AutoGridItem } from './ResponsiveGridItem'; import { DRAGGED_ITEM_HEIGHT, DRAGGED_ITEM_LEFT, DRAGGED_ITEM_TOP, DRAGGED_ITEM_WIDTH } from './const'; @@ -13,7 +14,8 @@ export function AutoGridItemRenderer({ model }: SceneComponentProps {repeatedPanels.map((item) => ( -
+
+ {conditionalRenderingOverlay}
))} @@ -38,14 +38,9 @@ export function AutoGridItemRenderer({ model }: SceneComponentProps {isDragged &&
} -
+
+ {conditionalRenderingOverlay}
); diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx index 62510d2ab65..ff61f974d0d 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx @@ -8,8 +8,9 @@ import { SceneComponentProps } from '@grafana/scenes'; import { clearButtonStyles, Icon, Tooltip, useElementSelection, usePointerDistance, useStyles2 } from '@grafana/ui'; import { t } from 'app/core/internationalization'; +import { useIsConditionallyHidden } from '../../conditional-rendering/useIsConditionallyHidden'; import { useIsClone } from '../../utils/clone'; -import { useDashboardState, useInterpolatedTitle, useIsConditionallyHidden } from '../../utils/utils'; +import { useDashboardState, useInterpolatedTitle } from '../../utils/utils'; import { DashboardScene } from '../DashboardScene'; import { RowItem } from './RowItem'; @@ -18,7 +19,8 @@ export function RowItemRenderer({ model }: SceneComponentProps) { const { layout, collapse: isCollapsed, fillScreen, hideHeader: isHeaderHidden, isDropTarget, key } = model.useState(); const isClone = useIsClone(model); const { isEditing } = useDashboardState(model); - const isConditionallyHidden = useIsConditionallyHidden(model); + const [isConditionallyHidden, conditionalRenderingClass, conditionalRenderingOverlay] = + useIsConditionallyHidden(model); const { isSelected, onSelect, isSelectable } = useElementSelection(key); const title = useInterpolatedTitle(model); const { rows } = model.getParentLayout().useState(); @@ -54,12 +56,13 @@ export function RowItemRenderer({ model }: SceneComponentProps) { data-dashboard-drop-target-key={model.state.key} className={cx( styles.wrapper, + !isCollapsed && styles.wrapperNotCollapsed, dragSnapshot.isDragging && styles.dragging, isEditing && !isCollapsed && styles.wrapperEditing, isEditing && isCollapsed && styles.wrapperEditingCollapsed, isCollapsed && styles.wrapperCollapsed, shouldGrow && styles.wrapperGrow, - isConditionallyHidden && 'dashboard-visible-hidden-element', + conditionalRenderingClass, !isClone && isSelected && 'dashboard-selected-element', !isClone && !isSelected && selectableHighlight && 'dashboard-selectable-element', isDropTarget && 'dashboard-drop-target' @@ -86,11 +89,7 @@ export function RowItemRenderer({ model }: SceneComponentProps) { > {(!isHeaderHidden || isEditing) && (
) {
)} {!isCollapsed && } + {conditionalRenderingOverlay}
)} @@ -170,6 +170,11 @@ function getStyles(theme: GrafanaTheme2) { }), rowTitleHidden: css({ textDecoration: 'line-through', + opacity: 0.6, + + '&:hover': css({ + opacity: 1, + }), }), rowTitleNested: css({ fontSize: theme.typography.body.fontSize, @@ -183,9 +188,12 @@ function getStyles(theme: GrafanaTheme2) { flexDirection: 'column', width: '100%', minHeight: '100px', + }), + wrapperNotCollapsed: css({ '> div:nth-child(2)': { marginLeft: theme.spacing(3), position: 'relative', + '&:before': { content: '""', position: 'absolute', diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx index 2a014f48433..afa22d1792a 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx @@ -15,6 +15,7 @@ import store from 'app/core/store'; import kbn from 'app/core/utils/kbn'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; +import { ConditionalRendering } from '../../conditional-rendering/ConditionalRendering'; import { serializeTab } from '../../serialization/layoutSerializers/TabsLayoutSerializer'; import { getElements } from '../../serialization/layoutSerializers/utils'; import { getDashboardSceneFor, getDefaultVizPanel } from '../../utils/utils'; @@ -41,6 +42,7 @@ export interface TabItemState extends SceneObjectState { */ isNew?: boolean; isDropTarget?: boolean; + conditionalRendering?: ConditionalRendering; } export class TabItem @@ -64,7 +66,20 @@ export class TabItem ...state, title: state?.title ?? t('dashboard.tabs-layout.tab.new', 'New tab'), layout: state?.layout ?? AutoGridLayoutManager.createEmpty(), + conditionalRendering: state?.conditionalRendering ?? ConditionalRendering.createEmpty(), }); + + this.addActivationHandler(() => this._activationHandler()); + } + + private _activationHandler() { + const deactivate = this.state.conditionalRendering?.activate(); + + return () => { + if (deactivate) { + deactivate(); + } + }; } public getEditableElementInfo(): EditableDashboardElementInfo { diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx index 8d4528022c6..9b2ff27e38c 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx @@ -5,6 +5,7 @@ import { t } from 'app/core/internationalization'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; +import { useConditionalRenderingEditor } from '../../conditional-rendering/ConditionalRenderingEditor'; import { useLayoutCategory } from '../layouts-shared/DashboardLayoutSelector'; import { useEditPaneInputAutoFocus } from '../layouts-shared/utils'; @@ -26,7 +27,18 @@ export function getEditOptions(model: TabItem): OptionsPaneCategoryDescriptor[] const layoutCategory = useLayoutCategory(layout); - return [tabCategory, layoutCategory]; + const editOptions = [tabCategory, layoutCategory]; + + const conditionalRenderingCategory = useMemo( + () => useConditionalRenderingEditor(model.state.conditionalRendering), + [model] + ); + + if (conditionalRenderingCategory) { + editOptions.push(conditionalRenderingCategory); + } + + return editOptions; } function TabTitleInput({ tab }: { tab: TabItem }) { diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx index 748b8c92e10..7f44fb2f0c3 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx @@ -6,6 +6,7 @@ import { locationUtil, textUtil } from '@grafana/data'; import { SceneComponentProps, sceneGraph } from '@grafana/scenes'; import { Tab, useElementSelection, usePointerDistance, useStyles2 } from '@grafana/ui'; +import { useIsConditionallyHidden } from '../../conditional-rendering/useIsConditionallyHidden'; import { useDashboardState } from '../../utils/utils'; import { TabItem } from './TabItem'; @@ -25,6 +26,11 @@ export function TabItemRenderer({ model }: SceneComponentProps) { const href = textUtil.sanitize(locationUtil.getUrlForPartial(location, { [urlKey]: mySlug })); const styles = useStyles2(getStyles); const pointerDistance = usePointerDistance(); + const [isConditionallyHidden] = useIsConditionallyHidden(model); + + if (isConditionallyHidden && !isEditing && !isActive) { + return null; + } return ( @@ -39,6 +45,7 @@ export function TabItemRenderer({ model }: SceneComponentProps) { ref={model.containerRef} truncate className={cx( + isConditionallyHidden && styles.hidden, isSelected && 'dashboard-selected-element', isSelectable && !isSelected && 'dashboard-selectable-element', isDropTarget && 'dashboard-drop-target' @@ -74,4 +81,11 @@ const getStyles = () => ({ dragging: css({ cursor: 'move', }), + hidden: css({ + opacity: 0.4, + + '&:hover': css({ + opacity: 1, + }), + }), }); diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts index fd76f2a1c74..6ab820816d1 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts @@ -7,6 +7,7 @@ import { TabItem } from '../../scene/layout-tabs/TabItem'; import { TabsLayoutManager } from '../../scene/layout-tabs/TabsLayoutManager'; import { layoutDeserializerRegistry } from './layoutSerializerRegistry'; +import { getConditionalRendering } from './utils'; export function serializeTabsLayout(layoutManager: TabsLayoutManager): DashboardV2Spec['layout'] { return { @@ -19,13 +20,21 @@ export function serializeTabsLayout(layoutManager: TabsLayoutManager): Dashboard export function serializeTab(tab: TabItem): TabsLayoutTabKind { const layout = tab.state.layout.serialize(); - return { + const tabKind: TabsLayoutTabKind = { kind: 'TabsLayoutTab', spec: { title: tab.state.title, layout: layout, }, }; + + const conditionalRenderingRootGroup = tab.state.conditionalRendering?.serialize(); + // Only serialize the conditional rendering if it has items + if (conditionalRenderingRootGroup?.spec.items.length) { + tabKind.spec.conditionalRendering = conditionalRenderingRootGroup; + } + + return tabKind; } export function deserializeTabsLayout( @@ -55,5 +64,6 @@ export function deserializeTab( return new TabItem({ title: tab.spec.title, layout: layoutDeserializerRegistry.get(layout.kind).deserialize(layout, elements, preload, panelIdGenerator), + conditionalRendering: getConditionalRendering(tab), }); } diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts index 30a5f12e49b..d47e786f3c9 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts @@ -18,6 +18,7 @@ import { PanelKind, PanelQueryKind, QueryVariableKind, + TabsLayoutTabKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; @@ -238,13 +239,16 @@ export function getLayout(sceneState: DashboardLayoutManager): DashboardV2Spec[' return sceneState.serialize(); } -export function getConditionalRendering(item: RowsLayoutRowKind | AutoGridLayoutItemKind): ConditionalRendering { +export function getConditionalRendering( + item: TabsLayoutTabKind | RowsLayoutRowKind | AutoGridLayoutItemKind +): ConditionalRendering { if (!item.spec.conditionalRendering) { return ConditionalRendering.createEmpty(); } + const rootGroup = conditionalRenderingSerializerRegistry .get(item.spec.conditionalRendering.kind) - .serializer.deserialize(item.spec.conditionalRendering); + .deserialize(item.spec.conditionalRendering); if (rootGroup && !(rootGroup instanceof ConditionalRenderingGroup)) { throw new Error(`Conditional rendering must always start with a root group`); diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index c7c1a305a2a..fff901adb67 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -23,8 +23,6 @@ import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks'; import { panelMenuBehavior } from '../scene/PanelMenuBehavior'; import { UNCONFIGURED_PANEL_PLUGIN_ID } from '../scene/UnconfiguredPanel'; import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; -import { AutoGridItem } from '../scene/layout-responsive-grid/ResponsiveGridItem'; -import { RowItem } from '../scene/layout-rows/RowItem'; import { setDashboardPanelContext } from '../scene/setDashboardPanelContext'; import { DashboardLayoutManager, isDashboardLayoutManager } from '../scene/types/DashboardLayoutManager'; @@ -456,12 +454,6 @@ export function useDashboardState(scene: SceneObject): DashboardSceneState { return dashboard.useState(); } -export function useIsConditionallyHidden(scene: RowItem | AutoGridItem): boolean { - const { conditionalRendering } = scene.useState(); - - return !(conditionalRendering?.evaluate() ?? true); -} - export function useInterpolatedTitle(scene: SceneObject): string { const { title } = scene.useState(); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 672a329ba3b..f2dc7fe42ce 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1423,43 +1423,67 @@ "paste-tab": "Paste tab" }, "conditional-rendering": { - "data": { - "disable": "Disable", - "enable": "Enable", - "label": "Data" - }, - "group": { - "add": { - "button": "Add condition based on", - "data": "Data", - "interval": "Interval", - "variable": "Variable value" + "conditions": { + "data": { + "disable": "No data", + "enable": "Has data", + "info": "Show or hide the {{type}} based on query results.", + "label": "Query result" }, - "condition": { - "label": "Evaluate conditions", - "meet-all": "Meet all", - "meet-any": "Meet any" + "group": { + "add": { + "button": "Add rule", + "data": "Query result", + "time-range-size": "Time range less than", + "variable": "Template variable" + }, + "condition": { + "all": "Match all (AND)", + "any": "Match any (OR)", + "label": "Match rules" + }, + "label": "Group", + "visibility": { + "hide": "Hide", + "label": "{{type}} visibility", + "show": "Show" + } }, - "label": "Group" - }, - "interval": { - "input-label": "Value", - "invalid-message": "Invalid interval", - "label": "Time range interval" - }, - "shared": { - "delete-condition": "Delete Condition" - }, - "title": "Conditional rendering options", - "variable": { - "label": "Variable", - "operator": { - "equals": "Equals", - "not-equal": "Not equal" + "shared": { + "delete-condition": "Delete Condition", + "unsupported-item-type": "This condition is not supported by the element, hence it will be ignored." }, - "select-operator": "Operator", - "select-variable": "Select variable", - "value-input": "Value" + "time-range-size": { + "info": "Show or hide the {{type}} if the dashboard time range is shorter than the selected time frame.", + "invalid-message": "Invalid interval", + "label": "Time range less than", + "values": { + "days": "{{value}} days", + "hour": "{{value}} hour", + "hours": "{{value}} hours", + "minutes": "{{value}} minutes", + "months": "{{value}} months", + "year": "{{value}} year", + "years": "{{value}} years" + } + }, + "variable": { + "info": "Show or hide the {{type}} dynamically based on the variable value.", + "label": "Template variable", + "name": "Name", + "operator": { + "equals": "Equals", + "not-equals": "Not equals" + }, + "value": "Value" + } + }, + "overlay": { + "tooltip": "Element is hidden due to conditional rendering." + }, + "root": { + "info": "Set rules to control {{type}} visibility by matching any or all rules.", + "title": "Show / hide rules" } }, "default-layout": {