CustomVariable: Be able to edit them using scenes (#80193)

---------
Co-authored-by: Alexandra Vargas <alexa1866@gmail.com>
This commit is contained in:
Ivan Ortega Alba 2024-01-17 18:14:01 +01:00 committed by GitHub
parent c9211fbd69
commit 3b401d0d4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 474 additions and 122 deletions

View File

@ -4392,8 +4392,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "1"]
],
"public/app/features/variables/editor/VariableEditorEditor.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"]
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/variables/editor/VariableEditorList.tsx:5381": [
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"],

View File

@ -69,7 +69,7 @@ describe('Variables - Query - Add variable', () => {
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption().should('not.exist');
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInputV2().should('not.exist');
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput().should('not.exist');
});
it('adding a single value query variable', () => {
@ -152,7 +152,7 @@ describe('Variables - Query - Add variable', () => {
cy.get('input[type="checkbox"]').click({ force: true }).should('be.checked');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInputV2().within((input) => {
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput().within((input) => {
expect(input.attr('placeholder')).equals('blank = auto');
expect(input.val()).equals('');
});

View File

@ -137,12 +137,11 @@ export const Pages = {
generalLabelInputV2: 'data-testid Variable editor Form Label field',
generalHideSelect: 'Variable editor Form Hide select',
generalHideSelectV2: 'data-testid Variable editor Form Hide select',
selectionOptionsMultiSwitch: 'Variable editor Form Multi switch',
selectionOptionsIncludeAllSwitch: 'Variable editor Form IncludeAll switch',
selectionOptionsCustomAllInput: 'Variable editor Form IncludeAll field',
selectionOptionsCustomAllInputV2: 'data-testid Variable editor Form IncludeAll field',
selectionOptionsMultiSwitch: 'data-testid Variable editor Form Multi switch',
selectionOptionsIncludeAllSwitch: 'data-testid Variable editor Form IncludeAll switch',
selectionOptionsCustomAllInput: 'data-testid Variable editor Form IncludeAll field',
previewOfValuesOption: 'data-testid Variable editor Preview of Values option',
submitButton: 'Variable editor Submit button',
submitButton: 'data-testid Variable editor Run Query button',
applyButton: 'data-testid Variable editor Apply button',
},
QueryVariable: {

View File

@ -1,10 +1,12 @@
import React from 'react';
import React, { FormEvent } from 'react';
import { useAsyncFn } from 'react-use';
import { lastValueFrom } from 'rxjs';
import { SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { SceneVariable } from '@grafana/scenes';
import { VariableHide, defaultVariableModel } from '@grafana/schema';
import { HorizontalGroup, Button } from '@grafana/ui';
import { HorizontalGroup, Button, LoadingPlaceholder } 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';
@ -24,11 +26,11 @@ interface VariableEditorFormProps {
}
export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDiscardChanges }: VariableEditorFormProps) {
const { name: initialName, type, label: initialLabel, description: initialDescription, hide } = variable.useState();
const { name, type, label, description, hide } = variable.useState();
const EditorToRender = isEditableVariableType(type) ? getVariableEditor(type) : undefined;
const [name, setName] = React.useState(initialName ?? '');
const [label, setLabel] = React.useState(initialLabel ?? '');
const [description, setDescription] = React.useState(initialDescription ?? '');
const [runQueryState, onRunQuery] = useAsyncFn(async () => {
await lastValueFrom(variable.validateAndUpdate!());
}, [variable]);
const onVariableTypeChange = (option: SelectableValue<EditableVariableType>) => {
if (option.value) {
@ -36,13 +38,10 @@ export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDiscard
}
};
const onNameChange = (e: React.FormEvent<HTMLInputElement>) => setName(e.currentTarget.value);
const onLabelChange = (e: React.FormEvent<HTMLInputElement>) => setLabel(e.currentTarget.value);
const onDescriptionChange = (e: React.FormEvent<HTMLTextAreaElement>) => setDescription(e.currentTarget.value);
const onNameBlur = () => variable.setState({ name });
const onLabelBlur = () => variable.setState({ label });
const onDescriptionBlur = () => variable.setState({ description });
const onNameBlur = (e: FormEvent<HTMLInputElement>) => variable.setState({ name: e.currentTarget.value });
const onLabelBlur = (e: FormEvent<HTMLInputElement>) => variable.setState({ label: e.currentTarget.value });
const onDescriptionBlur = (e: FormEvent<HTMLTextAreaElement>) =>
variable.setState({ description: e.currentTarget.value });
const onHideChange = (hide: VariableHide) => variable.setState({ hide });
return (
@ -52,12 +51,11 @@ export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDiscard
<VariableLegend>General</VariableLegend>
<VariableTextField
value={name}
onBlur={onNameBlur}
onChange={onNameChange}
name="Name"
placeholder="Variable name"
description="The name of the template variable. (Max. 50 characters)"
placeholder="Variable name"
defaultValue={name ?? ''}
onBlur={onNameBlur}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2}
maxLength={VariableNameConstraints.MaxSize}
required
@ -65,16 +63,14 @@ export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDiscard
<VariableTextField
name="Label"
description="Optional display name"
value={label}
onChange={onLabelChange}
placeholder="Label name"
defaultValue={label ?? ''}
onBlur={onLabelBlur}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2}
/>
<VariableTextAreaField
name="Description"
value={description}
onChange={onDescriptionChange}
defaultValue={description ?? ''}
placeholder="Descriptive text"
onBlur={onDescriptionBlur}
width={52}
@ -82,24 +78,15 @@ export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDiscard
<VariableHideSelect onChange={onHideChange} hide={hide || defaultVariableModel.hide!} type={type} />
{EditorToRender && <EditorToRender variable={variable} />}
{EditorToRender && <EditorToRender variable={variable} onRunQuery={onRunQuery} />}
{hasVariableOptions(variable) && <VariableValuesPreview options={variable.state.options} />}
{hasVariableOptions(variable) && <VariableValuesPreview options={variable.getOptionsForSelect()} />}
<div style={{ marginTop: '16px' }}>
<HorizontalGroup spacing="md" height="inherit">
{/* <Button variant="destructive" fill="outline" onClick={onModalOpen}>
Delete
</Button> */}
{/* <Button
type="submit"
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.submitButton}
disabled={loading}
variant="secondary"
>
Run query
{loading && <Icon className="spin-clockwise" name="sync" size="sm" style={{ marginLeft: '2px' }} />}
</Button> */}
<Button
variant="secondary"
data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.General.applyButton}
@ -107,6 +94,14 @@ export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDiscard
>
Back to list
</Button>
<Button
disabled={runQueryState.loading}
variant="secondary"
data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.General.submitButton}
onClick={onRunQuery}
>
{runQueryState.loading ? <LoadingPlaceholder text="Running query..." /> : `Run query`}
</Button>
<Button
variant="destructive"
data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.General.applyButton}

View File

@ -0,0 +1,116 @@
import { render, fireEvent } from '@testing-library/react';
import React from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { CustomVariableForm } from './CustomVariableForm';
describe('CustomVariableForm', () => {
const onQueryChange = jest.fn();
const onMultiChange = jest.fn();
const onIncludeAllChange = jest.fn();
const onAllValueChange = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
});
it('should render the form fields correctly', () => {
const { getByTestId } = render(
<CustomVariableForm
query="query"
multi={true}
allValue="custom value"
includeAll={true}
onQueryChange={onQueryChange}
onMultiChange={onMultiChange}
onIncludeAllChange={onIncludeAllChange}
onAllValueChange={onAllValueChange}
/>
);
const queryInput = getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput);
const multiCheckbox = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch
);
const includeAllCheckbox = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch
);
const allValueInput = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput
);
expect(queryInput).toBeInTheDocument();
expect(queryInput).toHaveValue('query');
expect(multiCheckbox).toBeInTheDocument();
expect(multiCheckbox).toBeChecked();
expect(includeAllCheckbox).toBeInTheDocument();
expect(includeAllCheckbox).toBeChecked();
expect(allValueInput).toBeInTheDocument();
expect(allValueInput).toHaveValue('custom value');
});
it('should call the correct event handlers on input change', () => {
const { getByTestId } = render(
<CustomVariableForm
query=""
multi={true}
allValue=""
includeAll={true}
onQueryChange={onQueryChange}
onMultiChange={onMultiChange}
onIncludeAllChange={onIncludeAllChange}
onAllValueChange={onAllValueChange}
/>
);
const queryInput = getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput);
const multiCheckbox = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch
);
const includeAllCheckbox = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch
);
const allValueInput = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput
);
fireEvent.click(multiCheckbox);
fireEvent.click(includeAllCheckbox);
fireEvent.change(queryInput, { currentTarget: { value: 'test query' } });
fireEvent.change(allValueInput, { currentTarget: { value: 'test value' } });
expect(onMultiChange).toHaveBeenCalledTimes(1);
expect(onIncludeAllChange).toHaveBeenCalledTimes(1);
expect(onQueryChange).not.toHaveBeenCalledTimes(1);
expect(onAllValueChange).not.toHaveBeenCalledTimes(1);
});
it('should call the correct event handlers on input blur', () => {
const { getByTestId } = render(
<CustomVariableForm
query="query value"
multi={true}
allValue="custom all value"
includeAll={true}
onQueryChange={onQueryChange}
onMultiChange={onMultiChange}
onIncludeAllChange={onIncludeAllChange}
onAllValueChange={onAllValueChange}
/>
);
const queryInput = getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput);
const allValueInput = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput
);
fireEvent.blur(queryInput);
fireEvent.blur(allValueInput);
expect(onQueryChange).toHaveBeenCalled();
expect(onAllValueChange).toHaveBeenCalled();
expect(onMultiChange).not.toHaveBeenCalled();
expect(onIncludeAllChange).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,57 @@
import React, { FormEvent } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { VariableLegend } from '../components/VariableLegend';
import { VariableTextAreaField } from '../components/VariableTextAreaField';
import { SelectionOptionsForm } from './SelectionOptionsForm';
interface CustomVariableFormProps {
query: string;
multi: boolean;
allValue?: string | null;
includeAll: boolean;
onQueryChange: (event: FormEvent<HTMLTextAreaElement>) => void;
onMultiChange: (event: FormEvent<HTMLInputElement>) => void;
onIncludeAllChange: (event: FormEvent<HTMLInputElement>) => void;
onAllValueChange: (event: FormEvent<HTMLInputElement>) => void;
onQueryBlur?: (event: FormEvent<HTMLTextAreaElement>) => void;
onAllValueBlur?: (event: FormEvent<HTMLInputElement>) => void;
}
export function CustomVariableForm({
query,
multi,
allValue,
includeAll,
onQueryChange,
onMultiChange,
onIncludeAllChange,
onAllValueChange,
}: CustomVariableFormProps) {
return (
<>
<VariableLegend>Custom options</VariableLegend>
<VariableTextAreaField
name="Values separated by comma"
defaultValue={query}
placeholder="1, 10, mykey : myvalue, myvalue, escaped\,value"
onBlur={onQueryChange}
required
width={52}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput}
/>
<VariableLegend>Selection options</VariableLegend>
<SelectionOptionsForm
multi={multi}
includeAll={includeAll}
allValue={allValue}
onMultiChange={onMultiChange}
onIncludeAllChange={onIncludeAllChange}
onAllValueChange={onAllValueChange}
/>
</>
);
}

