Dashboard: Variable selection and editing (#103451)
* Variable selection * Update * fix * Update * Updates * update * Update
This commit is contained in:
@@ -41,7 +41,7 @@ describe('Variables - Query - Add variable', () => {
|
||||
expect(input.attr('placeholder')).equals('Descriptive text');
|
||||
expect(input.val()).equals('');
|
||||
});
|
||||
cy.get('label').contains('Show on dashboard').should('be.visible');
|
||||
cy.get('label').contains('Hide').should('be.visible');
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsDataSourceSelect()
|
||||
.get('input[placeholder="gdev-testdata"]')
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('Variables - Query - Add variable', () => {
|
||||
expect(input.attr('placeholder')).equals('Descriptive text');
|
||||
expect(input.val()).equals('');
|
||||
});
|
||||
cy.get('label').contains('Show on dashboard').should('be.visible');
|
||||
cy.get('label').contains('Hide').should('be.visible');
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsDataSourceSelect()
|
||||
.get('input[placeholder="gdev-testdata"]')
|
||||
|
||||
+2
-2
@@ -275,8 +275,8 @@
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/saga-icons": "workspace:*",
|
||||
"@grafana/scenes": "6.6.3",
|
||||
"@grafana/scenes-react": "6.6.3",
|
||||
"@grafana/scenes": "^6.7.0",
|
||||
"@grafana/scenes-react": "^6.7.0",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useSessionStorage } from 'react-use';
|
||||
|
||||
import { BusEventWithPayload } from '@grafana/data';
|
||||
import { SceneGridRow, SceneObject, VizPanel } from '@grafana/scenes';
|
||||
import { SceneGridRow, SceneObject, SceneVariable, VizPanel } from '@grafana/scenes';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { SceneGridRowEditableElement } from '../scene/layout-default/SceneGridRowEditableElement';
|
||||
import { EditableDashboardElement, isEditableDashboardElement } from '../scene/types/EditableDashboardElement';
|
||||
import { VariableEditableElement } from '../settings/variables/VariableEditableElement';
|
||||
|
||||
import { DashboardEditableElement } from './DashboardEditableElement';
|
||||
import { VizPanelEditableElement } from './VizPanelEditableElement';
|
||||
@@ -35,6 +36,10 @@ export function getEditableElementFor(sceneObj: SceneObject | undefined): Editab
|
||||
return new DashboardEditableElement(sceneObj);
|
||||
}
|
||||
|
||||
if (isSceneVariable(sceneObj)) {
|
||||
return new VariableEditableElement(sceneObj);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -55,6 +60,10 @@ export function hasEditableElement(sceneObj: SceneObject | undefined): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isSceneVariable(sceneObj: SceneObject): sceneObj is SceneVariable {
|
||||
return 'getValue' in sceneObj;
|
||||
}
|
||||
|
||||
export class NewObjectAddedToCanvasEvent extends BusEventWithPayload<SceneObject> {
|
||||
static type = 'new-object-added-to-canvas';
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
|
||||
import { VariableHide } from '@grafana/data';
|
||||
import { VariableHide, GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { sceneGraph, useSceneObjectState, SceneVariable, SceneVariableState, ControlsLabel } from '@grafana/scenes';
|
||||
import { useElementSelection, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
|
||||
@@ -24,20 +25,37 @@ interface VariableSelectProps {
|
||||
|
||||
export function VariableValueSelectWrapper({ variable }: VariableSelectProps) {
|
||||
const state = useSceneObjectState<SceneVariableState>(variable, { shouldActivateOrKeepAlive: true });
|
||||
const { isSelected, onSelect, isSelectable } = useElementSelection(variable.state.key);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (state.hide === VariableHide.hideVariable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onPointerDown = (evt: React.PointerEvent) => {
|
||||
if (isSelectable && onSelect) {
|
||||
evt.stopPropagation();
|
||||
onSelect(evt);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={containerStyle} data-testid={selectors.pages.Dashboard.SubMenu.submenuItem}>
|
||||
<VariableLabel variable={variable} />
|
||||
<div
|
||||
className={cx(
|
||||
styles.container,
|
||||
isSelected && 'dashboard-selected-element',
|
||||
isSelectable && !isSelected && 'dashboard-selectable-element'
|
||||
)}
|
||||
onPointerDown={onPointerDown}
|
||||
data-testid={selectors.pages.Dashboard.SubMenu.submenuItem}
|
||||
>
|
||||
<VariableLabel variable={variable} className={cx(isSelectable && styles.labelSelectable)} />
|
||||
<variable.Component model={variable} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VariableLabel({ variable }: VariableSelectProps) {
|
||||
function VariableLabel({ variable, className }: { variable: SceneVariable; className?: string }) {
|
||||
const { state } = variable;
|
||||
|
||||
if (variable.state.hide === VariableHide.hideLabel) {
|
||||
@@ -56,15 +74,25 @@ function VariableLabel({ variable }: VariableSelectProps) {
|
||||
error={state.error}
|
||||
layout={'horizontal'}
|
||||
description={state.description ?? undefined}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const containerStyle = css({
|
||||
display: 'flex',
|
||||
// No border for second element (inputs) as label and input border is shared
|
||||
'> :nth-child(2)': css({
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
container: css({
|
||||
display: 'flex',
|
||||
// No border for second element (inputs) as label and input border is shared
|
||||
'> :nth-child(2)': css({
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
}),
|
||||
}),
|
||||
labelWrapper: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}),
|
||||
labelSelectable: css({
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { VariableHide } from '@grafana/data';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { SceneVariable, SceneVariableSet } from '@grafana/scenes';
|
||||
import { Combobox, Input, TextArea, Stack, Button } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
|
||||
import { ObjectRemovedFromCanvasEvent } from '../../edit-pane/shared';
|
||||
import { BulkActionElement } from '../../scene/types/BulkActionElement';
|
||||
import { EditableDashboardElement, EditableDashboardElementInfo } from '../../scene/types/EditableDashboardElement';
|
||||
import { VariableHideSelect } from '../../settings/variables/components/VariableHideSelect';
|
||||
import { getVariableTypeSelectOptions } from '../../settings/variables/utils';
|
||||
|
||||
export class VariableEditableElement implements EditableDashboardElement, BulkActionElement {
|
||||
public readonly isEditableDashboardElement = true;
|
||||
public readonly typeName = 'Variable';
|
||||
|
||||
public constructor(public variable: SceneVariable) {}
|
||||
|
||||
public getEditableElementInfo(): EditableDashboardElementInfo {
|
||||
return {
|
||||
typeName: t('dashboard.edit-pane.elements.variable', 'Variable'),
|
||||
icon: 'chart-line',
|
||||
instanceName: this.variable.state.name,
|
||||
};
|
||||
}
|
||||
|
||||
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
|
||||
const variable = this.variable;
|
||||
|
||||
const options = useMemo(() => {
|
||||
return new OptionsPaneCategoryDescriptor({ title: '', id: 'panel-options' })
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard-scene.variable-editor-form.name', 'Name'),
|
||||
popularRank: 1,
|
||||
render: () => <VariableNameInput variable={variable} />,
|
||||
})
|
||||
)
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard-scene.variable-editor-form.label', 'Label'),
|
||||
description: t(
|
||||
'dashboard-scene.variable-editor-form.description-optional-display-name',
|
||||
'Optional display name'
|
||||
),
|
||||
render: () => <VariableLabelInput variable={variable} />,
|
||||
})
|
||||
)
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard-scene.variable-editor-form.description', 'Description'),
|
||||
render: () => <VariableDescriptionTextArea variable={variable} />,
|
||||
})
|
||||
)
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: '',
|
||||
skipField: true,
|
||||
render: () => <VariableHideInput variable={variable} />,
|
||||
})
|
||||
)
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard-scene.variable-editor-form.type', 'Type'),
|
||||
render: () => <VariableTypeSelect variable={variable} />,
|
||||
})
|
||||
);
|
||||
}, [variable]);
|
||||
|
||||
return [options];
|
||||
}
|
||||
|
||||
public onDelete() {
|
||||
const set = this.variable.parent!;
|
||||
if (set instanceof SceneVariableSet) {
|
||||
this.variable.publishEvent(new ObjectRemovedFromCanvasEvent(this.variable), true);
|
||||
set.setState({ variables: set.state.variables.filter((v) => v !== this.variable) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface VariableInputProps {
|
||||
variable: SceneVariable;
|
||||
}
|
||||
|
||||
function VariableNameInput({ variable }: VariableInputProps) {
|
||||
const { name } = variable.useState();
|
||||
return <Input value={name} onChange={(e) => variable.setState({ name: e.currentTarget.value })} />;
|
||||
}
|
||||
|
||||
function VariableLabelInput({ variable }: VariableInputProps) {
|
||||
const { label } = variable.useState();
|
||||
return <Input value={label} onChange={(e) => variable.setState({ label: e.currentTarget.value })} />;
|
||||
}
|
||||
|
||||
function VariableDescriptionTextArea({ variable }: VariableInputProps) {
|
||||
const { description } = variable.useState();
|
||||
|
||||
return (
|
||||
<TextArea
|
||||
id="description-text-area"
|
||||
value={description ?? ''}
|
||||
placeholder={t('dashboard-scene.variable-editor-form.placeholder-descriptive-text', 'Descriptive text')}
|
||||
onChange={(e) => variable.setState({ description: e.currentTarget.value })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function VariableHideInput({ variable }: VariableInputProps) {
|
||||
const { hide = VariableHide.dontHide } = variable.useState();
|
||||
|
||||
const onChange = (option: VariableHide) => {
|
||||
variable.setState({ hide: option });
|
||||
};
|
||||
|
||||
return <VariableHideSelect hide={hide} type={variable.state.type} onChange={onChange} />;
|
||||
}
|
||||
|
||||
function VariableTypeSelect({ variable }: VariableInputProps) {
|
||||
const options = useMemo(() => getVariableTypeSelectOptions().map((o) => ({ value: o.value!, label: o.label })), []);
|
||||
|
||||
const onOpenVariableEdior = () => {
|
||||
const set = variable.parent!;
|
||||
if (!(set instanceof SceneVariableSet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const variableIndex = set.state.variables.indexOf(variable);
|
||||
locationService.partial({ editview: 'variables', editIndex: variableIndex });
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap={2} direction={'column'}>
|
||||
<Combobox value={variable.state.type} options={options} disabled={true} onChange={() => {}} />
|
||||
<Button
|
||||
tooltip={t(
|
||||
'dashboard-scene.variable-editor-form.open-editor-tooltip',
|
||||
'For more variable options open variable editor'
|
||||
)}
|
||||
onClick={onOpenVariableEdior}
|
||||
fullWidth
|
||||
>
|
||||
<Trans i18nKey="dashboard-scene.variable-editor-form.open-editor">Open variable editor</Trans>
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
+4
-4
@@ -11,9 +11,9 @@ interface Props {
|
||||
}
|
||||
|
||||
const HIDE_OPTIONS = [
|
||||
{ label: 'Label and value', value: VariableHide.dontHide },
|
||||
{ label: 'Value', value: VariableHide.hideLabel },
|
||||
{ label: 'Nothing', value: VariableHide.hideVariable },
|
||||
{ label: 'Nothing', value: VariableHide.dontHide },
|
||||
{ label: 'Variable', value: VariableHide.hideVariable },
|
||||
{ label: 'Label', value: VariableHide.hideLabel },
|
||||
];
|
||||
|
||||
export function VariableHideSelect({ onChange, hide, type }: PropsWithChildren<Props>) {
|
||||
@@ -24,7 +24,7 @@ export function VariableHideSelect({ onChange, hide, type }: PropsWithChildren<P
|
||||
}
|
||||
|
||||
return (
|
||||
<Field label={t('dashboard-scene.variable-hide-select.label-show-on-dashboard', 'Show on dashboard')}>
|
||||
<Field label={t('dashboard-scene.variable-hide-select.label', 'Hide')}>
|
||||
<RadioButtonGroup options={HIDE_OPTIONS} onChange={onChange} value={value} />
|
||||
</Field>
|
||||
);
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ export function VariableTypeSelect({ onChange, type }: PropsWithChildren<Props>)
|
||||
|
||||
return (
|
||||
<VariableSelectField
|
||||
name="Select variable type"
|
||||
name="Variable type"
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -2781,7 +2781,8 @@
|
||||
"row": "Row",
|
||||
"rows": "Rows",
|
||||
"tab": "Tab",
|
||||
"tabs": "Tabs"
|
||||
"tabs": "Tabs",
|
||||
"variable": "Variable"
|
||||
},
|
||||
"open": "Open options pane",
|
||||
"row": {
|
||||
@@ -3622,13 +3623,19 @@
|
||||
"aria-label-variable-editor-form": "Variable editor form",
|
||||
"back-to-list": "Back to list",
|
||||
"delete": "Delete",
|
||||
"description": "Description",
|
||||
"description-optional-display-name": "Optional display name",
|
||||
"description-template-variable-characters": "The name of the template variable. (Max. 50 characters)",
|
||||
"general": "General",
|
||||
"label": "Label",
|
||||
"name": "Name",
|
||||
"open-editor": "Open variable editor",
|
||||
"open-editor-tooltip": "For more variable options open variable editor",
|
||||
"placeholder-descriptive-text": "Descriptive text",
|
||||
"placeholder-label-name": "Label name",
|
||||
"placeholder-variable-name": "Variable name",
|
||||
"text-running-query": "Running query..."
|
||||
"text-running-query": "Running query...",
|
||||
"type": "Type"
|
||||
},
|
||||
"variable-editor-list": {
|
||||
"definition": "Definition",
|
||||
@@ -3641,7 +3648,7 @@
|
||||
"tooltip-remove-variable": "Remove variable"
|
||||
},
|
||||
"variable-hide-select": {
|
||||
"label-show-on-dashboard": "Show on dashboard"
|
||||
"label": "Hide"
|
||||
},
|
||||
"variable-usages-button": {
|
||||
"title-show-usages": "Showing usages for: {{variableId}}",
|
||||
|
||||
@@ -3466,11 +3466,11 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/scenes-react@npm:6.6.3":
|
||||
version: 6.6.3
|
||||
resolution: "@grafana/scenes-react@npm:6.6.3"
|
||||
"@grafana/scenes-react@npm:^6.7.0":
|
||||
version: 6.7.0
|
||||
resolution: "@grafana/scenes-react@npm:6.7.0"
|
||||
dependencies:
|
||||
"@grafana/scenes": "npm:6.6.3"
|
||||
"@grafana/scenes": "npm:6.7.0"
|
||||
lru-cache: "npm:^10.2.2"
|
||||
react-use: "npm:^17.4.0"
|
||||
peerDependencies:
|
||||
@@ -3482,13 +3482,13 @@ __metadata:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-router-dom: ^6.28.0
|
||||
checksum: 10/65d28b1a2d1213a40fd0f9fbfac8f6fe209c7fdf6160f6df14aff15746efe010baa931f7732c467bcbd8d7574185aa75e778b5151a18cc6415e64994489f7636
|
||||
checksum: 10/3561d1d0a9a2c07d8e5b560ff8cc03de017d29c73f1451f03f0d56a0cfb71e71bfe1c1e657840d9afb2fe5ed7e101532cbc843322b1d9a54ee2870a05acf77fb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/scenes@npm:6.6.3":
|
||||
version: 6.6.3
|
||||
resolution: "@grafana/scenes@npm:6.6.3"
|
||||
"@grafana/scenes@npm:6.7.0, @grafana/scenes@npm:^6.7.0":
|
||||
version: 6.7.0
|
||||
resolution: "@grafana/scenes@npm:6.7.0"
|
||||
dependencies:
|
||||
"@floating-ui/react": "npm:^0.26.16"
|
||||
"@leeoniya/ufuzzy": "npm:^1.0.16"
|
||||
@@ -3506,7 +3506,7 @@ __metadata:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-router-dom: ^6.28.0
|
||||
checksum: 10/f2ef34be526a6f59591ea496a514b734591778c870491bc09d175a3bf29845c5691e6e1ddfb2e4622c9139db8edfd3943bec7095cfc5804f01d6ed5584b06760
|
||||
checksum: 10/5b0a7f49b11d88a10c67f697c7951d730d6e3bf2d7ca6354134ccf099009feaa269ac7aa559147c3565a925935db2cf5c9d645c0e194d9cd02a99566d7b7299a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -17642,8 +17642,8 @@ __metadata:
|
||||
"@grafana/prometheus": "workspace:*"
|
||||
"@grafana/runtime": "workspace:*"
|
||||
"@grafana/saga-icons": "workspace:*"
|
||||
"@grafana/scenes": "npm:6.6.3"
|
||||
"@grafana/scenes-react": "npm:6.6.3"
|
||||
"@grafana/scenes": "npm:^6.7.0"
|
||||
"@grafana/scenes-react": "npm:^6.7.0"
|
||||
"@grafana/schema": "workspace:*"
|
||||
"@grafana/sql": "workspace:*"
|
||||
"@grafana/tsconfig": "npm:^2.0.0"
|
||||
|
||||
Reference in New Issue
Block a user