From 18c4f5b875436928e6fa7172997819c569b909ce Mon Sep 17 00:00:00 2001 From: grafakus Date: Tue, 25 Nov 2025 12:06:47 +0100 Subject: [PATCH] feat: Update dynamic dashboards editors --- .../components/CustomVariableForm.tsx | 73 +++++++----- .../components/VariableValuesPreview.tsx | 13 ++- .../CustomVariableEditor.tsx | 7 +- .../CustomVariableEditor/ModalEditor.tsx | 84 +++++++++++-- .../getCustomVariableOptions.tsx | 2 - .../variables/editors/QueryVariableEditor.tsx | 110 ++++++++++++------ .../useVariableSelectionOptionsCategory.tsx | 9 +- 7 files changed, 212 insertions(+), 86 deletions(-) diff --git a/public/app/features/dashboard-scene/settings/variables/components/CustomVariableForm.tsx b/public/app/features/dashboard-scene/settings/variables/components/CustomVariableForm.tsx index 84f89df1c6a..b4ea09d190a 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/CustomVariableForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/CustomVariableForm.tsx @@ -48,27 +48,7 @@ export function CustomVariableForm({ Custom options - - - {valuesFormat === 'json' && ( - - - - )} - + void; +} + +export function ValuesFormatSelector({ valuesFormat, onValuesFormatChange }: ValuesFormatSelectorProps) { return ( - - Provide a JSON representing an array of objects, where each object can have any number of properties. -
- Check{' '} - - our docs - {' '} - for more information. -
+ + + {valuesFormat === 'json' && ( + + Provide a JSON representing an array of objects, where each object can have any number of properties. +
+ Check{' '} + + our docs + {' '} + for more information. + + } + placement="top" + interactive + > + +
+ )} +
); } diff --git a/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx b/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx index 41a9d5c4fc3..f575363c956 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx @@ -36,7 +36,13 @@ function VariableValuesWithPropsPreview({ options }: { options: VariableValueOpt Preview of values - String(r.value)} pageSize={10} /> + String(r.value)} + pageSize={8} + /> ); } @@ -97,5 +103,10 @@ function getStyles(theme: GrafanaTheme2) { textOverflow: 'ellipsis', maxWidth: '50vw', }), + table: css({ + td: css({ + padding: theme.spacing(0.5, 1), + }), + }), }; } diff --git a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/CustomVariableEditor.tsx b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/CustomVariableEditor.tsx index 6c3774407f1..5c1f3158443 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/CustomVariableEditor.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/CustomVariableEditor.tsx @@ -23,6 +23,7 @@ export function CustomVariableEditor({ variable, onRunQuery }: CustomVariableEdi const onValuesFormatChange = useCallback( (format: CustomVariableModel['valuesFormat']) => { variable.setState({ query: prevQuery }); + variable.setState({ value: isMulti ? [] : undefined }); variable.setState({ valuesFormat: format }); variable.setState({ allowCustomValue: false }); variable.setState({ allValue: undefined }); @@ -33,7 +34,7 @@ export function CustomVariableEditor({ variable, onRunQuery }: CustomVariableEdi setPrevQuery(query); } }, - [onRunQuery, prevQuery, query, variable] + [isMulti, onRunQuery, prevQuery, query, variable] ); const onMultiChange = useCallback( @@ -52,6 +53,8 @@ export function CustomVariableEditor({ variable, onRunQuery }: CustomVariableEdi const onQueryChange = useCallback( (event: FormEvent) => { + setPrevQuery(''); + if (valuesFormat === 'json') { const validationError = validateJsonQuery(event.currentTarget.value); setQueryValidationError(validationError); @@ -113,7 +116,7 @@ export function getCustomVariableOptions(variable: SceneVariable): OptionsPaneIt ]; } -const validateJsonQuery = (rawQuery: string): Error | undefined => { +export const validateJsonQuery = (rawQuery: string): Error | undefined => { const query = rawQuery.trim(); if (!query) { return; diff --git a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ModalEditor.tsx b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ModalEditor.tsx index 3e8a8aa57b1..fa6dd46680a 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ModalEditor.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ModalEditor.tsx @@ -1,15 +1,16 @@ -import { useCallback, useRef } from 'react'; +import { FormEvent, useCallback, useState } from 'react'; +import { lastValueFrom } from 'rxjs'; +import { CustomVariableModel } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { t, Trans } from '@grafana/i18n'; import { CustomVariable } from '@grafana/scenes'; -import { Button, Modal, Stack } from '@grafana/ui'; +import { Button, FieldValidationMessage, Modal, Stack, TextArea } from '@grafana/ui'; -import { VariableStaticOptionsFormRef } from '../../components/VariableStaticOptionsForm'; -import { VariableStaticOptionsFormAddButton } from '../../components/VariableStaticOptionsFormAddButton'; +import { ValuesFormatSelector } from '../../components/CustomVariableForm'; +import { VariableValuesPreview } from '../../components/VariableValuesPreview'; -import { ValuesBuilder } from './ValuesBuilder'; -import { ValuesPreview } from './ValuesPreview'; +import { validateJsonQuery } from './CustomVariableEditor'; interface ModalEditorProps { variable: CustomVariable; @@ -18,9 +19,49 @@ interface ModalEditorProps { } export function ModalEditor({ variable, isOpen, onClose }: ModalEditorProps) { - const formRef = useRef(null); + const { query, valuesFormat, isMulti } = variable.useState(); + const [prevQuery, setPrevQuery] = useState(''); + const [queryValidationError, setQueryValidationError] = useState(); - const handleOnAdd = useCallback(() => formRef.current?.addItem(), []); + const onValuesFormatChange = useCallback( + async (format: CustomVariableModel['valuesFormat']) => { + variable.setState({ query: prevQuery }); + variable.setState({ value: isMulti ? [] : undefined }); + variable.setState({ valuesFormat: format }); + variable.setState({ allowCustomValue: false }); + variable.setState({ allValue: undefined }); + + await lastValueFrom(variable.validateAndUpdate()); + + setQueryValidationError(undefined); + if (query !== prevQuery) { + setPrevQuery(query); + } + }, + [isMulti, prevQuery, query, variable] + ); + + const onQueryChange = useCallback( + async (event: FormEvent) => { + setPrevQuery(''); + + if (valuesFormat === 'json') { + const validationError = validateJsonQuery(event.currentTarget.value); + setQueryValidationError(validationError); + if (validationError) { + return; + } + } + + variable.setState({ query: event.currentTarget.value }); + await lastValueFrom(variable.validateAndUpdate()); + }, + [valuesFormat, variable] + ); + + const optionsForSelect = variable.getOptionsForSelect(false); + const hasJsonValuesFormat = variable.state.valuesFormat === 'json'; + const hasMultiProps = hasJsonValuesFormat || optionsForSelect.every((o) => Boolean(o.properties)); return ( - - + +
+