View File

@ -0,0 +1,52 @@
import React, { ChangeEvent, FormEvent } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { VerticalGroup } from '@grafana/ui';
import { VariableCheckboxField } from 'app/features/dashboard-scene/settings/variables/components/VariableCheckboxField';
import { VariableTextField } from 'app/features/dashboard-scene/settings/variables/components/VariableTextField';
interface SelectionOptionsFormProps {
multi: boolean;
includeAll: boolean;
allValue?: string | null;
onMultiChange: (event: ChangeEvent<HTMLInputElement>) => void;
onIncludeAllChange: (event: ChangeEvent<HTMLInputElement>) => void;
onAllValueChange: (event: FormEvent<HTMLInputElement>) => void;
}
export function SelectionOptionsForm({
multi,
includeAll,
allValue,
onMultiChange,
onIncludeAllChange,
onAllValueChange,
}: SelectionOptionsFormProps) {
return (
<VerticalGroup spacing="md" height="inherit">
<VariableCheckboxField
value={multi}
name="Multi-value"
description="Enables multiple values to be selected at the same time"
onChange={onMultiChange}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch}
/>
<VariableCheckboxField
value={includeAll}
name="Include All option"
description="Enables an option to include all variables"
onChange={onIncludeAllChange}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch}
/>
{includeAll && (
<VariableTextField
defaultValue={allValue ?? ''}
onBlur={onAllValueChange}
name="Custom all value"
placeholder="blank = auto"
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput}
/>
)}
</VerticalGroup>
);
}

