mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard: Migration - EditVariable Settings: Implement Interval Variable (#81259)
* Extract logic from core IntervalEditor and create a new Form to be shared between scenes and core * Implement IntervalVariableEditor and refactor some utils functions * Add unit test
This commit is contained in:
parent
da1538ba82
commit
e3a648e107
@ -173,6 +173,9 @@ export const Pages = {
|
|||||||
},
|
},
|
||||||
IntervalVariable: {
|
IntervalVariable: {
|
||||||
intervalsValueInput: 'data-testid interval variable intervals input',
|
intervalsValueInput: 'data-testid interval variable intervals input',
|
||||||
|
autoEnabledCheckbox: 'data-testid interval variable auto value checkbox',
|
||||||
|
stepCountIntervalSelect: 'data-testid interval variable step count input',
|
||||||
|
minIntervalInput: 'data-testid interval variable mininum interval input',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -50,7 +50,7 @@ import { createPanelDataProvider } from '../utils/createPanelDataProvider';
|
|||||||
import { DashboardInteractions } from '../utils/interactions';
|
import { DashboardInteractions } from '../utils/interactions';
|
||||||
import {
|
import {
|
||||||
getCurrentValueForOldIntervalModel,
|
getCurrentValueForOldIntervalModel,
|
||||||
getIntervalsFromOldIntervalModel,
|
getIntervalsFromQueryString,
|
||||||
getVizPanelKeyForPanelId,
|
getVizPanelKeyForPanelId,
|
||||||
} from '../utils/utils';
|
} from '../utils/utils';
|
||||||
|
|
||||||
@ -350,7 +350,7 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
|
|||||||
hide: variable.hide,
|
hide: variable.hide,
|
||||||
});
|
});
|
||||||
} else if (variable.type === 'interval') {
|
} else if (variable.type === 'interval') {
|
||||||
const intervals = getIntervalsFromOldIntervalModel(variable);
|
const intervals = getIntervalsFromQueryString(variable.query);
|
||||||
const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals);
|
const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals);
|
||||||
return new IntervalVariable({
|
return new IntervalVariable({
|
||||||
...commonProperties,
|
...commonProperties,
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React, { ChangeEvent, FormEvent } from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||||
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import { useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { VariableCheckboxField } from './VariableCheckboxField';
|
||||||
|
import { VariableLegend } from './VariableLegend';
|
||||||
|
import { VariableSelectField } from './VariableSelectField';
|
||||||
|
import { VariableTextField } from './VariableTextField';
|
||||||
|
|
||||||
|
interface IntervalVariableFormProps {
|
||||||
|
intervals: string;
|
||||||
|
onIntervalsChange: (event: FormEvent<HTMLInputElement>) => void;
|
||||||
|
onAutoEnabledChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
onAutoMinIntervalChanged: (event: FormEvent<HTMLInputElement>) => void;
|
||||||
|
onAutoCountChanged: (option: SelectableValue) => void;
|
||||||
|
autoEnabled: boolean;
|
||||||
|
autoMinInterval: string;
|
||||||
|
autoStepCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IntervalVariableForm({
|
||||||
|
intervals,
|
||||||
|
onIntervalsChange,
|
||||||
|
onAutoEnabledChange,
|
||||||
|
onAutoMinIntervalChanged,
|
||||||
|
onAutoCountChanged,
|
||||||
|
autoEnabled,
|
||||||
|
autoMinInterval,
|
||||||
|
autoStepCount,
|
||||||
|
}: IntervalVariableFormProps) {
|
||||||
|
const STEP_OPTIONS = [1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500].map((count) => ({
|
||||||
|
label: `${count}`,
|
||||||
|
value: count,
|
||||||
|
}));
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
const stepCount = STEP_OPTIONS.find((option) => option.value === autoStepCount) ?? STEP_OPTIONS[0];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<VariableLegend>Interval options</VariableLegend>
|
||||||
|
<VariableTextField
|
||||||
|
defaultValue={intervals}
|
||||||
|
name="Values"
|
||||||
|
placeholder="1m,10m,1h,6h,1d,7d"
|
||||||
|
onBlur={onIntervalsChange}
|
||||||
|
testId={selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.intervalsValueInput}
|
||||||
|
width={32}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VariableCheckboxField
|
||||||
|
value={autoEnabled}
|
||||||
|
name="Auto option"
|
||||||
|
description="Dynamically calculates interval by dividing time range by the count specified"
|
||||||
|
onChange={onAutoEnabledChange}
|
||||||
|
testId={selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.autoEnabledCheckbox}
|
||||||
|
/>
|
||||||
|
{autoEnabled && (
|
||||||
|
<div className={styles.autoFields}>
|
||||||
|
<VariableSelectField
|
||||||
|
name="Step count"
|
||||||
|
description="How many times the current time range should be divided to calculate the value"
|
||||||
|
value={stepCount}
|
||||||
|
options={STEP_OPTIONS}
|
||||||
|
onChange={onAutoCountChanged}
|
||||||
|
width={9}
|
||||||
|
testId={selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.stepCountIntervalSelect}
|
||||||
|
/>
|
||||||
|
<VariableTextField
|
||||||
|
value={autoMinInterval}
|
||||||
|
name="Min interval"
|
||||||
|
description="The calculated value will not go below this threshold"
|
||||||
|
placeholder="10s"
|
||||||
|
onChange={onAutoMinIntervalChanged}
|
||||||
|
width={11}
|
||||||
|
testId={selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.minIntervalInput}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
autoFields: css({
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,115 @@
|
|||||||
|
// unit test for IntervalVariableEditor component
|
||||||
|
|
||||||
|
import { render, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import { IntervalVariable } from '@grafana/scenes';
|
||||||
|
|
||||||
|
import { IntervalVariableEditor } from './IntervalVariableEditor';
|
||||||
|
|
||||||
|
describe('IntervalVariableEditor', () => {
|
||||||
|
it('should render correctly', () => {
|
||||||
|
const variable = new IntervalVariable({
|
||||||
|
name: 'test',
|
||||||
|
type: 'interval',
|
||||||
|
intervals: ['1m', '10m', '1h', '6h', '1d', '7d'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const onRunQuery = jest.fn();
|
||||||
|
|
||||||
|
const { getByTestId, queryByTestId } = render(
|
||||||
|
<IntervalVariableEditor variable={variable} onRunQuery={onRunQuery} />
|
||||||
|
);
|
||||||
|
const intervalsInput = getByTestId(
|
||||||
|
selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.intervalsValueInput
|
||||||
|
);
|
||||||
|
const autoEnabledCheckbox = getByTestId(
|
||||||
|
selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.autoEnabledCheckbox
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(intervalsInput).toBeInTheDocument();
|
||||||
|
expect(intervalsInput).toHaveValue('1m,10m,1h,6h,1d,7d');
|
||||||
|
expect(autoEnabledCheckbox).toBeInTheDocument();
|
||||||
|
expect(autoEnabledCheckbox).not.toBeChecked();
|
||||||
|
expect(
|
||||||
|
queryByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.minIntervalInput)
|
||||||
|
).toBeNull();
|
||||||
|
expect(
|
||||||
|
queryByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.stepCountIntervalSelect)
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update intervals correctly', async () => {
|
||||||
|
const variable = new IntervalVariable({
|
||||||
|
name: 'test',
|
||||||
|
type: 'interval',
|
||||||
|
intervals: ['1m', '10m', '1h', '6h', '1d', '7d'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const onRunQuery = jest.fn();
|
||||||
|
|
||||||
|
const { user, getByTestId } = setup(<IntervalVariableEditor variable={variable} onRunQuery={onRunQuery} />);
|
||||||
|
const intervalsInput = getByTestId(
|
||||||
|
selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.intervalsValueInput
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.clear(intervalsInput);
|
||||||
|
await user.type(intervalsInput, '7d,30d, 1y, 5y, 10y');
|
||||||
|
await user.tab();
|
||||||
|
|
||||||
|
expect(intervalsInput).toBeInTheDocument();
|
||||||
|
expect(intervalsInput).toHaveValue('7d,30d, 1y, 5y, 10y');
|
||||||
|
expect(onRunQuery).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle auto enabled option correctly', async () => {
|
||||||
|
const variable = new IntervalVariable({
|
||||||
|
name: 'test',
|
||||||
|
type: 'interval',
|
||||||
|
intervals: ['1m', '10m', '1h', '6h', '1d', '7d'],
|
||||||
|
autoEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onRunQuery = jest.fn();
|
||||||
|
|
||||||
|
const { user, getByTestId, queryByTestId } = setup(
|
||||||
|
<IntervalVariableEditor variable={variable} onRunQuery={onRunQuery} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const autoEnabledCheckbox = getByTestId(
|
||||||
|
selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.autoEnabledCheckbox
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(autoEnabledCheckbox);
|
||||||
|
|
||||||
|
const minIntervalInput = getByTestId(
|
||||||
|
selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.minIntervalInput
|
||||||
|
);
|
||||||
|
|
||||||
|
const stepCountIntervalSelect = queryByTestId(
|
||||||
|
selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.stepCountIntervalSelect
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(autoEnabledCheckbox).toBeInTheDocument();
|
||||||
|
expect(autoEnabledCheckbox).toBeChecked();
|
||||||
|
expect(minIntervalInput).toBeInTheDocument();
|
||||||
|
expect(stepCountIntervalSelect).toBeInTheDocument();
|
||||||
|
expect(minIntervalInput).toHaveValue('10s');
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.clear(minIntervalInput);
|
||||||
|
await user.type(minIntervalInput, '10m');
|
||||||
|
await user.tab();
|
||||||
|
expect(minIntervalInput).toHaveValue('10m');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setup(jsx: JSX.Element) {
|
||||||
|
return {
|
||||||
|
user: userEvent.setup(),
|
||||||
|
...render(jsx),
|
||||||
|
};
|
||||||
|
}
|
@ -1,12 +1,53 @@
|
|||||||
import React from 'react';
|
import React, { ChangeEvent, FormEvent } from 'react';
|
||||||
|
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { IntervalVariable } from '@grafana/scenes';
|
import { IntervalVariable } from '@grafana/scenes';
|
||||||
|
import {
|
||||||
|
getIntervalsFromQueryString,
|
||||||
|
getIntervalsQueryFromNewIntervalModel,
|
||||||
|
} from 'app/features/dashboard-scene/utils/utils';
|
||||||
|
|
||||||
|
import { IntervalVariableForm } from '../components/IntervalVariableForm';
|
||||||
|
|
||||||
interface IntervalVariableEditorProps {
|
interface IntervalVariableEditorProps {
|
||||||
variable: IntervalVariable;
|
variable: IntervalVariable;
|
||||||
onChange: (variable: IntervalVariable) => void;
|
onRunQuery: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IntervalVariableEditor(props: IntervalVariableEditorProps) {
|
export function IntervalVariableEditor({ variable, onRunQuery }: IntervalVariableEditorProps) {
|
||||||
return <div>IntervalVariableEditor</div>;
|
const { intervals, autoStepCount, autoEnabled, autoMinInterval } = variable.useState();
|
||||||
|
|
||||||
|
//transform intervals array into string
|
||||||
|
const intervalsCombined = getIntervalsQueryFromNewIntervalModel(intervals);
|
||||||
|
|
||||||
|
const onIntervalsChange = (event: FormEvent<HTMLInputElement>) => {
|
||||||
|
const intervalsArray = getIntervalsFromQueryString(event.currentTarget.value);
|
||||||
|
variable.setState({ intervals: intervalsArray });
|
||||||
|
onRunQuery();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAutoCountChanged = (option: SelectableValue<number>) => {
|
||||||
|
variable.setState({ autoStepCount: option.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAutoEnabledChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
variable.setState({ autoEnabled: event.target.checked });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAutoMinIntervalChanged = (event: FormEvent<HTMLInputElement>) => {
|
||||||
|
variable.setState({ autoMinInterval: event.currentTarget.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntervalVariableForm
|
||||||
|
intervals={intervalsCombined}
|
||||||
|
autoStepCount={autoStepCount}
|
||||||
|
autoEnabled={autoEnabled}
|
||||||
|
onAutoCountChanged={onAutoCountChanged}
|
||||||
|
onIntervalsChange={onIntervalsChange}
|
||||||
|
onAutoEnabledChange={onAutoEnabledChange}
|
||||||
|
onAutoMinIntervalChanged={onAutoMinIntervalChanged}
|
||||||
|
autoMinInterval={autoMinInterval}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,8 @@ export function getVariableScene(type: EditableVariableType, initialState: Commo
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function hasVariableOptions(variable: SceneVariable): variable is MultiValueVariable {
|
export function hasVariableOptions(variable: SceneVariable): variable is MultiValueVariable {
|
||||||
return 'options' in variable.state;
|
// variable options can be defined by state.options or state.intervals in case of interval variable
|
||||||
|
return 'options' in variable.state || 'intervals' in variable.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefinition(model: SceneVariable): string {
|
export function getDefinition(model: SceneVariable): string {
|
||||||
|
@ -94,10 +94,10 @@ export function getMultiVariableValues(variable: MultiValueVariable) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform old interval model to new interval model from scenes
|
// used to transform old interval model to new interval model from scenes
|
||||||
export function getIntervalsFromOldIntervalModel(variable: IntervalVariableModel): string[] {
|
export function getIntervalsFromQueryString(query: string): string[] {
|
||||||
// separate intervals by quotes either single or double
|
// separate intervals by quotes either single or double
|
||||||
const matchIntervals = variable.query.match(/(["'])(.*?)\1|\w+/g);
|
const matchIntervals = query.match(/(["'])(.*?)\1|\w+/g);
|
||||||
|
|
||||||
// If no intervals are found in query, return the initial state of the interval reducer.
|
// If no intervals are found in query, return the initial state of the interval reducer.
|
||||||
if (!matchIntervals) {
|
if (!matchIntervals) {
|
||||||
|
@ -1,21 +1,10 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import React, { ChangeEvent, FormEvent } from 'react';
|
import React, { ChangeEvent, FormEvent } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2, IntervalVariableModel, SelectableValue } from '@grafana/data';
|
import { IntervalVariableModel, SelectableValue } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { IntervalVariableForm } from 'app/features/dashboard-scene/settings/variables/components/IntervalVariableForm';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { VariableCheckboxField } from '../../dashboard-scene/settings/variables/components/VariableCheckboxField';
|
|
||||||
import { VariableLegend } from '../../dashboard-scene/settings/variables/components/VariableLegend';
|
|
||||||
import { VariableSelectField } from '../../dashboard-scene/settings/variables/components/VariableSelectField';
|
|
||||||
import { VariableTextField } from '../../dashboard-scene/settings/variables/components/VariableTextField';
|
|
||||||
import { VariableEditorProps } from '../editor/types';
|
import { VariableEditorProps } from '../editor/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 interface Props extends VariableEditorProps<IntervalVariableModel> {}
|
||||||
|
|
||||||
export const IntervalVariableEditor = React.memo(({ onPropChange, variable }: Props) => {
|
export const IntervalVariableEditor = React.memo(({ onPropChange, variable }: Props) => {
|
||||||
@ -27,13 +16,6 @@ export const IntervalVariableEditor = React.memo(({ onPropChange, variable }: Pr
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onQueryChanged = (event: FormEvent<HTMLInputElement>) => {
|
|
||||||
onPropChange({
|
|
||||||
propName: 'query',
|
|
||||||
propValue: event.currentTarget.value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onQueryBlur = (event: FormEvent<HTMLInputElement>) => {
|
const onQueryBlur = (event: FormEvent<HTMLInputElement>) => {
|
||||||
onPropChange({
|
onPropChange({
|
||||||
propName: 'query',
|
propName: 'query',
|
||||||
@ -58,62 +40,18 @@ export const IntervalVariableEditor = React.memo(({ onPropChange, variable }: Pr
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const stepValue = STEP_OPTIONS.find((o) => o.value === variable.auto_count) ?? STEP_OPTIONS[0];
|
|
||||||
|
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<IntervalVariableForm
|
||||||
<VariableLegend>Interval options</VariableLegend>
|
intervals={variable.query}
|
||||||
<VariableTextField
|
autoStepCount={variable.auto_count}
|
||||||
value={variable.query}
|
autoEnabled={variable.auto}
|
||||||
name="Values"
|
onAutoCountChanged={onAutoCountChanged}
|
||||||
placeholder="1m,10m,1h,6h,1d,7d"
|
onIntervalsChange={onQueryBlur}
|
||||||
onChange={onQueryChanged}
|
onAutoEnabledChange={onAutoChange}
|
||||||
onBlur={onQueryBlur}
|
onAutoMinIntervalChanged={onAutoMinChanged}
|
||||||
testId={selectors.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.intervalsValueInput}
|
autoMinInterval={variable.auto_min}
|
||||||
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}
|
|
||||||
/>
|
|
||||||
<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';
|
IntervalVariableEditor.displayName = 'IntervalVariableEditor';
|
||||||
|
|
||||||
function getStyles(theme: GrafanaTheme2) {
|
|
||||||
return {
|
|
||||||
autoFields: css({
|
|
||||||
marginTop: theme.spacing(2),
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user