Dashboard: Improvements to conditional rendering (#108108)
This commit is contained in:
@@ -941,7 +941,7 @@ ConditionalRenderingVariableKind: {
|
||||
|
||||
ConditionalRenderingVariableSpec: {
|
||||
variable: string
|
||||
operator: "equals" | "notEquals"
|
||||
operator: "equals" | "notEquals" | "matches" | "notMatches"
|
||||
value: string
|
||||
}
|
||||
|
||||
|
||||
@@ -942,7 +942,7 @@ ConditionalRenderingVariableKind: {
|
||||
|
||||
ConditionalRenderingVariableSpec: {
|
||||
variable: string
|
||||
operator: "equals" | "notEquals"
|
||||
operator: "equals" | "notEquals" | "matches" | "notMatches"
|
||||
value: string
|
||||
}
|
||||
|
||||
|
||||
@@ -945,7 +945,7 @@ ConditionalRenderingVariableKind: {
|
||||
|
||||
ConditionalRenderingVariableSpec: {
|
||||
variable: string
|
||||
operator: "equals" | "notEquals"
|
||||
operator: "equals" | "notEquals" | "matches" | "notMatches"
|
||||
value: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1859,8 +1859,10 @@ const (
|
||||
type DashboardConditionalRenderingVariableSpecOperator string
|
||||
|
||||
const (
|
||||
DashboardConditionalRenderingVariableSpecOperatorEquals DashboardConditionalRenderingVariableSpecOperator = "equals"
|
||||
DashboardConditionalRenderingVariableSpecOperatorNotEquals DashboardConditionalRenderingVariableSpecOperator = "notEquals"
|
||||
DashboardConditionalRenderingVariableSpecOperatorEquals DashboardConditionalRenderingVariableSpecOperator = "equals"
|
||||
DashboardConditionalRenderingVariableSpecOperatorNotEquals DashboardConditionalRenderingVariableSpecOperator = "notEquals"
|
||||
DashboardConditionalRenderingVariableSpecOperatorMatches DashboardConditionalRenderingVariableSpecOperator = "matches"
|
||||
DashboardConditionalRenderingVariableSpecOperatorNotMatches DashboardConditionalRenderingVariableSpecOperator = "notMatches"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
@@ -946,7 +946,7 @@ ConditionalRenderingVariableKind: {
|
||||
|
||||
ConditionalRenderingVariableSpec: {
|
||||
variable: string
|
||||
operator: "equals" | "notEquals"
|
||||
operator: "equals" | "notEquals" | "matches" | "notMatches"
|
||||
value: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1884,8 +1884,10 @@ const (
|
||||
type DashboardConditionalRenderingVariableSpecOperator string
|
||||
|
||||
const (
|
||||
DashboardConditionalRenderingVariableSpecOperatorEquals DashboardConditionalRenderingVariableSpecOperator = "equals"
|
||||
DashboardConditionalRenderingVariableSpecOperatorNotEquals DashboardConditionalRenderingVariableSpecOperator = "notEquals"
|
||||
DashboardConditionalRenderingVariableSpecOperatorEquals DashboardConditionalRenderingVariableSpecOperator = "equals"
|
||||
DashboardConditionalRenderingVariableSpecOperatorNotEquals DashboardConditionalRenderingVariableSpecOperator = "notEquals"
|
||||
DashboardConditionalRenderingVariableSpecOperatorMatches DashboardConditionalRenderingVariableSpecOperator = "matches"
|
||||
DashboardConditionalRenderingVariableSpecOperatorNotMatches DashboardConditionalRenderingVariableSpecOperator = "notMatches"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
@@ -934,7 +934,7 @@ ConditionalRenderingVariableKind: {
|
||||
|
||||
ConditionalRenderingVariableSpec: {
|
||||
variable: string
|
||||
operator: "equals" | "notEquals"
|
||||
operator: "equals" | "notEquals" | "matches" | "notMatches"
|
||||
value: string
|
||||
}
|
||||
|
||||
|
||||
@@ -700,7 +700,7 @@ export const defaultConditionalRenderingVariableKind = (): ConditionalRenderingV
|
||||
|
||||
export interface ConditionalRenderingVariableSpec {
|
||||
variable: string;
|
||||
operator: "equals" | "notEquals";
|
||||
operator: "equals" | "notEquals" | "matches" | "notMatches";
|
||||
value: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -699,7 +699,7 @@ export const defaultConditionalRenderingVariableKind = (): ConditionalRenderingV
|
||||
|
||||
export interface ConditionalRenderingVariableSpec {
|
||||
variable: string;
|
||||
operator: "equals" | "notEquals";
|
||||
operator: "equals" | "notEquals" | "matches" | "notMatches";
|
||||
value: string;
|
||||
}
|
||||
|
||||
|
||||
+10
-8
@@ -70,15 +70,17 @@ export class ConditionalRenderingGroup extends ConditionalRenderingBase<Conditio
|
||||
this.setStateAndNotify({ condition });
|
||||
}
|
||||
|
||||
public createItem(itemType: GroupConditionItemType) {
|
||||
const item =
|
||||
itemType === 'data'
|
||||
? ConditionalRenderingData.createEmpty()
|
||||
: itemType === 'variable'
|
||||
? ConditionalRenderingVariable.createEmpty(sceneGraph.getVariables(this).state.variables[0].state.name)
|
||||
: ConditionalRenderingTimeRangeSize.createEmpty();
|
||||
public createItem(itemType: GroupConditionItemType): ConditionalRenderingConditions {
|
||||
switch (itemType) {
|
||||
case 'data':
|
||||
return ConditionalRenderingData.createEmpty();
|
||||
|
||||
return item;
|
||||
case 'timeRangeSize':
|
||||
return ConditionalRenderingTimeRangeSize.createEmpty();
|
||||
|
||||
case 'variable':
|
||||
return ConditionalRenderingVariable.createEmpty(sceneGraph.getVariables(this).state.variables[0].state.name);
|
||||
}
|
||||
}
|
||||
|
||||
public addItem(item: ConditionalRenderingConditions) {
|
||||
|
||||
+125
-36
@@ -1,9 +1,12 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
import { SceneComponentProps, sceneGraph, VariableDependencyConfig } from '@grafana/scenes';
|
||||
import { ConditionalRenderingVariableKind } from '@grafana/schema/dist/esm/schema/dashboard/v2';
|
||||
import { Box, Combobox, ComboboxOption, Input, Stack } from '@grafana/ui';
|
||||
import {
|
||||
ConditionalRenderingVariableKind,
|
||||
ConditionalRenderingVariableSpec,
|
||||
} from '@grafana/schema/dist/esm/schema/dashboard/v2';
|
||||
import { Box, Combobox, ComboboxOption, Field, Input, Stack } from '@grafana/ui';
|
||||
|
||||
import { dashboardEditActions } from '../edit-pane/shared';
|
||||
|
||||
@@ -54,16 +57,29 @@ export class ConditionalRenderingVariable extends ConditionalRenderingBase<Condi
|
||||
const variable = sceneGraph.getVariables(this).getByName(this.state.value.name);
|
||||
|
||||
if (!variable) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const variableValue = variable.getValue() ?? '';
|
||||
|
||||
const hit = Array.isArray(variableValue)
|
||||
? variableValue.includes(this.state.value.value)
|
||||
: variableValue === this.state.value.value;
|
||||
let hit: boolean;
|
||||
|
||||
return this.state.value.operator === '!=' ? !hit : hit;
|
||||
if (this.state.value.operator === '=' || this.state.value.operator === '!=') {
|
||||
hit = Array.isArray(variableValue)
|
||||
? variableValue.includes(this.state.value.value.toString())
|
||||
: variableValue === this.state.value.value.toString();
|
||||
} else {
|
||||
try {
|
||||
const regex = new RegExp(this.state.value.value);
|
||||
hit = Array.isArray(variableValue)
|
||||
? variableValue.some((currentVariableValue) => regex.test(currentVariableValue.toString()))
|
||||
: regex.test(variableValue.toString());
|
||||
} catch (err) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return this.state.value.operator === '!=' || this.state.value.operator === '!~' ? !hit : hit;
|
||||
}
|
||||
|
||||
public serialize(): ConditionalRenderingVariableKind {
|
||||
@@ -71,7 +87,7 @@ export class ConditionalRenderingVariable extends ConditionalRenderingBase<Condi
|
||||
kind: 'ConditionalRenderingVariable',
|
||||
spec: {
|
||||
variable: this.state.value.name,
|
||||
operator: this.state.value.operator === '=' ? 'equals' : 'notEquals',
|
||||
operator: this._getLongOperator(this.state.value.operator),
|
||||
value: this.state.value.value,
|
||||
},
|
||||
};
|
||||
@@ -81,7 +97,7 @@ export class ConditionalRenderingVariable extends ConditionalRenderingBase<Condi
|
||||
return new ConditionalRenderingVariable({
|
||||
value: {
|
||||
name: model.spec.variable,
|
||||
operator: model.spec.operator === 'equals' ? '=' : '!=',
|
||||
operator: ConditionalRenderingVariable._getShortOperator(model.spec.operator),
|
||||
value: model.spec.value,
|
||||
},
|
||||
});
|
||||
@@ -90,14 +106,54 @@ export class ConditionalRenderingVariable extends ConditionalRenderingBase<Condi
|
||||
public static createEmpty(name: string): ConditionalRenderingVariable {
|
||||
return new ConditionalRenderingVariable({ value: { name, operator: '=', value: '' } });
|
||||
}
|
||||
|
||||
private _getLongOperator(operator: VariableConditionValueOperator): ConditionalRenderingVariableSpec['operator'] {
|
||||
switch (operator) {
|
||||
case '=':
|
||||
return 'equals';
|
||||
|
||||
case '!=':
|
||||
return 'notEquals';
|
||||
|
||||
case '=~':
|
||||
return 'matches';
|
||||
|
||||
case '!~':
|
||||
return 'notMatches';
|
||||
}
|
||||
}
|
||||
|
||||
private static _getShortOperator(
|
||||
operator: ConditionalRenderingVariableSpec['operator']
|
||||
): VariableConditionValueOperator {
|
||||
switch (operator) {
|
||||
case 'equals':
|
||||
return '=';
|
||||
|
||||
case 'notEquals':
|
||||
return '!=';
|
||||
|
||||
case 'matches':
|
||||
return '=~';
|
||||
|
||||
case 'notMatches':
|
||||
return '!~';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ConditionalRenderingVariableRenderer({ model }: SceneComponentProps<ConditionalRenderingVariable>) {
|
||||
const { value } = model.useState();
|
||||
|
||||
const [actualValue, setActualValue] = useState(value.value);
|
||||
|
||||
useEffect(() => {
|
||||
setActualValue(value.value);
|
||||
}, [value.value]);
|
||||
|
||||
const variables = useMemo(() => sceneGraph.getVariables(model), [model]);
|
||||
|
||||
const variableNames: Array<ComboboxOption<string>> = useMemo(
|
||||
const variableNames: ComboboxOption[] = useMemo(
|
||||
() => variables.state.variables.map((v) => ({ value: v.state.name, label: v.state.label ?? v.state.name })),
|
||||
[variables.state.variables]
|
||||
);
|
||||
@@ -109,10 +165,31 @@ function ConditionalRenderingVariableRenderer({ model }: SceneComponentProps<Con
|
||||
value: '!=',
|
||||
description: t('dashboard.conditional-rendering.conditions.variable.operator.not-equals', 'Not equals'),
|
||||
},
|
||||
{
|
||||
value: '=~',
|
||||
description: t('dashboard.conditional-rendering.conditions.variable.operator.matches', 'Matches'),
|
||||
},
|
||||
{
|
||||
value: '!~',
|
||||
description: t('dashboard.conditional-rendering.conditions.variable.operator.not-matches', 'Not matches'),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const valueError = useMemo(() => {
|
||||
if (value.operator === '=~' || value.operator === '!~') {
|
||||
try {
|
||||
new RegExp(actualValue);
|
||||
return '';
|
||||
} catch (err) {
|
||||
return t('dashboard.conditional-rendering.conditions.variable.error.invalid-regex', 'Invalid regex');
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}, [actualValue, value.operator]);
|
||||
|
||||
const undoText = t('dashboard.edit-actions.edit-template-variable-rule', 'Change template variable rule');
|
||||
|
||||
return (
|
||||
@@ -124,12 +201,14 @@ function ConditionalRenderingVariableRenderer({ model }: SceneComponentProps<Con
|
||||
options={variableNames}
|
||||
value={value.name}
|
||||
onChange={(option) => {
|
||||
dashboardEditActions.edit({
|
||||
description: undoText,
|
||||
source: model,
|
||||
perform: () => model.setStateAndNotify({ value: { ...value, name: option.value } }),
|
||||
undo: () => model.setStateAndNotify({ value: { ...value, name: value.name } }),
|
||||
});
|
||||
if (option.value !== value.name) {
|
||||
dashboardEditActions.edit({
|
||||
description: undoText,
|
||||
source: model,
|
||||
perform: () => model.setStateAndNotify({ value: { ...value, name: option.value } }),
|
||||
undo: () => model.setStateAndNotify({ value: { ...value, name: value.name } }),
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@@ -140,28 +219,38 @@ function ConditionalRenderingVariableRenderer({ model }: SceneComponentProps<Con
|
||||
options={operatorOptions}
|
||||
value={value.operator}
|
||||
onChange={(option) => {
|
||||
dashboardEditActions.edit({
|
||||
description: undoText,
|
||||
source: model,
|
||||
perform: () => model.setStateAndNotify({ value: { ...value, operator: option.value } }),
|
||||
undo: () => model.setStateAndNotify({ value: { ...value, operator: value.operator } }),
|
||||
});
|
||||
if (option.value !== value.operator) {
|
||||
dashboardEditActions.edit({
|
||||
description: undoText,
|
||||
source: model,
|
||||
perform: () => model.setStateAndNotify({ value: { ...value, operator: option.value } }),
|
||||
undo: () => model.setStateAndNotify({ value: { ...value, operator: value.operator } }),
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Input
|
||||
placeholder={t('dashboard.conditional-rendering.conditions.variable.value', 'Value')}
|
||||
value={value.value}
|
||||
onChange={(e) => {
|
||||
const eventValue = e.currentTarget.value;
|
||||
dashboardEditActions.edit({
|
||||
description: undoText,
|
||||
source: model,
|
||||
perform: () => model.setStateAndNotify({ value: { ...value, value: eventValue } }),
|
||||
undo: () => model.setStateAndNotify({ value: { ...value, value: value.value } }),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Field error={valueError} invalid={!!valueError} noMargin>
|
||||
<Input
|
||||
placeholder={t('dashboard.conditional-rendering.conditions.variable.value', 'Value')}
|
||||
value={actualValue}
|
||||
onChange={(evt) => {
|
||||
if (evt.currentTarget.value !== value.value) {
|
||||
setActualValue(evt.currentTarget.value);
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (actualValue !== value.value) {
|
||||
dashboardEditActions.edit({
|
||||
description: undoText,
|
||||
source: model,
|
||||
perform: () => model.setStateAndNotify({ value: { ...value, value: actualValue } }),
|
||||
undo: () => model.setStateAndNotify({ value: { ...value, value: value.value } }),
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export type GroupConditionValue = ConditionalRenderingConditions[];
|
||||
|
||||
export type TimeRangeSizeConditionValue = string;
|
||||
|
||||
export type VariableConditionValueOperator = '=' | '!=';
|
||||
export type VariableConditionValueOperator = '=' | '!=' | '=~' | '!~';
|
||||
|
||||
export type VariableConditionValue = {
|
||||
name: string;
|
||||
|
||||
@@ -22,13 +22,6 @@ export function AutoGridItemRenderer({ model }: SceneComponentProps<AutoGridItem
|
||||
|
||||
const isLazy = useMemo(() => getIsLazy(preload), [preload]);
|
||||
|
||||
if (isConditionallyHidden && !isEditing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isDragging = !!draggingKey;
|
||||
const isDragged = draggingKey === key;
|
||||
|
||||
const Wrapper = useMemo(
|
||||
() =>
|
||||
memo(
|
||||
@@ -69,6 +62,13 @@ export function AutoGridItemRenderer({ model }: SceneComponentProps<AutoGridItem
|
||||
[conditionalRenderingClass, conditionalRenderingOverlay, isLazy, key, model.containerRef, styles]
|
||||
);
|
||||
|
||||
if (isConditionallyHidden && !isEditing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isDragging = !!draggingKey;
|
||||
const isDragged = draggingKey === key;
|
||||
|
||||
return repeatedPanels ? (
|
||||
<>
|
||||
{repeatedPanels.map((item, index) => (
|
||||
|
||||
@@ -4497,12 +4497,17 @@
|
||||
}
|
||||
},
|
||||
"variable": {
|
||||
"error": {
|
||||
"invalid-regex": "Invalid regex"
|
||||
},
|
||||
"info": "Show or hide the {{type}} dynamically based on the variable value.",
|
||||
"label": "Template variable",
|
||||
"name": "Name",
|
||||
"operator": {
|
||||
"equals": "Equals",
|
||||
"not-equals": "Not equals"
|
||||
"matches": "Matches",
|
||||
"not-equals": "Not equals",
|
||||
"not-matches": "Not matches"
|
||||
},
|
||||
"value": "Value"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user