View File

@ -5,17 +5,19 @@ import { VariableLegend } from 'app/features/dashboard-scene/settings/variables/
import { VariableTextField } from 'app/features/dashboard-scene/settings/variables/components/VariableTextField';
interface TextBoxVariableFormProps {
value: string;
onChange: (event: FormEvent<HTMLInputElement>) => void;
onBlur: (event: FormEvent<HTMLInputElement>) => void;
value?: string;
defaultValue?: string;
onChange?: (event: FormEvent<HTMLInputElement>) => void;
onBlur?: (event: FormEvent<HTMLInputElement>) => void;
}
export function TextBoxVariableForm({ onChange, onBlur, value }: TextBoxVariableFormProps) {
export function TextBoxVariableForm({ defaultValue, value, onChange, onBlur }: TextBoxVariableFormProps) {
return (
<>
<VariableLegend>Text options</VariableLegend>
<VariableTextField
value={value}
defaultValue={defaultValue}
name="Default value"
placeholder="default value, if any"
onChange={onChange}

View File

@ -3,12 +3,13 @@ import React, { ChangeEvent, PropsWithChildren, ReactElement } from 'react';
import { Checkbox } from '@grafana/ui';
interface VariableCheckboxFieldProps {
interface VariableCheckboxFieldProps extends React.HTMLAttributes<HTMLInputElement> {
value: boolean;
name: string;
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
description?: string;
ariaLabel?: string;
testId?: string;
}
export function VariableCheckboxField({
@ -17,6 +18,7 @@ export function VariableCheckboxField({
description,
onChange,
ariaLabel,
testId,
}: PropsWithChildren<VariableCheckboxFieldProps>): ReactElement {
const uniqueId = useId();
@ -28,6 +30,7 @@ export function VariableCheckboxField({
value={value}
onChange={onChange}
aria-label={ariaLabel}
data-testid={testId}
/>
);
}

View File

@ -7,7 +7,8 @@ import { Field, TextArea, useStyles2 } from '@grafana/ui';
interface VariableTextAreaFieldProps {
name: string;
value: string;
value?: string;
defaultValue?: string;
placeholder: string;
onChange?: (event: FormEvent<HTMLTextAreaElement>) => void;
width: number;
@ -20,6 +21,7 @@ interface VariableTextAreaFieldProps {
export function VariableTextAreaField({
value,
defaultValue,
name,
description,
placeholder,
@ -39,6 +41,7 @@ export function VariableTextAreaField({
id={id}
rows={2}
value={value}
defaultValue={defaultValue}
onChange={onChange}
onBlur={onBlur}
placeholder={placeholder}

View File

@ -4,7 +4,8 @@ import React, { FormEvent, PropsWithChildren } from 'react';
import { Field, Input } from '@grafana/ui';
interface VariableTextFieldProps {
value: string;
value?: string;
defaultValue?: string;
name: string;
placeholder?: string;
onChange?: (event: FormEvent<HTMLInputElement>) => void;
@ -21,6 +22,7 @@ interface VariableTextFieldProps {
export function VariableTextField({
value,
defaultValue,
name,
placeholder = '',
onChange,
@ -43,6 +45,7 @@ export function VariableTextField({
id={id}
placeholder={placeholder}
value={value}
defaultValue={defaultValue}
onChange={onChange}
onBlur={onBlur}
width={grow ? undefined : width ?? 30}

View File

@ -0,0 +1,115 @@
import { render, fireEvent } from '@testing-library/react';
import React from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { CustomVariable } from '@grafana/scenes';
import { CustomVariableEditor } from './CustomVariableEditor';
describe('CustomVariableEditor', () => {
it('should render the CustomVariableForm with correct initial values', () => {
const variable = new CustomVariable({
name: 'customVar',
query: 'test, test2',
value: 'test',
isMulti: true,
includeAll: true,
allValue: 'test',
});
const onRunQuery = jest.fn();
const { getByTestId } = render(<CustomVariableEditor variable={variable} onRunQuery={onRunQuery} />);
const queryInput = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput
) as HTMLInputElement;
const allValueInput = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput
) as HTMLInputElement;
const multiCheckbox = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch
) as HTMLInputElement;
const includeAllCheckbox = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch
) as HTMLInputElement;
expect(queryInput.value).toBe('test, test2');
expect(allValueInput.value).toBe('test');
expect(multiCheckbox.checked).toBe(true);
expect(includeAllCheckbox.checked).toBe(true);
});
it('should update the variable state when input values change', () => {
const variable = new CustomVariable({
name: 'customVar',
query: 'test, test2',
value: 'test',
});
const onRunQuery = jest.fn();
const { getByTestId } = render(<CustomVariableEditor variable={variable} onRunQuery={onRunQuery} />);
const multiCheckbox = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch
);
const includeAllCheckbox = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch
);
// It include-all-custom input appears after include-all checkbox is checked only
expect(() =>
getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput)
).toThrow('Unable to find an element');
fireEvent.click(multiCheckbox);
fireEvent.click(includeAllCheckbox);
const allValueInput = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput
);
expect(variable.state.isMulti).toBe(true);
expect(variable.state.includeAll).toBe(true);
expect(allValueInput).toBeInTheDocument();
});
it('should call update query and re-run query when input loses focus', async () => {
const variable = new CustomVariable({
name: 'customVar',
query: 'test, test2',
value: 'test',
});
const onRunQuery = jest.fn();
const { getByTestId } = render(<CustomVariableEditor variable={variable} onRunQuery={onRunQuery} />);
const queryInput = getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput);
fireEvent.change(queryInput, { target: { value: 'test3, test4' } });
fireEvent.blur(queryInput);
expect(onRunQuery).toHaveBeenCalled();
expect(variable.state.query).toBe('test3, test4');
});
it('should update the variable state when all-custom-value input loses focus', () => {
const variable = new CustomVariable({
name: 'customVar',
query: 'test, test2',
value: 'test',
isMulti: true,
includeAll: true,
});
const onRunQuery = jest.fn();
const { getByTestId } = render(<CustomVariableEditor variable={variable} onRunQuery={onRunQuery} />);
const allValueInput = getByTestId(
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput
) as HTMLInputElement;
fireEvent.change(allValueInput, { target: { value: 'new custom all' } });
fireEvent.blur(allValueInput);
expect(variable.state.allValue).toBe('new custom all');
});
});

View File

@ -1,11 +1,41 @@
import React from 'react';
import React, { FormEvent } from 'react';
import { CustomVariable } from '@grafana/scenes';
import { CustomVariableForm } from '../components/CustomVariableForm';
interface CustomVariableEditorProps {
variable: CustomVariable;
onRunQuery: () => void;
}
export function CustomVariableEditor(props: CustomVariableEditorProps) {
return <div>CustomVariableEditor</div>;
export function CustomVariableEditor({ variable, onRunQuery }: CustomVariableEditorProps) {
const { query, isMulti, allValue, includeAll } = variable.useState();
const onMultiChange = (event: FormEvent<HTMLInputElement>) => {
variable.setState({ isMulti: event.currentTarget.checked });
};
const onIncludeAllChange = (event: FormEvent<HTMLInputElement>) => {
variable.setState({ includeAll: event.currentTarget.checked });
};
const onQueryChange = (event: FormEvent<HTMLTextAreaElement>) => {
variable.setState({ query: event.currentTarget.value });
onRunQuery();
};
const onAllValueChange = (event: FormEvent<HTMLInputElement>) => {
variable.setState({ allValue: event.currentTarget.value });
};
return (
<CustomVariableForm
query={query ?? ''}
multi={!!isMulti}
allValue={allValue ?? ''}
includeAll={!!includeAll}
onMultiChange={onMultiChange}
onIncludeAllChange={onIncludeAllChange}
onQueryChange={onQueryChange}
onAllValueChange={onAllValueChange}
/>
);
}

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { FormEvent } from 'react';
import { TextBoxVariable } from '@grafana/scenes';
@ -11,15 +11,10 @@ interface TextBoxVariableEditorProps {
export function TextBoxVariableEditor({ variable }: TextBoxVariableEditorProps) {
const { value } = variable.useState();
const [textValue, setTextValue] = useState(value);
const onTextValueChange = (event: React.FormEvent<HTMLInputElement>) => {
setTextValue(event.currentTarget.value);
const onTextValueChange = (e: FormEvent<HTMLInputElement>) => {
variable.setState({ value: e.currentTarget.value });
};
const onBlur = () => {
variable.setState({ value: textValue });
};
return <TextBoxVariableForm value={textValue} onChange={onTextValueChange} onBlur={onBlur} />;
return <TextBoxVariableForm defaultValue={value} onBlur={onTextValueChange} />;
}

View File

@ -1,13 +1,10 @@
import React, { FormEvent, PureComponent } from 'react';
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
import { selectors } from '@grafana/e2e-selectors';
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
import { CustomVariableForm } from 'app/features/dashboard-scene/settings/variables/components/CustomVariableForm';
import { StoreState } from 'app/types';
import { VariableLegend } from '../../dashboard-scene/settings/variables/components/VariableLegend';
import { VariableTextAreaField } from '../../dashboard-scene/settings/variables/components/VariableTextAreaField';
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
import { changeVariableMultiValue } from '../state/actions';
import { CustomVariableModel, VariableWithMultiSupport } from '../types';
@ -23,18 +20,11 @@ interface DispatchProps {
export type Props = OwnProps & ConnectedProps & DispatchProps;
class CustomVariableEditorUnconnected extends PureComponent<Props> {
onChange = (event: FormEvent<HTMLTextAreaElement>) => {
this.props.onPropChange({
propName: 'query',
propValue: event.currentTarget.value,
});
};
onSelectionOptionsChange = async ({ propName, propValue }: OnPropChangeArguments<VariableWithMultiSupport>) => {
this.props.onPropChange({ propName, propValue, updateOptions: true });
};
onBlur = (event: FormEvent<HTMLTextAreaElement>) => {
onQueryChange = (event: FormEvent<HTMLTextAreaElement>) => {
this.props.onPropChange({
propName: 'query',
propValue: event.currentTarget.value,
@ -44,26 +34,22 @@ class CustomVariableEditorUnconnected extends PureComponent<Props> {
render() {
return (
<>
<VariableLegend>Custom options</VariableLegend>
<VariableTextAreaField
name="Values separated by comma"
value={this.props.variable.query}
placeholder="1, 10, mykey : myvalue, myvalue, escaped\,value"
onChange={this.onChange}
onBlur={this.onBlur}
required
width={52}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput}
/>
<VariableLegend>Selection options</VariableLegend>
<SelectionOptionsEditor
variable={this.props.variable}
onPropChange={this.onSelectionOptionsChange}
onMultiChanged={this.props.changeVariableMultiValue}
/>
</>
<CustomVariableForm
query={this.props.variable.query}
multi={this.props.variable.multi}
allValue={this.props.variable.allValue}
includeAll={this.props.variable.includeAll}
onQueryChange={this.onQueryChange}
onMultiChange={(event) =>
this.onSelectionOptionsChange({ propName: 'multi', propValue: event.currentTarget.checked })
}
onIncludeAllChange={(event) =>
this.onSelectionOptionsChange({ propName: 'includeAll', propValue: event.currentTarget.checked })
}
onAllValueChange={(event) =>
this.onSelectionOptionsChange({ propName: 'allValue', propValue: event.currentTarget.value })
}
/>
);
}
}

View File

@ -1,10 +1,7 @@
import React, { ChangeEvent, FormEvent, useCallback } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { VerticalGroup } from '@grafana/ui';
import { SelectionOptionsForm } from 'app/features/dashboard-scene/settings/variables/components/SelectionOptionsForm';
import { VariableCheckboxField } from '../../dashboard-scene/settings/variables/components/VariableCheckboxField';
import { VariableTextField } from '../../dashboard-scene/settings/variables/components/VariableTextField';
import { KeyedVariableIdentifier } from '../state/types';
import { VariableWithMultiSupport } from '../types';
import { toKeyedVariableIdentifier } from '../utils';
@ -43,29 +40,14 @@ export const SelectionOptionsEditor = ({
);
return (
<VerticalGroup spacing="md" height="inherit">
<VariableCheckboxField
value={variable.multi}
name="Multi-value"
description="Enables multiple values to be selected at the same time"
onChange={onMultiChanged}
/>
<VariableCheckboxField
value={variable.includeAll}
name="Include All option"
description="Enables an option to include all variables"
onChange={onIncludeAllChanged}
/>
{variable.includeAll && (
<VariableTextField
value={variable.allValue ?? ''}
onChange={onAllValueChanged}
name="Custom all value"
placeholder="blank = auto"
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInputV2}
/>
)}
</VerticalGroup>
<SelectionOptionsForm
multi={variable.multi}
includeAll={variable.includeAll}
allValue={variable.allValue}
onMultiChange={onMultiChanged}
onIncludeAllChange={onIncludeAllChanged}
onAllValueChange={onAllValueChanged}
/>
);
};
SelectionOptionsEditor.displayName = 'SelectionOptionsEditor';

View File

@ -205,7 +205,7 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props, State>
</Button>
<Button
type="submit"
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.submitButton}
data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.General.submitButton}
disabled={loading}
variant="secondary"
>

View File

@ -1,7 +1,9 @@
import React, { FormEvent, ReactElement, useCallback } from 'react';
import { TextBoxVariableForm } from 'app/features/dashboard-scene/settings/variables/components/TextBoxVariableForm';
import { selectors } from '@grafana/e2e-selectors';
import { VariableLegend } from '../../dashboard-scene/settings/variables/components/VariableLegend';
import { VariableTextField } from '../../dashboard-scene/settings/variables/components/VariableTextField';
import { VariableEditorProps } from '../editor/types';
import { TextBoxVariableModel } from '../types';
@ -20,5 +22,18 @@ export function TextBoxVariableEditor({ onPropChange, variable: { query } }: Pro
const onChange = useCallback((e: FormEvent<HTMLInputElement>) => updateVariable(e, false), [updateVariable]);
const onBlur = useCallback((e: FormEvent<HTMLInputElement>) => updateVariable(e, true), [updateVariable]);
return <TextBoxVariableForm value={query} onChange={onChange} onBlur={onBlur} />;
return (
<>
<VariableLegend>Text options</VariableLegend>
<VariableTextField
value={query}
name="Default value"
placeholder="default value, if any"
onChange={onChange}
onBlur={onBlur}
width={30}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInputV2}
/>
</>
);
}