feat: WiP

This commit is contained in:
grafakus
2025-11-13 14:08:00 +01:00
parent df816d00e4
commit 198f4dbf93
11 changed files with 280 additions and 79 deletions
@@ -113,7 +113,7 @@ DashboardLink: {
placement?: DashboardLinkPlacement
}
// Dashboard Link placement. Defines where the link should be displayed.
// Dashboard Link placement. Defines where the link should be displayed.
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
DashboardLinkPlacement: "inControlsMenu"
@@ -376,7 +376,7 @@ FetchOptions: {
url: string
body?: string
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: [...[...string]]
headers?: [...[...string]]
@@ -386,7 +386,7 @@ InfinityOptions: FetchOptions & {
datasourceUid: string
}
HttpRequestMethod: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"
HttpRequestMethod: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"
// Action variable type
ActionVariableType: "string"
@@ -903,6 +903,7 @@ CustomVariableSpec: {
skipUrlSync: bool | *false
description?: string
allowCustomValue: bool | *true
valuesFormat?: string
}
// Custom variable kind
@@ -101,6 +101,7 @@ export interface IntervalVariableModel extends VariableWithOptions {
export interface CustomVariableModel extends VariableWithMultiSupport {
type: 'custom';
valuesFormat?: 'csv' | 'json';
}
export interface DataSourceVariableModel extends VariableWithMultiSupport {
@@ -111,6 +111,7 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio
allValue: variable.state.allValue,
includeAll: variable.state.includeAll,
allowCustomValue: variable.state.allowCustomValue,
valuesFormat: variable.state.valuesFormat,
});
} else if (sceneUtils.isDataSourceVariable(variable)) {
variables.push({
@@ -393,6 +394,7 @@ export function sceneVariablesSetToSchemaV2Variables(
allValue: variable.state.allValue,
includeAll: variable.state.includeAll ?? false,
allowCustomValue: variable.state.allowCustomValue ?? true,
valuesFormat: variable.state.valuesFormat,
},
};
variables.push(customVariable);
@@ -321,12 +321,12 @@ function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): S
supportsMultiValueOperators: Boolean(getDataSourceSrv().getInstanceSettings(ds)?.meta.multiValueFilterOperators),
});
}
if (variable.kind === defaultCustomVariableKind().kind) {
return new CustomVariable({
...commonProperties,
value: variable.spec.current?.value ?? '',
text: variable.spec.current?.text ?? '',
query: variable.spec.query,
isMulti: variable.spec.multi,
allValue: variable.spec.allValue || undefined,
@@ -334,6 +334,10 @@ function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): S
defaultToAll: Boolean(variable.spec.includeAll),
skipUrlSync: variable.spec.skipUrlSync,
hide: transformVariableHideToEnumV1(variable.spec.hide),
valuesFormat:
variable.spec.valuesFormat === 'csv' || variable.spec.valuesFormat === 'json'
? variable.spec.valuesFormat
: undefined,
});
} else if (variable.kind === defaultQueryVariableKind().kind) {
return new QueryVariable({
@@ -9,7 +9,7 @@ import { Trans, t } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { SceneVariable } from '@grafana/scenes';
import { VariableHide, defaultVariableModel } from '@grafana/schema';
import { Button, LoadingPlaceholder, ConfirmModal, ModalsController, Stack, useStyles2 } from '@grafana/ui';
import { Button, ConfirmModal, LoadingPlaceholder, ModalsController, Stack, useStyles2 } from '@grafana/ui';
import { VariableHideSelect } from 'app/features/dashboard-scene/settings/variables/components/VariableHideSelect';
import { VariableLegend } from 'app/features/dashboard-scene/settings/variables/components/VariableLegend';
import { VariableTextAreaField } from 'app/features/dashboard-scene/settings/variables/components/VariableTextAreaField';
@@ -123,7 +123,7 @@ export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDelete
{EditorToRender && <EditorToRender variable={variable} onRunQuery={onRunQuery} />}
{isHasVariableOptions && <VariableValuesPreview options={variable.getOptionsForSelect(false)} />}
{isHasVariableOptions && <VariableValuesPreview variable={variable} />}
<div className={styles.buttonContainer}>
<Stack gap={2}>
@@ -1,7 +1,10 @@
import { FormEvent } from 'react';
import { isObject } from 'lodash';
import { FormEvent, useState } from 'react';
import { CustomVariableModel, shallowCompare } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { FieldValidationMessage, Icon, RadioButtonGroup, Stack, TextLink, Tooltip } from '@grafana/ui';
import { SelectionOptionsForm } from './SelectionOptionsForm';
import { VariableLegend } from './VariableLegend';
@@ -9,6 +12,7 @@ import { VariableTextAreaField } from './VariableTextAreaField';
interface CustomVariableFormProps {
query: string;
valuesFormat?: CustomVariableModel['valuesFormat'];
multi: boolean;
allValue?: string | null;
includeAll: boolean;
@@ -20,10 +24,12 @@ interface CustomVariableFormProps {
onQueryBlur?: (event: FormEvent<HTMLTextAreaElement>) => void;
onAllValueBlur?: (event: FormEvent<HTMLInputElement>) => void;
onAllowCustomValueChange?: (event: FormEvent<HTMLInputElement>) => void;
onValuesFormatChange?: (format: CustomVariableModel['valuesFormat']) => void;
}
export function CustomVariableForm({
query,
valuesFormat,
multi,
allValue,
includeAll,
@@ -33,23 +39,70 @@ export function CustomVariableForm({
onIncludeAllChange,
onAllValueChange,
onAllowCustomValueChange,
onValuesFormatChange,
}: CustomVariableFormProps) {
const [validationError, setValidationError] = useState<Error>();
const onChangeFormat = (newFormat: CustomVariableModel['valuesFormat']) => {
onValuesFormatChange?.(newFormat);
setValidationError(undefined);
};
const onQueryBlur = (e: FormEvent<HTMLTextAreaElement>) => {
if (valuesFormat === 'json') {
setValidationError(validateJsonQuery(e.currentTarget.value));
}
onQueryChange(e);
};
return (
<>
<VariableLegend>
<Trans i18nKey="dashboard-scene.custom-variable-form.custom-options">Custom options</Trans>
</VariableLegend>
<Stack direction="row" gap={1}>
<RadioButtonGroup
value={valuesFormat}
onChange={onChangeFormat}
options={[
{
value: 'csv',
label: t('dashboard-scene.custom-variable-form.name-values-separated-comma', 'Values separated by comma'),
},
{
value: 'json',
// TODO: add translation
label: t('dashboard-scene.custom-variable-form.name-json-values', 'Object values in a JSON array'),
},
]}
/>
{valuesFormat === 'json' && (
<Tooltip content={TooltipJsonFormat} placement="top" interactive>
<Icon name="info-circle" />
</Tooltip>
)}
</Stack>
<VariableTextAreaField
name={t('dashboard-scene.custom-variable-form.name-values-separated-comma', 'Values separated by comma')}
// we don't use a controlled component so we make sure the textarea content is cleared when changing format by providing a key
key={valuesFormat}
name=""
placeholder={
valuesFormat === 'json'
? // eslint-disable-next-line @grafana/i18n/no-untranslated-strings
'[{ "text": "text1", "propA": "a1", "propB": "b1" },\n{ "text": "text2", "propA": "a2", "propB": "b2" }]'
: // eslint-disable-next-line @grafana/i18n/no-untranslated-strings
'1, 10, mykey : myvalue, myvalue, escaped\,value'
}
defaultValue={query}
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
placeholder="1, 10, mykey : myvalue, myvalue, escaped\,value"
onBlur={onQueryChange}
onBlur={onQueryBlur}
required
width={52}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput}
/>
{validationError && <FieldValidationMessage>{validationError.message}</FieldValidationMessage>}
<VariableLegend>
<Trans i18nKey="dashboard-scene.custom-variable-form.selection-options">Selection options</Trans>
</VariableLegend>
@@ -58,6 +111,8 @@ export function CustomVariableForm({
includeAll={includeAll}
allValue={allValue}
allowCustomValue={allowCustomValue}
disableAllowCustomValue={valuesFormat === 'json'}
disableCustomAllValue={valuesFormat === 'json'}
onMultiChange={onMultiChange}
onIncludeAllChange={onIncludeAllChange}
onAllValueChange={onAllValueChange}
@@ -66,3 +121,57 @@ export function CustomVariableForm({
</>
);
}
function TooltipJsonFormat() {
return (
// TODO: add translation
<Trans i18nKey="">
Provide a JSON representing an array of objects, where each object can have any number of properties.
<br />
Check{' '}
<TextLink href="https://grafana.com/docs/grafana/latest/variables/xxx" external>
our docs
</TextLink>{' '}
for more information.
</Trans>
);
}
const validateJsonQuery = (rawQuwey: string): Error | undefined => {
const query = rawQuwey.trim();
if (!query) {
return;
}
try {
const options = JSON.parse(query);
if (!Array.isArray(options)) {
throw new Error('Invalid JSON array!');
}
if (!options.length) {
return;
}
const keys = Object.keys(options[0]);
if (!keys.length || !keys.some((k) => k === 'value')) {
throw new Error('The objects in the array must have at least a "value" key!');
}
for (let i = 0; i < options.length; i += 1) {
if (!isObject(options[i])) {
throw new Error(`All items in the array must be objects. Item at index=${i} is incorrect!`);
}
if (!shallowCompare(keys, Object.keys(options[i]))) {
throw new Error(`All objects in the array must have the same keys. Object at index=${i} is incorrect!`);
}
}
return;
} catch (error) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return error as Error;
}
};
@@ -1,4 +1,4 @@
import { FormEvent } from 'react';
import { FormEvent, useState } from 'react';
import { useAsync } from 'react-use';
import { DataSourceInstanceSettings, SelectableValue, TimeRange } from '@grafana/data';
@@ -7,7 +7,7 @@ import { Trans, t } from '@grafana/i18n';
import { getDataSourceSrv } from '@grafana/runtime';
import { QueryVariable } from '@grafana/scenes';
import { DataSourceRef, VariableRefresh, VariableSort } from '@grafana/schema';
import { Field, TextLink } from '@grafana/ui';
import { Box, Field, Switch, TextLink } from '@grafana/ui';
import { QueryEditor } from 'app/features/dashboard-scene/settings/variables/components/QueryEditor';
import { SelectionOptionsForm } from 'app/features/dashboard-scene/settings/variables/components/SelectionOptionsForm';
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
@@ -15,9 +15,9 @@ import { getVariableQueryEditor } from 'app/features/variables/editor/getVariabl
import { QueryVariableRefreshSelect } from 'app/features/variables/query/QueryVariableRefreshSelect';
import { QueryVariableSortSelect } from 'app/features/variables/query/QueryVariableSortSelect';
import {
QueryVariableStaticOptions,
StaticOptionsOrderType,
StaticOptionsType,
QueryVariableStaticOptions,
} from 'app/features/variables/query/QueryVariableStaticOptions';
import { VariableLegend } from './VariableLegend';
@@ -94,6 +94,16 @@ export function QueryVariableEditorForm({
const { datasource, VariableQueryEditor } = dsConfig ?? {};
// TODO: remove me after finished testing - each DS can/should implement their own UI
const [returnsMultiProps, setReturnsMultiProps] = useState(false);
const onChangeReturnsMultipleProps = (e: FormEvent<HTMLInputElement>) => {
setReturnsMultiProps(e.currentTarget.checked);
onAllowCustomValueChange?.({ currentTarget: { checked: false } });
onAllValueChange({ currentTarget: { value: '' } });
onRegExChange({ currentTarget: { value: '' } });
onStaticOptionsChange?.([]);
};
return (
<>
<VariableLegend>
@@ -102,48 +112,70 @@ export function QueryVariableEditorForm({
<Field
label={t('dashboard-scene.query-variable-editor-form.label-data-source', 'Data source')}
htmlFor="data-source-picker"
noMargin
>
<DataSourcePicker current={datasourceRef} onChange={onDataSourceChange} variables={true} width={30} />
</Field>
{datasource && VariableQueryEditor && (
<QueryEditor
onQueryChange={onQueryChange}
onLegacyQueryChange={onLegacyQueryChange}
datasource={datasource}
query={query}
VariableQueryEditor={VariableQueryEditor}
timeRange={timeRange}
/>
<Box marginBottom={2}>
<QueryEditor
onQueryChange={onQueryChange}
onLegacyQueryChange={onLegacyQueryChange}
datasource={datasource}
query={query}
VariableQueryEditor={VariableQueryEditor}
timeRange={timeRange}
/>
<Field
// TODO: add translation
label="Enable access to all the fields of the query results"
description={
// TODO: add translation
<Trans i18nKey="">
Check{' '}
<TextLink href="https://grafana.com/docs/grafana/latest/variables/xxx" external>
our docs
</TextLink>{' '}
for more information.
</Trans>
}
noMargin
>
<Switch onChange={onChangeReturnsMultipleProps} />
</Field>
</Box>
)}
<VariableTextAreaField
defaultValue={regex ?? ''}
name={t('dashboard-scene.query-variable-editor-form.name-regex', 'Regex')}
description={
<div>
<Trans i18nKey="dashboard-scene.query-variable-editor-form.description-optional">
Optional, if you want to extract part of a series name or metric node segment.
</Trans>
<br />
<Trans i18nKey="dashboard-scene.query-variable-editor-form.description-examples">
Named capture groups can be used to separate the display text and value (
<TextLink
href="https://grafana.com/docs/grafana/latest/variables/filter-variables-with-regex#filter-and-modify-using-named-text-and-value-capture-groups"
external
>
see examples
</TextLink>
).
</Trans>
</div>
}
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/"
onBlur={onRegExChange}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2}
width={52}
/>
{!returnsMultiProps && (
<VariableTextAreaField
defaultValue={regex ?? ''}
name={t('dashboard-scene.query-variable-editor-form.name-regex', 'Regex')}
description={
<div>
<Trans i18nKey="dashboard-scene.query-variable-editor-form.description-optional">
Optional, if you want to extract part of a series name or metric node segment.
</Trans>
<br />
<Trans i18nKey="dashboard-scene.query-variable-editor-form.description-examples">
Named capture groups can be used to separate the display text and value (
<TextLink
href="https://grafana.com/docs/grafana/latest/variables/filter-variables-with-regex#filter-and-modify-using-named-text-and-value-capture-groups"
external
>
see examples
</TextLink>
).
</Trans>
</div>
}
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/"
onBlur={onRegExChange}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2}
width={52}
/>
)}
<QueryVariableSortSelect
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelectV2}
@@ -157,7 +189,7 @@ export function QueryVariableEditorForm({
refresh={refresh}
/>
{onStaticOptionsChange && onStaticOptionsOrderChange && (
{!returnsMultiProps && onStaticOptionsChange && onStaticOptionsOrderChange && (
<QueryVariableStaticOptions
staticOptions={staticOptions}
staticOptionsOrder={staticOptionsOrder}
@@ -173,6 +205,8 @@ export function QueryVariableEditorForm({
multi={!!isMulti}
includeAll={!!includeAll}
allowCustomValue={allowCustomValue}
disableAllowCustomValue={returnsMultiProps}
disableCustomAllValue={returnsMultiProps}
allValue={allValue}
onMultiChange={onMultiChange}
onIncludeAllChange={onIncludeAllChange}
@@ -10,7 +10,9 @@ interface SelectionOptionsFormProps {
multi: boolean;
includeAll: boolean;
allowCustomValue?: boolean;
disableAllowCustomValue?: boolean;
allValue?: string | null;
disableCustomAllValue?: boolean;
onMultiChange: (event: ChangeEvent<HTMLInputElement>) => void;
onAllowCustomValueChange?: (event: ChangeEvent<HTMLInputElement>) => void;
onIncludeAllChange: (event: ChangeEvent<HTMLInputElement>) => void;
@@ -20,8 +22,10 @@ interface SelectionOptionsFormProps {
export function SelectionOptionsForm({
multi,
allowCustomValue,
disableAllowCustomValue,
includeAll,
allValue,
disableCustomAllValue,
onMultiChange,
onAllowCustomValueChange,
onIncludeAllChange,
@@ -39,18 +43,19 @@ export function SelectionOptionsForm({
onChange={onMultiChange}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch}
/>
{onAllowCustomValueChange && ( // backwards compat with old arch, remove on cleanup
<VariableCheckboxField
value={allowCustomValue ?? true}
name={t('dashboard-scene.selection-options-form.name-allow-custom-values', 'Allow custom values')}
description={t(
'dashboard-scene.selection-options-form.description-enables-users-custom-values',
'Enables users to add custom values to the list'
)}
onChange={onAllowCustomValueChange}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsAllowCustomValueSwitch}
/>
)}
{!disableAllowCustomValue &&
onAllowCustomValueChange && ( // backwards compat with old arch, remove on cleanup
<VariableCheckboxField
value={allowCustomValue ?? true}
name={t('dashboard-scene.selection-options-form.name-allow-custom-values', 'Allow custom values')}
description={t(
'dashboard-scene.selection-options-form.description-enables-users-custom-values',
'Enables users to add custom values to the list'
)}
onChange={onAllowCustomValueChange}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsAllowCustomValueSwitch}
/>
)}
<VariableCheckboxField
value={includeAll}
name={t('dashboard-scene.selection-options-form.name-include-all-option', 'Include All option')}
@@ -61,7 +66,7 @@ export function SelectionOptionsForm({
onChange={onIncludeAllChange}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch}
/>
{includeAll && (
{!disableCustomAllValue && includeAll && (
<VariableTextField
defaultValue={allValue ?? ''}
onBlur={onAllValueChange}
@@ -4,14 +4,44 @@ import { MouseEvent, useCallback, useEffect, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Trans } from '@grafana/i18n';
import { VariableValueOption } from '@grafana/scenes';
import { Button, InlineFieldRow, InlineLabel, useStyles2, Text } from '@grafana/ui';
import { MultiValueVariable, VariableValueOption } from '@grafana/scenes';
import { Button, InlineFieldRow, InlineLabel, InteractiveTable, Text, useStyles2 } from '@grafana/ui';
export interface VariableValuesPreviewProps {
options: VariableValueOption[];
export interface Props {
variable: MultiValueVariable;
}
export const VariableValuesPreview = ({ options }: VariableValuesPreviewProps) => {
export const VariableValuesPreview = ({ variable }: Props) => {
const options = variable.getOptionsForSelect(false);
if (!options.length) {
return null;
}
if ('valuesFormat' in variable.state && variable.state.valuesFormat === 'json') {
return <VariableValuesWithPropsPreview options={options} />;
}
return <VariableValuesWithoutPropsPreview options={options} />;
};
VariableValuesPreview.displayName = 'VariableValuesPreview';
function VariableValuesWithPropsPreview({ options }: { options: VariableValueOption[] }) {
const styles = useStyles2(getStyles);
const data = options.map((o) => ({ label: String(o.label), value: String(o.value), ...o.properties }));
const columns = Object.keys(data[0]).map((id) => ({ id, header: id, sortType: 'alphanumeric' as const }));
return (
<div className={styles.previewContainer} style={{ gap: '8px' }}>
<Text variant="bodySmall" weight="medium">
<Trans i18nKey="dashboard-scene.variable-values-preview.preview-of-values">Preview of values</Trans>
</Text>
<InteractiveTable columns={columns} data={data} getRowId={(r) => String(r.value)} pageSize={2} />
</div>
);
}
function VariableValuesWithoutPropsPreview({ options }: { options: VariableValueOption[] }) {
const styles = useStyles2(getStyles);
const [previewLimit, setPreviewLimit] = useState(20);
const [previewOptions, setPreviewOptions] = useState<VariableValueOption[]>([]);
const showMoreOptions = useCallback(
@@ -21,15 +51,10 @@ export const VariableValuesPreview = ({ options }: VariableValuesPreviewProps) =
},
[previewLimit, setPreviewLimit]
);
const styles = useStyles2(getStyles);
useEffect(() => setPreviewOptions(options.slice(0, previewLimit)), [previewLimit, options]);
if (!previewOptions.length) {
return null;
}
return (
<div style={{ display: 'flex', flexDirection: 'column', marginTop: '16px' }}>
<div className={styles.previewContainer}>
<Text variant="bodySmall" weight="medium">
<Trans i18nKey="dashboard-scene.variable-values-preview.preview-of-values">Preview of values</Trans>
</Text>
@@ -51,12 +76,12 @@ export const VariableValuesPreview = ({ options }: VariableValuesPreviewProps) =
)}
</div>
);
};
VariableValuesPreview.displayName = 'VariableValuesPreview';
}
VariableValuesWithoutPropsPreview.displayName = 'VariableValuesWithoutPropsPreview';
function getStyles(theme: GrafanaTheme2) {
return {
wrapper: css({
previewContainer: css({
display: 'flex',
flexDirection: 'column',
marginTop: theme.spacing(2),
@@ -1,5 +1,6 @@
import { FormEvent, useCallback } from 'react';
import { FormEvent, useCallback, useState } from 'react';
import { CustomVariableModel } from '@grafana/data';
import { t } from '@grafana/i18n';
import { CustomVariable, SceneVariable } from '@grafana/scenes';
@@ -14,7 +15,23 @@ interface CustomVariableEditorProps {
}
export function CustomVariableEditor({ variable, onRunQuery }: CustomVariableEditorProps) {
const { query, isMulti, allValue, includeAll, allowCustomValue } = variable.useState();
const { query, valuesFormat, isMulti, allValue, includeAll, allowCustomValue } = variable.useState();
const [prevQuery, setPrevQuery] = useState('');
const onValuesFormatChange = useCallback(
(format: CustomVariableModel['valuesFormat']) => {
variable.setState({ valuesFormat: format });
variable.setState({ allowCustomValue: false });
variable.setState({ allValue: undefined });
variable.setState({ query: prevQuery });
if (query !== prevQuery) {
setPrevQuery(query);
}
onRunQuery();
},
[onRunQuery, prevQuery, query, variable]
);
const onMultiChange = useCallback(
(event: FormEvent<HTMLInputElement>) => {
@@ -55,6 +72,7 @@ export function CustomVariableEditor({ variable, onRunQuery }: CustomVariableEdi
return (
<CustomVariableForm
query={query ?? ''}
valuesFormat={valuesFormat ?? 'csv'}
multi={!!isMulti}
allValue={allValue ?? ''}
includeAll={!!includeAll}
@@ -64,6 +82,7 @@ export function CustomVariableEditor({ variable, onRunQuery }: CustomVariableEdi
onQueryChange={onQueryChange}
onAllValueChange={onAllValueChange}
onAllowCustomValueChange={onAllowCustomValueChange}
onValuesFormatChange={onValuesFormatChange}
/>
);
}
@@ -43,6 +43,7 @@ export function getLocalVariableValueSet(
name: variable.state.name,
value,
text,
properties: variable.state.options.find((o) => o.value === value)?.properties,
isMulti: variable.state.isMulti,
includeAll: variable.state.includeAll,
}),