Dynamic Dashboard: Redesign conditional rendering [2] (#103112)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 => ({
|
||||
|
||||
@@ -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 => ({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Text variant="h6">{title}</Text>
|
||||
<IconButton
|
||||
aria-label={t('dashboard.conditional-rendering.shared.delete-condition', 'Delete Condition')}
|
||||
name="trash-alt"
|
||||
onClick={() => onDelete()}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -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<ConditionalRenderingState> {
|
||||
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<ConditionalRenderingSt
|
||||
|
||||
private _activationHandler() {
|
||||
// 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());
|
||||
@@ -32,19 +46,32 @@ export class ConditionalRendering extends SceneObjectBase<ConditionalRenderingSt
|
||||
|
||||
public notifyChange() {
|
||||
this.parent?.forceRender();
|
||||
this.parent?.publishEvent(new ConditionalRenderingChangedEvent(this), true);
|
||||
}
|
||||
|
||||
public static createEmpty(): ConditionalRendering {
|
||||
return new ConditionalRendering({ rootGroup: ConditionalRenderingGroup.createEmpty() });
|
||||
public deleteItem<T extends ConditionalRenderingBase>(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<ConditionalRendering>) {
|
||||
const { rootGroup } = model.useState();
|
||||
|
||||
return <rootGroup.Component model={rootGroup} />;
|
||||
return rootGroup.render(false);
|
||||
}
|
||||
|
||||
+80
-21
@@ -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<V = ConditionValues> extends SceneObjectState {
|
||||
value: V;
|
||||
}
|
||||
|
||||
export abstract class ConditionalRenderingBase<
|
||||
S extends ConditionalRenderingBaseState<ConditionValues>,
|
||||
S extends ConditionalRenderingBaseState = ConditionalRenderingBaseState,
|
||||
> extends SceneObjectBase<S> {
|
||||
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 <ConditionalRenderingBaseRenderer model={this} withWrapper={withWrapper} key={this.state.key!} />;
|
||||
}
|
||||
|
||||
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<S>) {
|
||||
this.setState(state);
|
||||
this.getConditionalLogicRoot().notifyChange();
|
||||
this.notifyChange();
|
||||
}
|
||||
|
||||
private _getConditionalLogicRoot(): ConditionalRendering {
|
||||
return sceneGraph.getAncestor(this, ConditionalRendering);
|
||||
}
|
||||
}
|
||||
|
||||
function ConditionalRenderingBaseRenderer({
|
||||
function ConditionalRenderingBaseRenderer<T extends ConditionalRenderingBase>({
|
||||
model,
|
||||
}: SceneComponentProps<ConditionalRenderingBase<ConditionalRenderingBaseState>>) {
|
||||
return model.render();
|
||||
withWrapper,
|
||||
}: SceneComponentProps<T> & { withWrapper: boolean }) {
|
||||
const comp = <model.Component model={model} key={model.state.key} />;
|
||||
|
||||
if (!withWrapper) {
|
||||
return comp;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack direction="column" key={model.state.key}>
|
||||
<Stack direction="row" gap={1}>
|
||||
<Text variant="bodySmall">{model.title}</Text>
|
||||
{model.info && (
|
||||
<Tooltip content={model.info}>
|
||||
<Icon name="info-circle" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" gap={1} justifyContent="stretch" alignItems="baseline">
|
||||
<Stack flex={1} direction="column" gap={1}>
|
||||
{comp}
|
||||
{!model.isItemSupported() && (
|
||||
<Alert severity="error" title="">
|
||||
<Trans i18nKey="dashboard.conditional-rendering.conditions.shared.unsupported-item-type">
|
||||
This condition is not supported by the element, hence it will be ignored.
|
||||
</Trans>
|
||||
</Alert>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
<IconButton
|
||||
aria-label={t('dashboard.conditional-rendering.conditions.shared.delete-condition', 'Delete Condition')}
|
||||
name="trash-alt"
|
||||
onClick={() => model.onDelete()}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
+72
-90
@@ -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<DataConditionValue>;
|
||||
|
||||
export class ConditionalRenderingData extends ConditionalRenderingBase<ConditionalRenderingDataState> {
|
||||
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<Condition
|
||||
}
|
||||
|
||||
private _activationHandler() {
|
||||
let panelDataProviders: SceneDataProvider[] = [];
|
||||
const item = this.getConditionalLogicRoot().parent;
|
||||
if (item instanceof AutoGridItem) {
|
||||
const panelData = sceneGraph.getData(item.state.body);
|
||||
if (panelData) {
|
||||
panelDataProviders.push(panelData);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
if (panelData) {
|
||||
panelDataProviders.push(panelData);
|
||||
}
|
||||
}
|
||||
if (!this.isItemSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
panelDataProviders.forEach((d) => {
|
||||
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 <ConditionalRenderingDataRenderer model={this} />;
|
||||
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<ConditionalRenderingData>) {
|
||||
const { value } = model.useState();
|
||||
|
||||
const enableConditionOptions: Array<SelectableValue<true | false>> = useMemo(
|
||||
const enableConditionOptions: Array<ComboboxOption<1 | 0>> = 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 (
|
||||
<Stack direction="column">
|
||||
<ConditionHeader title={model.title} onDelete={() => model.onDelete()} />
|
||||
<RadioButtonGroup
|
||||
fullWidth
|
||||
options={enableConditionOptions}
|
||||
value={value}
|
||||
onChange={(value) => model.setStateAndNotify({ value: value })}
|
||||
/>
|
||||
</Stack>
|
||||
<Combobox
|
||||
options={enableConditionOptions}
|
||||
value={enableConditionOption}
|
||||
onChange={({ value }) => model.setStateAndNotify({ value: Boolean(value) })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+13
-2
@@ -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: () => (
|
||||
<Stack direction="row" gap={1} alignItems="center">
|
||||
<div>{title}</div>
|
||||
<Tooltip content={conditionalRendering.info}>
|
||||
<Icon name={!conditionalRendering.evaluate() ? 'eye-slash' : 'eye'} />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
),
|
||||
isOpenDefault: true,
|
||||
}).addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.conditional-rendering.title', 'Conditional rendering options'),
|
||||
title,
|
||||
render: () => <conditionalRendering.Component model={conditionalRendering} />,
|
||||
})
|
||||
);
|
||||
|
||||
+89
-115
@@ -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<GroupConditionValue> {
|
||||
condition: 'and' | 'or';
|
||||
visibility: GroupConditionVisibility;
|
||||
condition: GroupConditionCondition;
|
||||
}
|
||||
|
||||
export class ConditionalRenderingGroup extends ConditionalRenderingBase<ConditionalRenderingGroupState> {
|
||||
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<Conditio
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.state.condition === 'and') {
|
||||
return this.state.value.every((entry) => 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 <ConditionalRenderingGroupRenderer model={this} />;
|
||||
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<Conditio
|
||||
item.activate();
|
||||
}
|
||||
|
||||
this.getConditionalLogicRoot().notifyChange();
|
||||
this.notifyChange();
|
||||
}
|
||||
|
||||
public static createEmpty(): ConditionalRenderingGroup {
|
||||
return new ConditionalRenderingGroup({ condition: 'and', value: [] });
|
||||
}
|
||||
|
||||
public onDelete() {
|
||||
const rootGroup = this.getRootGroup();
|
||||
if (this === rootGroup) {
|
||||
this.getConditionalLogicRoot().setState({ rootGroup: ConditionalRenderingGroup.createEmpty() });
|
||||
} else {
|
||||
rootGroup.setState({ value: rootGroup.state.value.filter((condition) => 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<Conditio
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public static deserialize(model: ConditionalRenderingGroupKind): ConditionalRenderingGroup {
|
||||
return new ConditionalRenderingGroup({
|
||||
condition: model.spec.condition,
|
||||
visibility: model.spec.visibility,
|
||||
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.deserialize(item);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
public static createEmpty(): ConditionalRenderingGroup {
|
||||
return new ConditionalRenderingGroup({ condition: 'and', visibility: 'show', value: [] });
|
||||
}
|
||||
}
|
||||
|
||||
function ConditionalRenderingGroupRenderer({ model }: SceneComponentProps<ConditionalRenderingGroup>) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const { condition, value } = model.useState();
|
||||
|
||||
const conditionsOptions: Array<SelectableValue<'and' | 'or'>> = 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 (
|
||||
<Stack direction="column">
|
||||
<ConditionHeader title={model.title} onDelete={() => model.onDelete()} />
|
||||
<Field label={t('dashboard.conditional-rendering.group.condition.label', 'Evaluate conditions')}>
|
||||
<RadioButtonGroup
|
||||
fullWidth
|
||||
options={conditionsOptions}
|
||||
value={condition}
|
||||
onChange={(value) => model.changeCondition(value!)}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Divider spacing={1} />
|
||||
|
||||
{value.map((entry) => (
|
||||
<Fragment key={entry!.state.key}>
|
||||
{/* @ts-expect-error */}
|
||||
<entry.Component model={entry} />
|
||||
|
||||
<div className={styles.entryDivider}>
|
||||
<Divider spacing={1} />
|
||||
<p className={styles.entryDividerText}> {condition}</p>
|
||||
<Divider spacing={1} />
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
<div className={styles.addButtonContainer}>
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
label={t('dashboard.conditional-rendering.group.add.data', 'Data')}
|
||||
onClick={() => model.addItem(new ConditionalRenderingData({ value: true }))}
|
||||
/>
|
||||
<Menu.Item
|
||||
label={t('dashboard.conditional-rendering.group.add.interval', 'Interval')}
|
||||
onClick={() => model.addItem(new ConditionalRenderingInterval({ value: '7d' }))}
|
||||
/>
|
||||
<Menu.Item
|
||||
label={t('dashboard.conditional-rendering.group.add.variable', 'Variable value')}
|
||||
onClick={() =>
|
||||
model.addItem(new ConditionalRenderingVariable({ value: { name: '', operator: '=', value: '' } }))
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<ToolbarButton icon="plus" iconSize="xs" variant="canvas">
|
||||
<Trans i18nKey="dashboard.conditional-rendering.group.add.button">Add condition based on</Trans>
|
||||
</ToolbarButton>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<Stack direction="column" gap={2}>
|
||||
<ConditionalRenderingGroupVisibility
|
||||
itemType={model.getItemType()}
|
||||
value={visibility}
|
||||
onChange={(value) => model.changeVisibility(value)}
|
||||
/>
|
||||
{value.length > 1 && (
|
||||
<ConditionalRenderingGroupCondition value={condition} onChange={(value) => model.changeCondition(value)} />
|
||||
)}
|
||||
{value.map((entry) => entry.render())}
|
||||
<ConditionalRenderingGroupAdd
|
||||
itemType={model.getItemType()}
|
||||
hasVariables={variables.length > 0}
|
||||
onAdd={(itemType) => model.addItem(itemType)}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
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',
|
||||
}),
|
||||
});
|
||||
|
||||
+48
@@ -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<Array<SelectableValue<GroupConditionItemType>>>(() => {
|
||||
const allOptions: Array<SelectableValue<GroupConditionItemType>> = [
|
||||
{ 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 (
|
||||
<ValuePicker
|
||||
isFullWidth
|
||||
size="sm"
|
||||
icon="plus"
|
||||
variant="secondary"
|
||||
label={t('dashboard.conditional-rendering.conditions.group.add.button', 'Add rule')}
|
||||
options={options}
|
||||
onChange={({ value }) => onAdd(value!)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+40
@@ -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<SelectableValue<GroupConditionCondition>> = 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 (
|
||||
<Field
|
||||
label={t('dashboard.conditional-rendering.conditions.group.condition.label', 'Match rules')}
|
||||
className={styles.container}
|
||||
>
|
||||
<RadioButtonGroup fullWidth options={options} value={value} onChange={onChange} />
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = () => ({
|
||||
container: css({
|
||||
margin: 0,
|
||||
}),
|
||||
});
|
||||
+44
@@ -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<SelectableValue<GroupConditionVisibility>> = 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 (
|
||||
<Field
|
||||
label={t('dashboard.conditional-rendering.conditions.group.visibility.label', '{{type}} visibility', {
|
||||
type: capitalize(itemType),
|
||||
})}
|
||||
className={styles.container}
|
||||
>
|
||||
<RadioButtonGroup fullWidth options={options} value={value} onChange={onChange} />
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = () => ({
|
||||
container: css({
|
||||
margin: 0,
|
||||
}),
|
||||
});
|
||||
-93
@@ -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<IntervalConditionValue>;
|
||||
|
||||
export class ConditionalRenderingInterval extends ConditionalRenderingBase<ConditionalRenderingIntervalState> {
|
||||
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 <ConditionalRenderingIntervalRenderer model={this} />;
|
||||
}
|
||||
|
||||
public onDelete() {
|
||||
handleDeleteNonGroupCondition(this);
|
||||
}
|
||||
|
||||
public serialize(): ConditionalRenderingTimeIntervalKind {
|
||||
return {
|
||||
kind: 'ConditionalRenderingTimeInterval',
|
||||
spec: {
|
||||
value: this.state.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function ConditionalRenderingIntervalRenderer({ model }: SceneComponentProps<ConditionalRenderingInterval>) {
|
||||
const { value } = model.useState();
|
||||
const [isValid, setIsValid] = useState(validateIntervalRegex.test(value));
|
||||
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<ConditionHeader title={model.title} onDelete={() => model.onDelete()} />
|
||||
<Field
|
||||
invalid={!isValid}
|
||||
error={t('dashboard.conditional-rendering.interval.invalid-message', 'Invalid interval')}
|
||||
label={t('dashboard.conditional-rendering.interval.input-label', 'Value')}
|
||||
>
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
setIsValid(validateIntervalRegex.test(e.currentTarget.value));
|
||||
model.setStateAndNotify({ value: e.currentTarget.value });
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export const validateIntervalRegex = /^(-?\d+(?:\.\d+)?)(ms|[Mwdhmsy])?$/;
|
||||
+67
@@ -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 (
|
||||
<div className={styles.container}>
|
||||
<Tooltip
|
||||
content={t(
|
||||
'dashboard.conditional-rendering.overlay.tooltip',
|
||||
'Element is hidden due to conditional rendering.'
|
||||
)}
|
||||
>
|
||||
<Icon name="eye-slash" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
+219
@@ -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<TimeRangeSizeConditionValue>;
|
||||
|
||||
export class ConditionalRenderingTimeRangeSize extends ConditionalRenderingBase<ConditionalRenderingTimeRangeSizeState> {
|
||||
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<ConditionalRenderingTimeRangeSize>) {
|
||||
const { value } = model.useState();
|
||||
const [isValid, setIsValid] = useState(validateIntervalRegex.test(value));
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
useEffect(() => setIsValid(validateIntervalRegex.test(value)), [value]);
|
||||
|
||||
const staticOptions = useMemo<Array<SelectableValue<string>>>(
|
||||
() => [
|
||||
{
|
||||
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 (
|
||||
<Field
|
||||
invalid={!isValid}
|
||||
error={t('dashboard.conditional-rendering.conditions.time-range-size.invalid-message', 'Invalid interval')}
|
||||
className={styles.container}
|
||||
>
|
||||
<Select
|
||||
isClearable={false}
|
||||
allowCustomValue
|
||||
onCreateOption={(value) => model.setStateAndNotify({ value })}
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={({ value }) => model.setStateAndNotify({ value })}
|
||||
/>
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = () => ({
|
||||
container: css({
|
||||
margin: 0,
|
||||
}),
|
||||
});
|
||||
|
||||
const validateIntervalRegex = /^(\d+(?:\.\d+)?)[Mwdhmsy]$/;
|
||||
+76
-74
@@ -1,33 +1,44 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { SceneComponentProps, sceneGraph, VariableDependencyConfig } from '@grafana/scenes';
|
||||
import { ConditionalRenderingVariableKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
|
||||
import { Combobox, ComboboxOption, Field, Input, Stack, useStyles2 } from '@grafana/ui';
|
||||
import { Box, Combobox, ComboboxOption, 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 VariableConditionValue = {
|
||||
name: string;
|
||||
operator: '=' | '!=';
|
||||
value: string;
|
||||
};
|
||||
import {
|
||||
ConditionalRenderingSerializerRegistryItem,
|
||||
VariableConditionValue,
|
||||
VariableConditionValueOperator,
|
||||
} from './types';
|
||||
|
||||
type ConditionalRenderingVariableState = ConditionalRenderingBaseState<VariableConditionValue>;
|
||||
|
||||
export class ConditionalRenderingVariable extends ConditionalRenderingBase<ConditionalRenderingVariableState> {
|
||||
public static Component = ConditionalRenderingVariableRenderer;
|
||||
|
||||
public static serializer: ConditionalRenderingSerializerRegistryItem = {
|
||||
id: 'ConditionalRenderingVariable',
|
||||
name: 'Variable',
|
||||
deserialize: this.deserialize,
|
||||
};
|
||||
|
||||
public get title(): string {
|
||||
return t('dashboard.conditional-rendering.variable.label', 'Variable');
|
||||
return t('dashboard.conditional-rendering.conditions.variable.label', 'Template variable');
|
||||
}
|
||||
|
||||
public get info(): string {
|
||||
return t(
|
||||
'dashboard.conditional-rendering.conditions.variable.info',
|
||||
'Show or hide the {{type}} dynamically based on the variable value.',
|
||||
{ type: this.getItemType() }
|
||||
);
|
||||
}
|
||||
|
||||
protected _variableDependency = new VariableDependencyConfig(this, {
|
||||
onAnyVariableChanged: (v) => {
|
||||
if (v.state.name === this.state.value.name) {
|
||||
this.getConditionalLogicRoot().notifyChange();
|
||||
this.notifyChange();
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -37,25 +48,19 @@ export class ConditionalRenderingVariable extends ConditionalRenderingBase<Condi
|
||||
return true;
|
||||
}
|
||||
|
||||
const variable = sceneGraph.getVariables(this).state.variables.find((v) => v.state.name === this.state.value.name);
|
||||
const variable = sceneGraph.getVariables(this).getByName(this.state.value.name);
|
||||
|
||||
// name is defined but no variable found - return false
|
||||
if (!variable) {
|
||||
return false;
|
||||
}
|
||||
const value = variable.getValue();
|
||||
|
||||
let hit = Array.isArray(value) ? value.includes(this.state.value.value) : value === this.state.value.value;
|
||||
const variableValue = variable.getValue() ?? '';
|
||||
|
||||
if (this.state.value.operator === '!=') {
|
||||
hit = !hit;
|
||||
}
|
||||
const hit = Array.isArray(variableValue)
|
||||
? variableValue.includes(this.state.value.value)
|
||||
: variableValue === this.state.value.value;
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
return <ConditionalRenderingVariableRenderer model={this} />;
|
||||
return this.state.value.operator === '!=' ? !hit : hit;
|
||||
}
|
||||
|
||||
public serialize(): ConditionalRenderingVariableKind {
|
||||
@@ -69,70 +74,67 @@ export class ConditionalRenderingVariable extends ConditionalRenderingBase<Condi
|
||||
};
|
||||
}
|
||||
|
||||
public onDelete() {
|
||||
handleDeleteNonGroupCondition(this);
|
||||
public static deserialize(model: ConditionalRenderingVariableKind): ConditionalRenderingVariable {
|
||||
return new ConditionalRenderingVariable({
|
||||
value: {
|
||||
name: model.spec.variable,
|
||||
operator: model.spec.operator === 'equals' ? '=' : '!=',
|
||||
value: model.spec.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public static createEmpty(name: string): ConditionalRenderingVariable {
|
||||
return new ConditionalRenderingVariable({ value: { name, operator: '=', value: '' } });
|
||||
}
|
||||
}
|
||||
|
||||
function ConditionalRenderingVariableRenderer({ model }: SceneComponentProps<ConditionalRenderingVariable>) {
|
||||
const { value } = model.useState();
|
||||
|
||||
const variables = useMemo(() => sceneGraph.getVariables(model), [model]);
|
||||
const variableNames = useMemo(
|
||||
|
||||
const variableNames: Array<ComboboxOption<string>> = useMemo(
|
||||
() => variables.state.variables.map((v) => ({ value: v.state.name, label: v.state.label ?? v.state.name })),
|
||||
[variables.state.variables]
|
||||
);
|
||||
const operatorOptions: Array<ComboboxOption<'=' | '!='>> = useMemo(
|
||||
|
||||
const operatorOptions: Array<ComboboxOption<VariableConditionValueOperator>> = useMemo(
|
||||
() => [
|
||||
{ value: '=', description: t('dashboard.conditional-rendering.variable.operator.equals', 'Equals') },
|
||||
{ value: '!=', description: t('dashboard.conditional-rendering.variable.operator.not-equal', 'Not equal') },
|
||||
{ value: '=', description: t('dashboard.conditional-rendering.conditions.variable.operator.equals', 'Equals') },
|
||||
{
|
||||
value: '!=',
|
||||
description: t('dashboard.conditional-rendering.conditions.variable.operator.not-equals', 'Not equals'),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
const { value } = model.useState();
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<ConditionHeader title={model.title} onDelete={() => model.onDelete()} />
|
||||
<Stack direction="column">
|
||||
<Stack direction="row" gap={0.5} grow={1}>
|
||||
<Field
|
||||
label={t('dashboard.conditional-rendering.variable.select-variable', 'Select variable')}
|
||||
className={styles.variableNameSelect}
|
||||
>
|
||||
<Combobox
|
||||
options={variableNames}
|
||||
value={value.name}
|
||||
onChange={(option) => model.setStateAndNotify({ value: { ...value, name: option.value } })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label={t('dashboard.conditional-rendering.variable.select-operator', 'Operator')}
|
||||
className={styles.operatorSelect}
|
||||
>
|
||||
<Combobox
|
||||
options={operatorOptions}
|
||||
value={value.operator}
|
||||
onChange={(option) => model.setStateAndNotify({ value: { ...value, operator: option.value } })}
|
||||
/>
|
||||
</Field>
|
||||
</Stack>
|
||||
<Field label={t('dashboard.conditional-rendering.variable.value-input', 'Value')}>
|
||||
<Input
|
||||
value={value.value}
|
||||
onChange={(e) => model.setStateAndNotify({ value: { ...value, value: e.currentTarget.value } })}
|
||||
<Stack direction="column" gap={0.5}>
|
||||
<Stack direction="row" gap={0.5} grow={1}>
|
||||
<Box flex={1}>
|
||||
<Combobox
|
||||
placeholder={t('dashboard.conditional-rendering.conditions.variable.name', 'Name')}
|
||||
options={variableNames}
|
||||
value={value.name}
|
||||
onChange={(option) => model.setStateAndNotify({ value: { ...value, name: option.value } })}
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
|
||||
<Combobox
|
||||
width="auto"
|
||||
minWidth={10}
|
||||
options={operatorOptions}
|
||||
value={value.operator}
|
||||
onChange={(option) => model.setStateAndNotify({ value: { ...value, operator: option.value } })}
|
||||
/>
|
||||
</Stack>
|
||||
<Input
|
||||
placeholder={t('dashboard.conditional-rendering.conditions.variable.value', 'Value')}
|
||||
value={value.value}
|
||||
onChange={(e) => model.setStateAndNotify({ value: { ...value, value: e.currentTarget.value } })}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
variableNameSelect: css({
|
||||
flexGrow: 1,
|
||||
}),
|
||||
operatorSelect: css({
|
||||
width: theme.spacing(12),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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<ConditionalRenderingSerializerRegistryItem>(() => {
|
||||
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,
|
||||
];
|
||||
});
|
||||
|
||||
@@ -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<ConditionalRenderingConditions, ConditionalRenderingGroup>;
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
+22
@@ -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 : <ConditionalRenderingOverlay />,
|
||||
];
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
@@ -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<DashboardEditPaneState> {
|
||||
this.forceRender();
|
||||
})
|
||||
);
|
||||
|
||||
this._subs.add(
|
||||
dashboard.subscribeToEvent(ConditionalRenderingChangedEvent, ({ payload }) => {
|
||||
this.forceRender();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public enableSelection() {
|
||||
|
||||
@@ -66,3 +66,7 @@ export class ObjectRemovedFromCanvasEvent extends BusEventWithPayload<SceneObjec
|
||||
export class ObjectsReorderedOnCanvasEvent extends BusEventWithPayload<SceneObject> {
|
||||
static type = 'objects-reordered-on-canvas';
|
||||
}
|
||||
|
||||
export class ConditionalRenderingChangedEvent extends BusEventWithPayload<SceneObject> {
|
||||
static type = 'conditional-rendering-changed';
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+8
-13
@@ -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<AutoGridItem
|
||||
const { body, repeatedPanels, key } = model.useState();
|
||||
const { draggingKey } = model.getParentGrid().useState();
|
||||
const { isEditing } = useDashboardState(model);
|
||||
const isConditionallyHidden = useIsConditionallyHidden(model);
|
||||
const [isConditionallyHidden, conditionalRenderingClass, conditionalRenderingOverlay] =
|
||||
useIsConditionallyHidden(model);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (isConditionallyHidden && !isEditing) {
|
||||
@@ -26,11 +28,9 @@ export function AutoGridItemRenderer({ model }: SceneComponentProps<AutoGridItem
|
||||
return repeatedPanels ? (
|
||||
<>
|
||||
{repeatedPanels.map((item) => (
|
||||
<div
|
||||
className={cx(isConditionallyHidden && 'dashboard-visible-hidden-element', styles.wrapper)}
|
||||
key={item.state.key}
|
||||
>
|
||||
<div className={cx(conditionalRenderingClass, styles.wrapper)} key={item.state.key}>
|
||||
<item.Component model={item} />
|
||||
{conditionalRenderingOverlay}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
@@ -38,14 +38,9 @@ export function AutoGridItemRenderer({ model }: SceneComponentProps<AutoGridItem
|
||||
<div ref={model.containerRef} data-auto-grid-item-drop-target={isDragging ? key : undefined}>
|
||||
{isDragged && <div className={styles.draggedPlaceholder} />}
|
||||
|
||||
<div
|
||||
className={cx(
|
||||
isConditionallyHidden && 'dashboard-visible-hidden-element',
|
||||
styles.wrapper,
|
||||
isDragged && styles.draggedWrapper
|
||||
)}
|
||||
>
|
||||
<div className={cx(!isDragged && conditionalRenderingClass, styles.wrapper, isDragged && styles.draggedWrapper)}>
|
||||
<body.Component model={body} />
|
||||
{conditionalRenderingOverlay}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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<RowItem>) {
|
||||
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<RowItem>) {
|
||||
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<RowItem>) {
|
||||
>
|
||||
{(!isHeaderHidden || isEditing) && (
|
||||
<div
|
||||
className={cx(
|
||||
isHeaderHidden && 'dashboard-visible-hidden-element',
|
||||
styles.rowHeader,
|
||||
'dashboard-row-header'
|
||||
)}
|
||||
className={cx(styles.rowHeader, 'dashboard-row-header')}
|
||||
onMouseEnter={isSelectable ? onHeaderEnter : undefined}
|
||||
onMouseLeave={isSelectable ? onHeaderLeave : undefined}
|
||||
{...dragProvided.dragHandleProps}
|
||||
@@ -129,6 +128,7 @@ export function RowItemRenderer({ model }: SceneComponentProps<RowItem>) {
|
||||
</div>
|
||||
)}
|
||||
{!isCollapsed && <layout.Component model={layout} />}
|
||||
{conditionalRenderingOverlay}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
@@ -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',
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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<TabItem>) {
|
||||
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 (
|
||||
<Draggable key={key!} draggableId={key!} index={myIndex} isDragDisabled={!isEditing}>
|
||||
@@ -39,6 +45,7 @@ export function TabItemRenderer({ model }: SceneComponentProps<TabItem>) {
|
||||
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,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
+11
-1
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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<T extends SceneObjectState & { title?: string }>(scene: SceneObject<T>): string {
|
||||
const { title } = scene.useState();
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user