VariableEditor: Use new form styles (#56326)

This commit is contained in:
kay delaney
2022-10-12 09:43:41 +01:00
committed by GitHub
parent 6edce00b1a
commit 45edce90d3
32 changed files with 521 additions and 560 deletions

View File

@@ -5301,16 +5301,11 @@ 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, "Unexpected any. Specify a different type.", "1"]
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/variables/editor/VariableSelectField.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/variables/editor/VariableTextAreaField.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/features/variables/editor/getVariableQueryEditor.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],

View File

@@ -10,7 +10,9 @@ describe('Variables - Constant', () => {
// Create a new "Constant" variable
e2e.components.CallToActionCard.buttonV2('Add variable').click();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Constant{enter}');
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().within(() => {
e2e().get('input').type('Constant{enter}');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type('VariableUnderTest').blur();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type('Variable under test').blur();
e2e.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInputV2().type('pesto').blur();

View File

@@ -3,7 +3,9 @@ import { e2e } from '@grafana/e2e';
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
function fillInCustomVariable(name: string, label: string, value: string) {
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Custom{enter}');
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().within(() => {
e2e().get('input').type('Custom{enter}');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type(name).blur();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type(label).blur();
e2e.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput().type(value).blur();
@@ -42,7 +44,9 @@ describe('Variables - Custom', () => {
// Create a new "Custom" variable
e2e.components.CallToActionCard.buttonV2('Add variable').click();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Custom{enter}');
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().within(() => {
e2e().get('input').type('Custom{enter}');
});
// Set its name, label, and content
fillInCustomVariable('VariableUnderTest', 'Variable under test', 'One : 1,Two : 2, Three : 3');

View File

@@ -9,13 +9,17 @@ describe('Variables - Datasource', () => {
// Create a new "Datasource" variable
e2e.components.CallToActionCard.buttonV2('Add variable').click();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Data source{enter}');
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().within(() => {
e2e().get('input').type('Data source{enter}');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type('VariableUnderTest').blur();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type('Variable under test').blur();
// If this is failing, but sure to check there are Prometheus datasources named "gdev-prometheus" and "gdev-slow-prometheus"
// Or, just update is to match some gdev datasources to test with :)
e2e.pages.Dashboard.Settings.Variables.Edit.DatasourceVariable.datasourceSelect().type('Prometheus{enter}');
e2e.pages.Dashboard.Settings.Variables.Edit.DatasourceVariable.datasourceSelect().within(() => {
e2e().get('input').type('Prometheus{enter}');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption()
.eq(0)
.should('have.text', 'gdev-prometheus');

View File

@@ -16,7 +16,9 @@ describe('Variables - Interval', () => {
// Create a new "Interval" variable
e2e.components.CallToActionCard.buttonV2('Add variable').click();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Interval{enter}');
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().within(() => {
e2e().get('input').type('Interval{enter}');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type('VariableUnderTest').blur();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type('Variable under test').blur();

View File

@@ -12,7 +12,7 @@ describe('Variables - Query - Add variable', () => {
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2()
.should('be.visible')
.within((input) => {
expect(input.attr('placeholder')).equals('name');
expect(input.attr('placeholder')).equals('Variable name');
expect(input.val()).equals('query0');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2()
@@ -23,21 +23,17 @@ describe('Variables - Query - Add variable', () => {
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2()
.should('be.visible')
.within((input) => {
expect(input.attr('placeholder')).equals('optional display name');
expect(input.attr('placeholder')).equals('Label name');
expect(input.val()).equals('');
});
e2e()
.get('#Description')
.get('[placeholder="Descriptive text"]')
.should('be.visible')
.within((input) => {
expect(input.attr('placeholder')).equals('descriptive text');
expect(input.attr('placeholder')).equals('Descriptive text');
expect(input.val()).equals('');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalHideSelectV2()
.should('be.visible')
.within((select) => {
e2e.components.Select.singleValue().should('have.text', '');
});
e2e().get('label').contains('Show on dashboard').should('be.visible');
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsDataSourceSelect()
.should('be.visible')
@@ -45,11 +41,9 @@ describe('Variables - Query - Add variable', () => {
e2e.components.Select.singleValue().should('have.text', 'gdev-testdata');
});
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelectV2()
.should('be.visible')
.within((select) => {
e2e.components.Select.singleValue().should('have.text', 'On dashboard load');
});
e2e().get('label').contains('Refresh').scrollIntoView().should('be.visible');
e2e().get('label').contains('On dashboard load').scrollIntoView().should('be.visible');
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2()
.should('be.visible')
.within((input) => {
@@ -62,8 +56,18 @@ describe('Variables - Query - Add variable', () => {
.within((select) => {
e2e.components.Select.singleValue().should('have.text', 'Disabled');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch().should('not.be.checked');
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch().should('not.be.checked');
e2e()
.contains('label', 'Multi-value')
.within(() => {
e2e().get('input[type="checkbox"]').should('not.be.checked');
});
e2e()
.contains('label', 'Include All option')
.within(() => {
e2e().get('input[type="checkbox"]').should('not.be.checked');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption().should('not.exist');
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInputV2().should('not.exist');
@@ -80,9 +84,9 @@ describe('Variables - Query - Add variable', () => {
.clear()
.type('a label');
e2e().get('#Description').should('be.visible').clear().type('a description');
e2e().get('[placeholder="Descriptive text"]').should('be.visible').clear().type('a description');
e2e.components.DataSourcePicker.container().should('be.visible').type('gdev-testdata{enter}');
e2e.components.DataSourcePicker.inputV2().should('be.visible').type('gdev-testdata{enter}');
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput()
.should('be.visible')
@@ -96,7 +100,7 @@ describe('Variables - Query - Add variable', () => {
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption().should('exist');
e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().should('be.visible').click();
e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().scrollIntoView().should('be.visible').click();
e2e().wait(1500);
@@ -130,9 +134,9 @@ describe('Variables - Query - Add variable', () => {
.clear()
.type('a label');
e2e().get('#Description').should('be.visible').clear().type('a description');
e2e().get('[placeholder="Descriptive text"]').should('be.visible').clear().type('a description');
e2e.components.DataSourcePicker.container().type('gdev-testdata{enter}');
e2e.components.DataSourcePicker.inputV2().type('gdev-testdata{enter}');
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput()
.should('be.visible')
@@ -144,13 +148,17 @@ describe('Variables - Query - Add variable', () => {
.type('/.*C.*/')
.blur();
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch()
.click({ force: true })
.should('be.checked');
e2e()
.contains('label', 'Multi-value')
.within(() => {
e2e().get('input[type="checkbox"]').click({ force: true }).should('be.checked');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch()
.click({ force: true })
.should('be.checked');
e2e()
.contains('label', 'Include All option')
.within(() => {
e2e().get('input[type="checkbox"]').click({ force: true }).should('be.checked');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInputV2().within((input) => {
expect(input.attr('placeholder')).equals('blank = auto');
@@ -159,7 +167,7 @@ describe('Variables - Query - Add variable', () => {
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption().should('exist');
e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().should('be.visible').click();
e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().scrollIntoView().should('be.visible').click();
e2e().wait(500);

View File

@@ -10,7 +10,9 @@ describe('Variables - Text box', () => {
// Create a new "text box" variable
e2e.components.CallToActionCard.buttonV2('Add variable').click();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Text box{enter}');
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().within(() => {
e2e().get('input').type('Text box{enter}');
});
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type('VariableUnderTest').blur();
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type('Variable under test').blur();
e2e.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInputV2().type('cat-dog').blur();

View File

@@ -3,10 +3,10 @@ import { connect, ConnectedProps } from 'react-redux';
import { DataSourceInstanceSettings, getDataSourceRef } from '@grafana/data';
import { DataSourcePicker } from '@grafana/runtime';
import { Alert, InlineField, InlineFieldRow, VerticalGroup } from '@grafana/ui';
import { Alert, Field } from '@grafana/ui';
import { StoreState } from 'app/types';
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
import { VariableLegend } from '../editor/VariableLegend';
import { initialVariableEditorState } from '../editor/reducer';
import { getAdhocVariableEditorState } from '../editor/selectors';
import { VariableEditorProps } from '../editor/types';
@@ -61,18 +61,14 @@ export class AdHocVariableEditorUnConnected extends PureComponent<Props> {
const infoText = extended?.infoText ?? null;
return (
<VerticalGroup spacing="xs">
<VariableSectionHeader name="Options" />
<VerticalGroup spacing="sm">
<InlineFieldRow>
<InlineField label="Data source" labelWidth={20} htmlFor="data-source-picker">
<DataSourcePicker current={variable.datasource} onChange={this.onDatasourceChanged} noDefault />
</InlineField>
</InlineFieldRow>
<>
<VariableLegend>Ad-hoc options</VariableLegend>
<Field label="Data source" htmlFor="data-source-picker">
<DataSourcePicker current={variable.datasource} onChange={this.onDatasourceChanged} width={30} noDefault />
</Field>
{infoText ? <Alert title={infoText} severity="info" /> : null}
</VerticalGroup>
</VerticalGroup>
{infoText ? <Alert title={infoText} severity="info" /> : null}
</>
);
}
}

View File

@@ -1,9 +1,8 @@
import React, { FormEvent, PureComponent } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { VerticalGroup } from '@grafana/ui';
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
import { VariableLegend } from '../editor/VariableLegend';
import { VariableTextField } from '../editor/VariableTextField';
import { VariableEditorProps } from '../editor/types';
import { ConstantVariableModel } from '../types';
@@ -28,19 +27,18 @@ export class ConstantVariableEditor extends PureComponent<Props> {
render() {
return (
<VerticalGroup spacing="xs">
<VariableSectionHeader name="Constant options" />
<>
<VariableLegend>Constant options</VariableLegend>
<VariableTextField
value={this.props.variable.query}
name="Value"
placeholder="your metric prefix"
onChange={this.onChange}
onBlur={this.onBlur}
labelWidth={20}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInputV2}
grow
width={30}
/>
</VerticalGroup>
</>
);
}
}

View File

@@ -2,12 +2,11 @@ import React, { FormEvent, PureComponent } from 'react';
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
import { selectors } from '@grafana/e2e-selectors';
import { VerticalGroup } from '@grafana/ui';
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
import { StoreState } from 'app/types';
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
import { VariableLegend } from '../editor/VariableLegend';
import { VariableTextAreaField } from '../editor/VariableTextAreaField';
import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
import { changeVariableMultiValue } from '../state/actions';
@@ -45,29 +44,26 @@ class CustomVariableEditorUnconnected extends PureComponent<Props> {
render() {
return (
<VerticalGroup spacing="xs">
<VariableSectionHeader name="Custom options" />
<VerticalGroup spacing="md">
<VerticalGroup spacing="none">
<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={50}
labelWidth={27}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput}
/>
</VerticalGroup>
<SelectionOptionsEditor
variable={this.props.variable}
onPropChange={this.onSelectionOptionsChange}
onMultiChanged={this.props.changeVariableMultiValue}
/>{' '}
</VerticalGroup>
</VerticalGroup>
<>
<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}
/>
</>
);
}
}

View File

@@ -41,13 +41,13 @@ describe('DataSourceVariableEditor', () => {
it('has a regex filter field', () => {
render(<DataSourceVariableEditor {...props} />);
const field = screen.getByLabelText('Instance name filter');
const field = screen.getByLabelText(/Instance name filter/);
expect(field).toBeInTheDocument();
});
it('calls the handler when the regex filter is changed', () => {
render(<DataSourceVariableEditor {...props} />);
const field = screen.getByLabelText('Instance name filter');
const field = screen.getByLabelText(/Instance name filter/);
fireEvent.change(field, { target: { value: '/prod/' } });
expect(props.onPropChange).toBeCalledWith({ propName: 'regex', propValue: '/prod/' });
});

View File

@@ -3,11 +3,10 @@ import { connect, ConnectedProps } from 'react-redux';
import { SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { InlineFieldRow, VerticalGroup } from '@grafana/ui';
import { StoreState } from '../../../types';
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
import { VariableLegend } from '../editor/VariableLegend';
import { VariableSelectField } from '../editor/VariableSelectField';
import { VariableTextField } from '../editor/VariableTextField';
import { initialVariableEditorState } from '../editor/reducer';
@@ -103,48 +102,40 @@ export class DataSourceVariableEditorUnConnected extends PureComponent<Props> {
const typeValue = typeOptions.find((o) => o.value === variable.query) ?? typeOptions[0];
return (
<VerticalGroup spacing="xs">
<VariableSectionHeader name="Data source options" />
<VerticalGroup spacing="md">
<VerticalGroup spacing="xs">
<InlineFieldRow>
<VariableSelectField
name="Type"
value={typeValue}
options={typeOptions}
onChange={this.onDataSourceTypeChanged}
labelWidth={10}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.DatasourceVariable.datasourceSelect}
/>
</InlineFieldRow>
<InlineFieldRow>
<VariableTextField
value={this.props.variable.regex}
name="Instance name filter"
placeholder="/.*-(.*)-.*/"
onChange={this.onRegExChange}
onBlur={this.onRegExBlur}
labelWidth={20}
tooltip={
<div>
Regex filter for which data source instances to choose from in the variable value list. Leave empty
for all.
<br />
<br />
Example: <code>/^prod/</code>
</div>
}
/>
</InlineFieldRow>
</VerticalGroup>
<>
<VariableLegend>Data source options</VariableLegend>
<VariableSelectField
name="Type"
value={typeValue}
options={typeOptions}
onChange={this.onDataSourceTypeChanged}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.DatasourceVariable.datasourceSelect}
/>
<SelectionOptionsEditor
variable={variable}
onPropChange={this.onSelectionOptionsChange}
onMultiChanged={changeVariableMultiValue}
/>
</VerticalGroup>
</VerticalGroup>
<VariableTextField
value={this.props.variable.regex}
name="Instance name filter"
placeholder="/.*-(.*)-.*/"
onChange={this.onRegExChange}
onBlur={this.onRegExBlur}
description={
<div>
Regex filter for which data source instances to choose from in the variable value list. Leave empty for
all.
<br />
<br />
Example: <code>/^prod/</code>
</div>
}
/>
<VariableLegend>Selection options</VariableLegend>
<SelectionOptionsEditor
variable={variable}
onPropChange={this.onSelectionOptionsChange}
onMultiChanged={changeVariableMultiValue}
/>
</>
);
}
}

View File

@@ -15,7 +15,7 @@ import { dataSourceVariableReducer, initialDataSourceVariableModelState } from '
export const createDataSourceVariableAdapter = (): VariableAdapter<DataSourceVariableModel> => {
return {
id: 'datasource',
description: 'Enabled you to dynamically switch the data source for multiple panels.',
description: 'Enables you to dynamically switch the data source for multiple panels.',
name: 'Data source',
initialState: initialDataSourceVariableModelState,
reducer: dataSourceVariableReducer,

View File

@@ -1,18 +1,17 @@
import { css } from '@emotion/css';
import { useId } from '@react-aria/utils';
import React, { FC, useCallback, useState } from 'react';
import { GrafanaTheme } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { useStyles } from '@grafana/ui';
import { TextArea, useStyles2 } from '@grafana/ui';
import { VariableQueryEditorProps } from '../types';
import { VariableTextAreaField } from './VariableTextAreaField';
import { getStyles } from './VariableTextAreaField';
export const LEGACY_VARIABLE_QUERY_EDITOR_NAME = 'Grafana-LegacyVariableQueryEditor';
export const LegacyVariableQueryEditor: FC<VariableQueryEditorProps> = ({ onChange, query }) => {
const styles = useStyles(getStyles);
const styles = useStyles2(getStyles);
const [value, setValue] = useState(query);
const onValueChange = (event: React.FormEvent<HTMLTextAreaElement>) => {
setValue(event.currentTarget.value);
@@ -25,29 +24,22 @@ export const LegacyVariableQueryEditor: FC<VariableQueryEditorProps> = ({ onChan
[onChange]
);
const id = useId();
return (
<div className={styles.container}>
<VariableTextAreaField
name="Query"
value={value}
placeholder="metric name or tags query"
width={100}
onChange={onValueChange}
onBlur={onBlur}
required
labelWidth={20}
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput}
/>
</div>
<TextArea
id={id}
rows={2}
value={value}
onChange={onValueChange}
onBlur={onBlur}
placeholder="Metric name or tags query"
required
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput}
cols={52}
className={styles.textarea}
/>
);
};
function getStyles(theme: GrafanaTheme) {
return {
container: css`
margin-bottom: ${theme.spacing.xs};
`,
};
}
LegacyVariableQueryEditor.displayName = LEGACY_VARIABLE_QUERY_EDITOR_NAME;

View File

@@ -1,14 +1,13 @@
import React, { ChangeEvent, FormEvent, FunctionComponent, useCallback } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { InlineFieldRow, VerticalGroup } from '@grafana/ui';
import { VerticalGroup } from '@grafana/ui';
import { KeyedVariableIdentifier } from '../state/types';
import { VariableWithMultiSupport } from '../types';
import { toKeyedVariableIdentifier } from '../utils';
import { VariableSectionHeader } from './VariableSectionHeader';
import { VariableSwitchField } from './VariableSwitchField';
import { VariableCheckboxField } from './VariableCheckboxField';
import { VariableTextField } from './VariableTextField';
import { VariableEditorProps } from './types';
@@ -44,37 +43,27 @@ export const SelectionOptionsEditor: FunctionComponent<SelectionOptionsEditorPro
);
return (
<VerticalGroup spacing="none">
<VariableSectionHeader name="Selection options" />
<InlineFieldRow>
<VariableSwitchField
value={variable.multi}
name="Multi-value"
tooltip="Enables multiple values to be selected at the same time"
onChange={onMultiChanged}
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch}
/>
</InlineFieldRow>
<InlineFieldRow>
<VariableSwitchField
value={variable.includeAll}
name="Include All option"
tooltip="Enables an option to include all variables"
onChange={onIncludeAllChanged}
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch}
/>
</InlineFieldRow>
<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 && (
<InlineFieldRow>
<VariableTextField
value={variable.allValue ?? ''}
onChange={onAllValueChanged}
name="Custom all value"
placeholder="blank = auto"
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInputV2}
labelWidth={20}
/>
</InlineFieldRow>
<VariableTextField
value={variable.allValue ?? ''}
onChange={onAllValueChanged}
name="Custom all value"
placeholder="blank = auto"
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInputV2}
/>
)}
</VerticalGroup>
);

View File

@@ -0,0 +1,33 @@
import { useId } from '@react-aria/utils';
import React, { ChangeEvent, PropsWithChildren, ReactElement } from 'react';
import { Checkbox } from '@grafana/ui';
interface VariableCheckboxFieldProps {
value: boolean;
name: string;
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
description?: string;
ariaLabel?: string;
}
export function VariableCheckboxField({
value,
name,
description,
onChange,
ariaLabel,
}: PropsWithChildren<VariableCheckboxFieldProps>): ReactElement {
const uniqueId = useId();
return (
<Checkbox
id={uniqueId}
label={name}
description={description}
value={value}
onChange={onChange}
aria-label={ariaLabel}
/>
);
}

View File

@@ -1,14 +1,12 @@
import { isEqual } from 'lodash';
import React, { FormEvent, PureComponent } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { bindActionCreators } from 'redux';
import { AppEvents, LoadingState, SelectableValue, VariableType } from '@grafana/data';
import { LoadingState, SelectableValue, VariableType } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { locationService } from '@grafana/runtime';
import { Button, HorizontalGroup, Icon, InlineFieldRow, VerticalGroup } from '@grafana/ui';
import { Button, HorizontalGroup, Icon } from '@grafana/ui';
import { appEvents } from '../../../core/core';
import { StoreState, ThunkDispatch } from '../../../types';
import { variableAdapters } from '../adapters';
import { hasOptions } from '../guard';
@@ -22,7 +20,8 @@ import { toKeyedVariableIdentifier, toVariablePayload } from '../utils';
import { ConfirmDeleteModal } from './ConfirmDeleteModal';
import { VariableHideSelect } from './VariableHideSelect';
import { VariableSectionHeader } from './VariableSectionHeader';
import { VariableLegend } from './VariableLegend';
import { VariableTextAreaField } from './VariableTextAreaField';
import { VariableTextField } from './VariableTextField';
import { VariableTypeSelect } from './VariableTypeSelect';
import { VariableValuesPreview } from './VariableValuesPreview';
@@ -75,14 +74,6 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props, State>
this.props.variableEditorMount(this.props.identifier);
}
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<{}>, snapshot?: any): void {
if (!isEqual(prevProps.editor.errors, this.props.editor.errors)) {
Object.values(this.props.editor.errors).forEach((error) => {
appEvents.emit(AppEvents.alertWarning, ['Validation', error]);
});
}
}
componentWillUnmount(): void {
this.props.variableEditorUnMount(this.props.identifier);
}
@@ -104,12 +95,12 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props, State>
this.props.changeVariableProp(this.props.identifier, 'label', event.currentTarget.value);
};
onDescriptionChange = (event: FormEvent<HTMLInputElement>) => {
onDescriptionChange = (event: FormEvent<HTMLTextAreaElement>) => {
this.props.changeVariableProp(this.props.identifier, 'description', event.currentTarget.value);
};
onHideChange = (option: SelectableValue<VariableHide>) => {
this.props.changeVariableProp(this.props.identifier, 'hide', option.value);
onHideChange = (option: VariableHide) => {
this.props.changeVariableProp(this.props.identifier, 'hide', option);
};
onPropChanged = ({ propName, propValue, updateOptions = false }: OnPropChangeArguments) => {
@@ -156,79 +147,68 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props, State>
const loading = variable.state === LoadingState.Loading;
return (
<div>
<>
<form aria-label="Variable editor Form" onSubmit={this.onHandleSubmit}>
<VerticalGroup spacing="lg">
<VerticalGroup spacing="none">
<VariableSectionHeader name="General" />
<InlineFieldRow>
<VariableTextField
value={this.props.editor.name}
onChange={this.onNameChange}
name="Name"
placeholder="name"
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2}
maxLength={VariableNameConstraints.MaxSize}
required
tooltip="Variable name cannot be longer than 50 characters"
/>
<VariableTypeSelect onChange={this.onTypeChange} type={this.props.variable.type} />
</InlineFieldRow>
<VariableTypeSelect onChange={this.onTypeChange} type={this.props.variable.type} />
{this.props.editor.errors.name && (
<div className="gf-form">
<span className="gf-form-label gf-form-label--error">{this.props.editor.errors.name}</span>
</div>
)}
<VariableLegend>General</VariableLegend>
<VariableTextField
value={this.props.editor.name}
onChange={this.onNameChange}
name="Name"
placeholder="Variable name"
description="The name of the template variable. (Max. 50 characters)"
invalid={!!this.props.editor.errors.name}
error={this.props.editor.errors.name}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2}
maxLength={VariableNameConstraints.MaxSize}
required
/>
<InlineFieldRow>
<VariableTextField
value={this.props.variable.label ?? ''}
onChange={this.onLabelChange}
name="Label"
placeholder="optional display name"
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2}
/>
<VariableHideSelect
onChange={this.onHideChange}
hide={this.props.variable.hide}
type={this.props.variable.type}
/>
</InlineFieldRow>
<VariableTextField
name="Label"
description="Optional display name"
value={this.props.variable.label ?? ''}
placeholder="Label name"
onChange={this.onLabelChange}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2}
/>
<VariableTextAreaField
name="Description"
value={variable.description ?? ''}
placeholder="Descriptive text"
onChange={this.onDescriptionChange}
width={52}
/>
<VariableHideSelect
onChange={this.onHideChange}
hide={this.props.variable.hide}
type={this.props.variable.type}
/>
<VariableTextField
name="Description"
value={variable.description ?? ''}
placeholder="descriptive text"
onChange={this.onDescriptionChange}
grow
/>
</VerticalGroup>
{EditorToRender && <EditorToRender variable={this.props.variable} onPropChange={this.onPropChanged} />}
{EditorToRender && <EditorToRender variable={this.props.variable} onPropChange={this.onPropChanged} />}
{hasOptions(this.props.variable) ? <VariableValuesPreview variable={this.props.variable} /> : null}
{hasOptions(this.props.variable) ? <VariableValuesPreview variable={this.props.variable} /> : null}
<HorizontalGroup spacing="md">
<Button variant="destructive" onClick={this.onModalOpen}>
<div style={{ marginTop: '16px' }}>
<HorizontalGroup spacing="md" height="inherit">
<Button variant="destructive" fill="outline" onClick={this.onModalOpen}>
Delete
</Button>
<Button
type="submit"
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.submitButton}
disabled={loading}
variant={'secondary'}
variant="secondary"
>
Run query
{loading ? (
<Icon className="spin-clockwise" name="sync" size="sm" style={{ marginLeft: '2px' }} />
) : null}
{loading && <Icon className="spin-clockwise" name="sync" size="sm" style={{ marginLeft: '2px' }} />}
</Button>
<Button variant="primary" onClick={this.onApply}>
Apply
</Button>
</HorizontalGroup>
</VerticalGroup>
</div>
</form>
<ConfirmDeleteModal
isOpen={this.state.showDeleteModal}
@@ -236,7 +216,7 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props, State>
onConfirm={this.onDelete}
onDismiss={this.onModalClose}
/>
</div>
</>
);
}
}

View File

@@ -1,37 +1,30 @@
import React, { PropsWithChildren, useMemo } from 'react';
import { SelectableValue, VariableType } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { VariableSelectField } from '../editor/VariableSelectField';
import { VariableHide } from '../types';
import { VariableType, VariableHide } from '@grafana/data';
import { Field, RadioButtonGroup } from '@grafana/ui';
interface Props {
onChange: (option: SelectableValue<VariableHide>) => void;
onChange: (option: VariableHide) => void;
hide: VariableHide;
type: VariableType;
}
const HIDE_OPTIONS = [
{ label: '', value: VariableHide.dontHide },
{ label: 'Label', value: VariableHide.hideLabel },
{ label: 'Variable', value: VariableHide.hideVariable },
{ label: 'Label and value', value: VariableHide.dontHide },
{ label: 'Value', value: VariableHide.hideLabel },
{ label: 'Nothing', value: VariableHide.hideVariable },
];
export function VariableHideSelect({ onChange, hide, type }: PropsWithChildren<Props>) {
const value = useMemo(() => HIDE_OPTIONS.find((o) => o.value === hide) ?? HIDE_OPTIONS[0], [hide]);
const value = useMemo(() => HIDE_OPTIONS.find((o) => o.value === hide)?.value ?? HIDE_OPTIONS[0].value, [hide]);
if (type === 'constant') {
return null;
}
return (
<VariableSelectField
name="Hide"
value={value}
options={HIDE_OPTIONS}
onChange={onChange}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalHideSelectV2}
/>
<Field label="Show on dashboard">
<RadioButtonGroup options={HIDE_OPTIONS} onChange={onChange} value={value} />
</Field>
);
}

View File

@@ -0,0 +1,19 @@
import { css, cx } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Legend, useStyles2 } from '@grafana/ui';
export function VariableLegend({ className, ...rest }: Parameters<typeof Legend>['0']) {
const styles = useStyles2(getStyles);
return <Legend {...rest} className={cx(styles.legend, className)} />;
}
function getStyles(theme: GrafanaTheme2) {
return {
legend: css({
marginTop: theme.spacing(3),
marginBottom: theme.spacing(1),
}),
};
}

View File

@@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import React, { PropsWithChildren, ReactElement } from 'react';
import { GrafanaTheme, SelectableValue } from '@grafana/data';
import { InlineFormLabel, Select, useStyles } from '@grafana/ui';
import { Field, Select, useStyles } from '@grafana/ui';
import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId';
interface VariableSelectFieldProps<T> {
@@ -10,42 +10,37 @@ interface VariableSelectFieldProps<T> {
value: SelectableValue<T>;
options: Array<SelectableValue<T>>;
onChange: (option: SelectableValue<T>) => void;
tooltip?: string;
testId?: string;
width?: number;
labelWidth?: number;
description?: React.ReactNode;
}
export function VariableSelectField({
name,
description,
value,
options,
tooltip,
onChange,
testId,
width,
labelWidth,
}: PropsWithChildren<VariableSelectFieldProps<any>>): ReactElement {
const styles = useStyles(getStyles);
const uniqueId = useUniqueId();
const inputId = `variable-select-input-${name}-${uniqueId}`;
return (
<>
<InlineFormLabel width={labelWidth ?? 6} tooltip={tooltip} htmlFor={inputId}>
{name}
</InlineFormLabel>
<Field label={name} description={description} htmlFor={inputId}>
<div data-testid={testId}>
<Select
inputId={inputId}
onChange={onChange}
value={value}
width={width ?? 25}
width={width ?? 30}
options={options}
className={styles.selectContainer}
/>
</div>
</>
</Field>
);
}

View File

@@ -1,32 +0,0 @@
import React, { ChangeEvent, PropsWithChildren, ReactElement } from 'react';
import { InlineField, InlineSwitch } from '@grafana/ui';
import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId';
interface VariableSwitchFieldProps {
value: boolean;
name: string;
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
tooltip?: string;
ariaLabel?: string;
}
export function VariableSwitchField({
value,
name,
tooltip,
onChange,
ariaLabel,
}: PropsWithChildren<VariableSwitchFieldProps>): ReactElement {
const uniqueId = useUniqueId();
return (
<InlineField label={name} labelWidth={20} tooltip={tooltip}>
<InlineSwitch
id={`var-switch-${uniqueId}`}
label={name}
value={value}
onChange={onChange}
aria-label={ariaLabel}
/>
</InlineField>
);
}

View File

@@ -1,49 +1,43 @@
import { css } from '@emotion/css';
import React, { FormEvent, PropsWithChildren, ReactElement, useCallback } from 'react';
import { useId } from '@react-aria/utils';
import React, { FormEvent, PropsWithChildren, ReactElement } from 'react';
import { GrafanaTheme } from '@grafana/data';
import { InlineField, TextArea, useStyles } from '@grafana/ui';
import { GrafanaTheme2 } from '@grafana/data';
import { Field, TextArea, useStyles2 } from '@grafana/ui';
interface VariableTextAreaFieldProps<T> {
interface VariableTextAreaFieldProps {
name: string;
value: string;
placeholder: string;
onChange: (event: FormEvent<HTMLTextAreaElement>) => void;
width: number;
tooltip?: string;
ariaLabel?: string;
required?: boolean;
labelWidth?: number;
testId?: string;
onBlur?: (event: FormEvent<HTMLTextAreaElement>) => void;
description?: React.ReactNode;
}
export function VariableTextAreaField({
name,
value,
name,
description,
placeholder,
tooltip,
onChange,
onBlur,
ariaLabel,
required,
width,
labelWidth,
testId,
}: PropsWithChildren<VariableTextAreaFieldProps<any>>): ReactElement {
const styles = useStyles(getStyles);
const getLineCount = useCallback((value: any) => {
if (value && typeof value === 'string') {
return value.split('\n').length;
}
return 1;
}, []);
}: PropsWithChildren<VariableTextAreaFieldProps>): ReactElement {
const styles = useStyles2(getStyles);
const id = useId();
return (
<InlineField label={name} labelWidth={labelWidth ?? 12} tooltip={tooltip}>
<Field label={name} description={description} htmlFor={id}>
<TextArea
rows={getLineCount(value)}
id={id}
rows={2}
value={value}
onChange={onChange}
onBlur={onBlur}
@@ -54,18 +48,19 @@ export function VariableTextAreaField({
className={styles.textarea}
data-testid={testId}
/>
</InlineField>
</Field>
);
}
function getStyles(theme: GrafanaTheme) {
export function getStyles(theme: GrafanaTheme2) {
return {
textarea: css`
white-space: pre-wrap;
min-height: 32px;
min-height: ${theme.spacing(4)};
height: auto;
overflow: auto;
padding: 6px 8px;
padding: ${theme.spacing(0.75, 1)};
width: inherit;
`,
};
}

View File

@@ -1,53 +1,55 @@
import React, { FormEvent, PropsWithChildren, ReactElement } from 'react';
import { useId } from '@react-aria/utils';
import React, { FormEvent, PropsWithChildren } from 'react';
import { InlineField, Input, PopoverContent } from '@grafana/ui';
import { Field, Input } from '@grafana/ui';
interface VariableTextFieldProps {
value: string;
name: string;
placeholder: string;
placeholder?: string;
onChange: (event: FormEvent<HTMLInputElement>) => void;
testId?: string;
tooltip?: PopoverContent;
required?: boolean;
width?: number;
labelWidth?: number;
grow?: boolean;
onBlur?: (event: FormEvent<HTMLInputElement>) => void;
interactive?: boolean;
maxLength?: number;
description?: React.ReactNode;
invalid?: boolean;
error?: React.ReactNode;
}
export function VariableTextField({
value,
name,
placeholder,
placeholder = '',
onChange,
testId,
width,
labelWidth,
required,
onBlur,
tooltip,
grow,
interactive,
description,
invalid,
error,
maxLength,
}: PropsWithChildren<VariableTextFieldProps>): ReactElement {
}: PropsWithChildren<VariableTextFieldProps>) {
const id = useId(name);
return (
<InlineField interactive={interactive} label={name} labelWidth={labelWidth ?? 12} tooltip={tooltip} grow={grow}>
<Field label={name} description={description} invalid={invalid} error={error} htmlFor={id}>
<Input
type="text"
id={name}
name={name}
id={id}
placeholder={placeholder}
value={value}
onChange={onChange}
onBlur={onBlur}
width={grow ? undefined : width ?? 25}
width={grow ? undefined : width ?? 30}
data-testid={testId}
maxLength={maxLength}
required={required}
/>
</InlineField>
</Field>
);
}

View File

@@ -3,7 +3,6 @@ import React, { PropsWithChildren, useMemo } from 'react';
import { SelectableValue, VariableType } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { variableAdapters } from '../adapters';
import { VariableSelectField } from '../editor/VariableSelectField';
import { getVariableTypes } from '../utils';
@@ -18,11 +17,10 @@ export function VariableTypeSelect({ onChange, type }: PropsWithChildren<Props>)
return (
<VariableSelectField
name="Type"
name="Select variable type"
value={value}
options={options}
onChange={onChange}
tooltip={variableAdapters.get(type).description}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2}
/>
);

View File

@@ -1,9 +1,9 @@
import { css } from '@emotion/css';
import React, { MouseEvent, useCallback, useEffect, useState } from 'react';
import { GrafanaTheme } from '@grafana/data';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Button, InlineFieldRow, InlineLabel, useStyles, VerticalGroup } from '@grafana/ui';
import { Button, InlineFieldRow, InlineLabel, useStyles2 } from '@grafana/ui';
import { VariableOption, VariableWithOptions } from '../types';
@@ -23,7 +23,7 @@ export const VariableValuesPreview: React.FunctionComponent<VariableValuesPrevie
},
[previewLimit, setPreviewLimit]
);
const styles = useStyles(getStyles);
const styles = useStyles2(getStyles);
useEffect(() => setPreviewOptions(options.slice(0, previewLimit)), [previewLimit, options]);
if (!previewOptions.length) {
@@ -31,7 +31,7 @@ export const VariableValuesPreview: React.FunctionComponent<VariableValuesPrevie
}
return (
<VerticalGroup spacing="none">
<div style={{ display: 'flex', flexDirection: 'column', marginTop: '16px' }}>
<h5>Preview of values</h5>
<InlineFieldRow>
{previewOptions.map((o, index) => (
@@ -54,22 +54,27 @@ export const VariableValuesPreview: React.FunctionComponent<VariableValuesPrevie
</Button>
</InlineFieldRow>
)}
</VerticalGroup>
</div>
);
};
VariableValuesPreview.displayName = 'VariableValuesPreview';
function getStyles(theme: GrafanaTheme) {
function getStyles(theme: GrafanaTheme2) {
return {
optionContainer: css`
margin-left: ${theme.spacing.xs};
margin-bottom: ${theme.spacing.xs};
`,
label: css`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 50vw;
`,
wrapper: css({
display: 'flex',
flexDirection: 'column',
marginTop: theme.spacing(2),
}),
optionContainer: css({
marginLeft: theme.spacing(0.5),
marginBottom: theme.spacing(0.5),
}),
label: css({
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
maxWidth: '50vw',
}),
};
}

View File

@@ -1,113 +1,119 @@
import React, { ChangeEvent, FormEvent, PureComponent } from 'react';
import { css } from '@emotion/css';
import React, { ChangeEvent, FormEvent } from 'react';
import { SelectableValue } from '@grafana/data';
import { GrafanaTheme2, IntervalVariableModel, SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { InlineFieldRow, VerticalGroup } from '@grafana/ui';
import { useStyles2 } from '@grafana/ui';
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
import { VariableCheckboxField } from '../editor/VariableCheckboxField';
import { VariableLegend } from '../editor/VariableLegend';
import { VariableSelectField } from '../editor/VariableSelectField';
import { VariableSwitchField } from '../editor/VariableSwitchField';
import { VariableTextField } from '../editor/VariableTextField';
import { VariableEditorProps } from '../editor/types';
import { IntervalVariableModel } from '../types';
const STEP_OPTIONS = [1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500].map((count) => ({
label: `${count}`,
value: count,
}));
export interface Props extends VariableEditorProps<IntervalVariableModel> {}
export class IntervalVariableEditor extends PureComponent<Props> {
onAutoChange = (event: ChangeEvent<HTMLInputElement>) => {
this.props.onPropChange({
export const IntervalVariableEditor = React.memo(({ onPropChange, variable }: Props) => {
const onAutoChange = (event: ChangeEvent<HTMLInputElement>) => {
onPropChange({
propName: 'auto',
propValue: event.target.checked,
updateOptions: true,
});
};
onQueryChanged = (event: FormEvent<HTMLInputElement>) => {
this.props.onPropChange({
const onQueryChanged = (event: FormEvent<HTMLInputElement>) => {
onPropChange({
propName: 'query',
propValue: event.currentTarget.value,
});
};
onQueryBlur = (event: FormEvent<HTMLInputElement>) => {
this.props.onPropChange({
const onQueryBlur = (event: FormEvent<HTMLInputElement>) => {
onPropChange({
propName: 'query',
propValue: event.currentTarget.value,
updateOptions: true,
});
};
onAutoCountChanged = (option: SelectableValue<number>) => {
this.props.onPropChange({
const onAutoCountChanged = (option: SelectableValue<number>) => {
onPropChange({
propName: 'auto_count',
propValue: option.value,
updateOptions: true,
});
};
onAutoMinChanged = (event: FormEvent<HTMLInputElement>) => {
this.props.onPropChange({
const onAutoMinChanged = (event: FormEvent<HTMLInputElement>) => {
onPropChange({
propName: 'auto_min',
propValue: event.currentTarget.value,
updateOptions: true,
});
};
render() {
const { variable } = this.props;
const stepOptions = [1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500].map((count) => ({
label: `${count}`,
value: count,
}));
const stepValue = stepOptions.find((o) => o.value === variable.auto_count) ?? stepOptions[0];
const stepValue = STEP_OPTIONS.find((o) => o.value === variable.auto_count) ?? STEP_OPTIONS[0];
return (
<VerticalGroup spacing="xs">
<VariableSectionHeader name="Interval options" />
<VerticalGroup spacing="none">
<VariableTextField
value={this.props.variable.query}
name="Values"
placeholder="1m,10m,1h,6h,1d,7d"
onChange={this.onQueryChanged}
onBlur={this.onQueryBlur}
labelWidth={20}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.intervalsValueInput}
grow
required
const styles = useStyles2(getStyles);
return (
<>
<VariableLegend>Interval options</VariableLegend>
<VariableTextField
value={variable.query}
name="Values"
placeholder="1m,10m,1h,6h,1d,7d"
onChange={onQueryChanged}
onBlur={onQueryBlur}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.intervalsValueInput}
width={32}
required
/>
<VariableCheckboxField
value={variable.auto}
name="Auto option"
description="Dynamically calculates interval by dividing time range by the count specified"
onChange={onAutoChange}
/>
{variable.auto && (
<div className={styles.autoFields}>
<VariableSelectField
name="Step count"
description="How many times the current time range should be divided to calculate the value"
value={stepValue}
options={STEP_OPTIONS}
onChange={onAutoCountChanged}
width={9}
/>
<InlineFieldRow>
<VariableSwitchField
value={this.props.variable.auto}
name="Auto option"
tooltip="Dynamically calculates interval by dividing time range by the count specified."
onChange={this.onAutoChange}
/>
{this.props.variable.auto ? (
<>
<VariableSelectField
name="Step count"
value={stepValue}
options={stepOptions}
onChange={this.onAutoCountChanged}
tooltip="How many times the current time range should be divided to calculate the value."
labelWidth={7}
width={9}
/>
<VariableTextField
value={this.props.variable.auto_min}
name="Min interval"
placeholder="10s"
onChange={this.onAutoMinChanged}
tooltip="The calculated value will not go below this threshold."
labelWidth={13}
width={11}
/>
</>
) : null}
</InlineFieldRow>
</VerticalGroup>
</VerticalGroup>
);
}
<VariableTextField
value={variable.auto_min}
name="Min interval"
description="The calculated value will not go below this threshold"
placeholder="10s"
onChange={onAutoMinChanged}
width={11}
/>
</div>
)}
</>
);
});
IntervalVariableEditor.displayName = 'IntervalVariableEditor';
function getStyles(theme: GrafanaTheme2) {
return {
autoFields: css({
marginTop: theme.spacing(2),
display: 'flex',
flexDirection: 'column',
}),
};
}

View File

@@ -110,7 +110,7 @@ describe('QueryVariableEditor', () => {
const getQueryField = () =>
screen.getByRole('textbox', { name: /variable editor form default variable query editor textarea/i });
const getRegExField = () => screen.getByLabelText('Regex');
const getRegExField = () => screen.getByLabelText(/Regex/);
const fieldAccessors: Record<string, () => HTMLElement> = {
query: getQueryField,

View File

@@ -1,17 +1,16 @@
import { css } from '@emotion/css';
import React, { FormEvent, PureComponent } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { DataSourceInstanceSettings, getDataSourceRef, LoadingState, SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { DataSourcePicker, getTemplateSrv } from '@grafana/runtime';
import { InlineField, InlineFieldRow, VerticalGroup } from '@grafana/ui';
import { Field } from '@grafana/ui';
import { StoreState } from '../../../types';
import { getTimeSrv } from '../../dashboard/services/TimeSrv';
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
import { VariableTextField } from '../editor/VariableTextField';
import { VariableLegend } from '../editor/VariableLegend';
import { VariableTextAreaField } from '../editor/VariableTextAreaField';
import { initialVariableEditorState } from '../editor/reducer';
import { getQueryVariableEditorState } from '../editor/selectors';
import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
@@ -105,19 +104,19 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
}
};
onRegExChange = (event: FormEvent<HTMLInputElement>) => {
onRegExChange = (event: FormEvent<HTMLTextAreaElement>) => {
this.setState({ regex: event.currentTarget.value });
};
onRegExBlur = async (event: FormEvent<HTMLInputElement>) => {
onRegExBlur = async (event: FormEvent<HTMLTextAreaElement>) => {
const regex = event.currentTarget.value;
if (this.props.variable.regex !== regex) {
this.props.onPropChange({ propName: 'regex', propValue: regex, updateOptions: true });
}
};
onRefreshChange = (option: SelectableValue<VariableRefresh>) => {
this.props.onPropChange({ propName: 'refresh', propValue: option.value });
onRefreshChange = (option: VariableRefresh) => {
this.props.onPropChange({ propName: 'refresh', propValue: option });
};
onSortChange = async (option: SelectableValue<VariableSort>) => {
@@ -141,12 +140,14 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
if (isLegacyQueryEditor(VariableQueryEditor, datasource)) {
return (
<VariableQueryEditor
datasource={datasource}
query={query}
templateSrv={getTemplateSrv()}
onChange={this.onLegacyQueryChange}
/>
<Field label="Query">
<VariableQueryEditor
datasource={datasource}
query={query}
templateSrv={getTemplateSrv()}
onChange={this.onLegacyQueryChange}
/>
</Field>
);
}
@@ -154,16 +155,18 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
if (isQueryEditor(VariableQueryEditor, datasource)) {
return (
<VariableQueryEditor
datasource={datasource}
query={query}
onChange={this.onQueryChange}
onRunQuery={() => {}}
data={{ series: [], state: LoadingState.Done, timeRange: range }}
range={range}
onBlur={() => {}}
history={[]}
/>
<Field label="Query">
<VariableQueryEditor
datasource={datasource}
query={query}
onChange={this.onQueryChange}
onRunQuery={() => {}}
data={{ series: [], state: LoadingState.Done, timeRange: range }}
range={range}
onBlur={() => {}}
history={[]}
/>
</Field>
);
}
@@ -172,63 +175,55 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
render() {
return (
<VerticalGroup spacing="xs">
<VariableSectionHeader name="Query Options" />
<VerticalGroup spacing="lg">
<VerticalGroup spacing="none">
<InlineFieldRow>
<InlineField label="Data source" labelWidth={20} htmlFor="data-source-picker">
<DataSourcePicker
current={this.props.variable.datasource}
onChange={this.onDataSourceChange}
variables={true}
/>
</InlineField>
<QueryVariableRefreshSelect onChange={this.onRefreshChange} refresh={this.props.variable.refresh} />
</InlineFieldRow>
<div
className={css`
flex-direction: column;
width: 100%;
`}
>
{this.renderQueryEditor()}
</div>
<VariableTextField
value={this.state.regex ?? this.props.variable.regex}
name="Regex"
placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/"
onChange={this.onRegExChange}
onBlur={this.onRegExBlur}
labelWidth={20}
interactive={true}
tooltip={
<div>
Optional, if you want to extract part of a series name or metric node segment. Named capture groups
can be used to separate the display text and value (
<a
className="external-link"
href="https://grafana.com/docs/grafana/latest/variables/filter-variables-with-regex#filter-and-modify-using-named-text-and-value-capture-groups"
target="__blank"
>
see examples
</a>
).
</div>
}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2}
grow
/>
<QueryVariableSortSelect onChange={this.onSortChange} sort={this.props.variable.sort} />
</VerticalGroup>
<SelectionOptionsEditor
variable={this.props.variable}
onPropChange={this.onSelectionOptionsChange}
onMultiChanged={this.props.changeVariableMultiValue}
<>
<VariableLegend>Query options</VariableLegend>
<Field label="Data source" htmlFor="data-source-picker">
<DataSourcePicker
current={this.props.variable.datasource}
onChange={this.onDataSourceChange}
variables={true}
width={30}
/>
</VerticalGroup>
</VerticalGroup>
</Field>
{this.renderQueryEditor()}
<VariableTextAreaField
value={this.state.regex ?? this.props.variable.regex}
name="Regex"
description={
<div>
Optional, if you want to extract part of a series name or metric node segment.
<br />
Named capture groups can be used to separate the display text and value (
<a
className="external-link"
href="https://grafana.com/docs/grafana/latest/variables/filter-variables-with-regex#filter-and-modify-using-named-text-and-value-capture-groups"
target="__blank"
>
see examples
</a>
).
</div>
}
placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/"
onChange={this.onRegExChange}
onBlur={this.onRegExBlur}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2}
width={52}
/>
<QueryVariableSortSelect onChange={this.onSortChange} sort={this.props.variable.sort} />
<QueryVariableRefreshSelect onChange={this.onRefreshChange} refresh={this.props.variable.refresh} />
<VariableLegend>Selection options</VariableLegend>
<SelectionOptionsEditor
variable={this.props.variable}
onPropChange={this.onSelectionOptionsChange}
onMultiChanged={this.props.changeVariableMultiValue}
/>
</>
);
}
}

View File

@@ -1,13 +1,10 @@
import React, { PropsWithChildren, useMemo } from 'react';
import { SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { VariableSelectField } from '../editor/VariableSelectField';
import { VariableRefresh } from '../types';
import { VariableRefresh } from '@grafana/data';
import { Field, RadioButtonGroup } from '@grafana/ui';
interface Props {
onChange: (option: SelectableValue<VariableRefresh>) => void;
onChange: (option: VariableRefresh) => void;
refresh: VariableRefresh;
}
@@ -17,17 +14,14 @@ const REFRESH_OPTIONS = [
];
export function QueryVariableRefreshSelect({ onChange, refresh }: PropsWithChildren<Props>) {
const value = useMemo(() => REFRESH_OPTIONS.find((o) => o.value === refresh) ?? REFRESH_OPTIONS[0], [refresh]);
const value = useMemo(
() => REFRESH_OPTIONS.find((o) => o.value === refresh)?.value ?? REFRESH_OPTIONS[0].value,
[refresh]
);
return (
<VariableSelectField
name="Refresh"
value={value}
options={REFRESH_OPTIONS}
onChange={onChange}
labelWidth={10}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelectV2}
tooltip="When to update the values of this variable."
/>
<Field label="Refresh" description="When to update the values of this variable">
<RadioButtonGroup options={REFRESH_OPTIONS} onChange={onChange} value={value} />
</Field>
);
}

View File

@@ -27,12 +27,12 @@ export function QueryVariableSortSelect({ onChange, sort }: PropsWithChildren<Pr
return (
<VariableSelectField
name="Sort"
description="How to sort the values of this variable"
value={value}
options={SORT_OPTIONS}
onChange={onChange}
labelWidth={10}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelectV2}
tooltip="How to sort the values of this variable."
width={25}
/>
);
}

View File

@@ -1,9 +1,8 @@
import React, { FormEvent, ReactElement, useCallback } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { VerticalGroup } from '@grafana/ui';
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
import { VariableLegend } from '../editor/VariableLegend';
import { VariableTextField } from '../editor/VariableTextField';
import { VariableEditorProps } from '../editor/types';
import { TextBoxVariableModel } from '../types';
@@ -24,18 +23,17 @@ export function TextBoxVariableEditor({ onPropChange, variable: { query } }: Pro
const onBlur = useCallback((e: FormEvent<HTMLInputElement>) => updateVariable(e, true), [updateVariable]);
return (
<VerticalGroup spacing="xs">
<VariableSectionHeader name="Text options" />
<>
<VariableLegend>Text options</VariableLegend>
<VariableTextField
value={query}
name="Default value"
placeholder="default value, if any"
onChange={onChange}
onBlur={onBlur}
labelWidth={20}
grow
width={30}
testId={selectors.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInputV2}
/>
</VerticalGroup>
</>
);
}

View File

@@ -193,9 +193,10 @@ export function getVariableTypes(): Array<{ label: string; value: VariableType }
return variableAdapters
.list()
.filter((v) => v.id !== 'system')
.map(({ id, name }) => ({
.map(({ id, name, description }) => ({
label: name,
value: id,
description,
}));
}