Commit 85a04794 by Isa Ozler Committed by GitHub

Field config API: add slider editor (#28007)

* Field config: implementation slider editor (#27592)

* PR-28007 feedback fixed

* Field config: implementation slider editor (#27592)

* PR-28007 feedback fixed

* processed review PR-28007

* Field config: implementation slider editor (#27592)

* PR-28007 feedback fixed

* Field config: implementation slider editor (#27592)

* processed review PR-28007

* fixing leftover number[] bugs

* RichHistoryQueriesTab.test fix + slider vertical feat fixed

* fixed Slider.test.tsx expectation

* Added @docs to prevent build-frontend-docs from failing

Co-authored-by: Isa Ozler <contactme@isaozler.com>
parent 1bff2fde
...@@ -24,6 +24,12 @@ export const numberOverrideProcessor = ( ...@@ -24,6 +24,12 @@ export const numberOverrideProcessor = (
return parseFloat(value); return parseFloat(value);
}; };
export interface SliderFieldConfigSettings {
min: number;
max: number;
step?: number;
}
export interface DataLinksFieldConfigSettings {} export interface DataLinksFieldConfigSettings {}
export const dataLinksOverrideProcessor = ( export const dataLinksOverrideProcessor = (
......
import { ComponentType } from 'react'; import { ComponentType } from 'react';
import { RegistryItem, Registry } from '../utils/Registry'; import { RegistryItem, Registry } from '../utils/Registry';
import { NumberFieldConfigSettings, SelectFieldConfigSettings, StringFieldConfigSettings } from '../field'; import {
NumberFieldConfigSettings,
SliderFieldConfigSettings,
SelectFieldConfigSettings,
StringFieldConfigSettings,
} from '../field';
/** /**
* Option editor registry item * Option editor registry item
...@@ -71,6 +76,10 @@ export interface OptionsUIRegistryBuilderAPI< ...@@ -71,6 +76,10 @@ export interface OptionsUIRegistryBuilderAPI<
config: OptionEditorConfig<TOptions, TSettings, number> config: OptionEditorConfig<TOptions, TSettings, number>
): this; ): this;
addSliderInput?<TSettings extends SliderFieldConfigSettings = SliderFieldConfigSettings>(
config: OptionEditorConfig<TOptions, TSettings, number>
): this;
addTextInput?<TSettings extends StringFieldConfigSettings = StringFieldConfigSettings>( addTextInput?<TSettings extends StringFieldConfigSettings = StringFieldConfigSettings>(
config: OptionEditorConfig<TOptions, TSettings, string> config: OptionEditorConfig<TOptions, TSettings, string>
): this; ): this;
......
...@@ -12,6 +12,7 @@ import { ...@@ -12,6 +12,7 @@ import {
StandardEditorProps, StandardEditorProps,
StringFieldConfigSettings, StringFieldConfigSettings,
NumberFieldConfigSettings, NumberFieldConfigSettings,
SliderFieldConfigSettings,
ColorFieldConfigSettings, ColorFieldConfigSettings,
identityOverrideProcessor, identityOverrideProcessor,
UnitFieldConfigSettings, UnitFieldConfigSettings,
...@@ -39,6 +40,18 @@ export class FieldConfigEditorBuilder<TOptions> extends OptionsUIRegistryBuilder ...@@ -39,6 +40,18 @@ export class FieldConfigEditorBuilder<TOptions> extends OptionsUIRegistryBuilder
}); });
} }
addSliderInput<TSettings>(config: FieldConfigEditorConfig<TOptions, TSettings & SliderFieldConfigSettings, number>) {
return this.addCustomEditor({
...config,
id: config.path,
override: standardEditorsRegistry.get('slider').editor as any,
editor: standardEditorsRegistry.get('slider').editor as any,
process: numberOverrideProcessor,
shouldApply: config.shouldApply ? config.shouldApply : field => field.type === FieldType.number,
settings: config.settings || {},
});
}
addTextInput<TSettings>(config: FieldConfigEditorConfig<TOptions, TSettings & StringFieldConfigSettings, string>) { addTextInput<TSettings>(config: FieldConfigEditorConfig<TOptions, TSettings & StringFieldConfigSettings, string>) {
return this.addCustomEditor({ return this.addCustomEditor({
...config, ...config,
...@@ -136,6 +149,14 @@ export class PanelOptionsEditorBuilder<TOptions> extends OptionsUIRegistryBuilde ...@@ -136,6 +149,14 @@ export class PanelOptionsEditorBuilder<TOptions> extends OptionsUIRegistryBuilde
}); });
} }
addSliderInput<TSettings>(config: PanelOptionsEditorConfig<TOptions, TSettings & SliderFieldConfigSettings, number>) {
return this.addCustomEditor({
...config,
id: config.path,
editor: standardEditorsRegistry.get('slider').editor as any,
});
}
addTextInput<TSettings>(config: PanelOptionsEditorConfig<TOptions, TSettings & StringFieldConfigSettings, string>) { addTextInput<TSettings>(config: PanelOptionsEditorConfig<TOptions, TSettings & StringFieldConfigSettings, string>) {
return this.addCustomEditor({ return this.addCustomEditor({
...config, ...config,
......
import React, { useCallback } from 'react';
import { FieldConfigEditorProps, SliderFieldConfigSettings } from '@grafana/data';
import { Slider } from '../Slider/Slider';
export const SliderValueEditor: React.FC<FieldConfigEditorProps<number, SliderFieldConfigSettings>> = ({
value,
onChange,
item,
}) => {
const { settings } = item;
const onValueAfterChange = useCallback(
(value?: number) => {
onChange(value);
},
[onChange]
);
const initialValue = typeof value === 'number' ? value : typeof value === 'string' ? +value : 0;
return (
<Slider
value={initialValue}
min={settings?.min || 0}
max={settings?.max || 100}
step={settings?.step}
onAfterChange={onValueAfterChange}
/>
);
};
import { Meta, Props } from '@storybook/addon-docs/blocks';
import { RangeSliderProps } from './types';
<Meta title="MDX|RangeSlider" />
# Range-slider
The `Range-slider` component is an input element where users can manipulate two values on a one-dimensional axis.
`Range-slider` can be implemented in horizontal or vertical orientation. You can set the default starting values for the slider with the `value` prop.
<Props of={RangeSliderProps} />
import React from 'react';
import { RangeSlider } from '@grafana/ui';
import { select, number, boolean } from '@storybook/addon-knobs';
export default {
title: 'Forms/Slider/Range',
component: RangeSlider,
};
const getKnobs = () => {
return {
min: number('min', 0),
max: number('max', 100),
step: boolean('enable step', false),
orientation: select('orientation', ['horizontal', 'vertical'], 'horizontal'),
reverse: boolean('reverse', false),
};
};
const SliderWrapper = () => {
const { min, max, orientation, reverse, step } = getKnobs();
const stepValue = step ? 10 : undefined;
return (
<div style={{ width: '200px', height: '200px' }}>
<RangeSlider min={min} max={max} step={stepValue} orientation={orientation} value={[10, 20]} reverse={reverse} />
</div>
);
};
export const basic = () => <SliderWrapper />;
import React from 'react';
import { RangeSlider } from './RangeSlider';
import { RangeSliderProps } from './types';
import { render } from '@testing-library/react';
const sliderProps: RangeSliderProps = {
min: 10,
max: 20,
};
describe('RangeSlider', () => {
it('renders without error', () => {
expect(() => {
render(<RangeSlider {...sliderProps} />);
});
});
});
import React, { FunctionComponent } from 'react';
import { Range as RangeComponent, createSliderWithTooltip } from 'rc-slider';
import { cx } from 'emotion';
import { Global } from '@emotion/core';
import { useTheme } from '../../themes/ThemeContext';
import { getStyles } from './styles';
import { RangeSliderProps } from './types';
/**
* @public
*
* RichHistoryQueriesTab uses this Range Component
*/
export const RangeSlider: FunctionComponent<RangeSliderProps> = ({
min,
max,
onChange,
onAfterChange,
orientation = 'horizontal',
reverse,
step,
formatTooltipResult,
value,
tooltipAlwaysVisible = true,
}) => {
const isHorizontal = orientation === 'horizontal';
const theme = useTheme();
const styles = getStyles(theme, isHorizontal);
const RangeWithTooltip = createSliderWithTooltip(RangeComponent);
return (
<div className={cx(styles.container, styles.slider)}>
{/** Slider tooltip's parent component is body and therefore we need Global component to do css overrides for it. */}
<Global styles={styles.tooltip} />
<RangeWithTooltip
tipProps={{
visible: tooltipAlwaysVisible,
placement: isHorizontal ? 'top' : 'right',
}}
min={min}
max={max}
step={step}
defaultValue={value}
tipFormatter={(value: number) => (formatTooltipResult ? formatTooltipResult(value) : value)}
onChange={onChange}
onAfterChange={onAfterChange}
vertical={!isHorizontal}
reverse={reverse}
/>
</div>
);
};
RangeSlider.displayName = 'Range';
import { Meta, Props } from '@storybook/addon-docs/blocks'; import { Meta, Props } from '@storybook/addon-docs/blocks';
import { Slider } from './Slider'; import { SliderProps } from './types';
<Meta title="MDX|Slider" /> <Meta title="MDX|Slider" />
# Slider # Slider
The `Slider` component is an input element where users can manipulate one or two values on a one-dimensional axis. The `Slider` component is an input element where users can manipulate one value on a one-dimensional axis.
`Slider` can be implemented in horizontal or vertical orientation. You can set the default starting value(s) for the slider with the `value` prop. `Slider` can be implemented in horizontal or vertical orientation. You can set the default starting value(s) for the slider with the `value` prop.
<Props of={Slider} /> <Props of={SliderProps} />
...@@ -13,24 +13,16 @@ const getKnobs = () => { ...@@ -13,24 +13,16 @@ const getKnobs = () => {
max: number('max', 100), max: number('max', 100),
step: boolean('enable step', false), step: boolean('enable step', false),
orientation: select('orientation', ['horizontal', 'vertical'], 'horizontal'), orientation: select('orientation', ['horizontal', 'vertical'], 'horizontal'),
reverse: boolean('reverse', true), reverse: boolean('reverse', false),
singleValue: boolean('single value', false),
}; };
}; };
const SliderWrapper = () => { const SliderWrapper = () => {
const { min, max, orientation, reverse, singleValue, step } = getKnobs(); const { min, max, orientation, reverse, step } = getKnobs();
const stepValue = step ? 10 : undefined; const stepValue = step ? 10 : undefined;
return ( return (
<div style={{ width: '200px', height: '200px' }}> <div style={{ width: '200px', height: '200px' }}>
<Slider <Slider min={min} max={max} step={stepValue} orientation={orientation} value={10} reverse={reverse} />
min={min}
max={max}
step={stepValue}
orientation={orientation}
value={singleValue ? [10] : undefined}
reverse={reverse}
/>
</div> </div>
); );
}; };
......
import React from 'react'; import React from 'react';
import { Slider, Props } from './Slider'; import { Slider } from './Slider';
import { SliderProps } from './types';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
const sliderProps: Props = { const sliderProps: SliderProps = {
min: 10, min: 10,
max: 20, max: 20,
}; };
...@@ -17,11 +18,10 @@ describe('Slider', () => { ...@@ -17,11 +18,10 @@ describe('Slider', () => {
expect(wrapper.html()).toContain('aria-valuemin="10"'); expect(wrapper.html()).toContain('aria-valuemin="10"');
expect(wrapper.html()).toContain('aria-valuemax="20"'); expect(wrapper.html()).toContain('aria-valuemax="20"');
expect(wrapper.html()).toContain('aria-valuenow="10"'); expect(wrapper.html()).toContain('aria-valuenow="10"');
expect(wrapper.html()).toContain('aria-valuenow="20"');
}); });
it('renders correct contents with a value', () => { it('renders correct contents with a value', () => {
const wrapper = mount(<Slider {...sliderProps} value={[15]} />); const wrapper = mount(<Slider {...sliderProps} value={15} />);
expect(wrapper.html()).toContain('aria-valuenow="15"'); expect(wrapper.html()).toContain('aria-valuenow="15"');
expect(wrapper.html()).not.toContain('aria-valuenow="20"'); expect(wrapper.html()).not.toContain('aria-valuenow="20"');
expect(wrapper.html()).not.toContain('aria-valuenow="10"'); expect(wrapper.html()).not.toContain('aria-valuenow="10"');
......
import React, { FunctionComponent } from 'react'; import React, { useState, useCallback, ChangeEvent, FunctionComponent } from 'react';
import { Range, createSliderWithTooltip } from 'rc-slider'; import SliderComponent from 'rc-slider';
import { cx, css } from 'emotion'; import { cx } from 'emotion';
import { Global, css as cssCore } from '@emotion/core'; import { Global } from '@emotion/core';
import { stylesFactory } from '../../themes';
import { GrafanaTheme } from '@grafana/data';
import { useTheme } from '../../themes/ThemeContext'; import { useTheme } from '../../themes/ThemeContext';
import { Orientation } from '../../types/orientation'; import { getStyles } from './styles';
import { SliderProps } from './types';
export interface Props { /**
min: number; * @public
max: number; */
orientation?: Orientation; export const Slider: FunctionComponent<SliderProps> = ({
/** Set current positions of handle(s). If only 1 value supplied, only 1 handle displayed. */
value?: number[];
reverse?: boolean;
step?: number;
tooltipAlwaysVisible?: boolean;
formatTooltipResult?: (value: number) => number | string;
onChange?: (values: number[]) => void;
onAfterChange?: (values: number[]) => void;
}
const getStyles = stylesFactory((theme: GrafanaTheme, isHorizontal: boolean) => {
const trackColor = theme.isLight ? theme.palette.gray5 : theme.palette.dark6;
const container = isHorizontal
? css`
width: 100%;
margin: ${theme.spacing.lg} ${theme.spacing.sm} ${theme.spacing.sm} ${theme.spacing.sm};
`
: css`
height: 100%;
margin: ${theme.spacing.sm} ${theme.spacing.lg} ${theme.spacing.sm} ${theme.spacing.sm};
`;
return {
container,
slider: css`
.rc-slider-vertical .rc-slider-handle {
margin-top: -10px;
}
.rc-slider-handle {
border: solid 2px ${theme.palette.blue77};
background-color: ${theme.palette.blue77};
}
.rc-slider-handle:hover {
border-color: ${theme.palette.blue77};
}
.rc-slider-handle:focus {
border-color: ${theme.palette.blue77};
box-shadow: none;
}
.rc-slider-handle:active {
border-color: ${theme.palette.blue77};
box-shadow: none;
}
.rc-slider-handle-click-focused:focus {
border-color: ${theme.palette.blue77};
}
.rc-slider-dot-active {
border-color: ${theme.palette.blue77};
}
.rc-slider-track {
background-color: ${theme.palette.blue77};
}
.rc-slider-rail {
background-color: ${trackColor};
border: 1px solid ${trackColor};
}
`,
/** Global component from @emotion/core doesn't accept computed classname string returned from css from emotion.
* It accepts object containing the computed name and flattened styles returned from css from @emotion/core
* */
tooltip: cssCore`
body {
.rc-slider-tooltip {
cursor: grab;
user-select: none;
z-index: ${theme.zIndex.tooltip};
}
.rc-slider-tooltip-inner {
color: ${theme.colors.text};
background-color: transparent !important;
border-radius: 0;
box-shadow: none;
}
.rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow {
display: none;
}
.rc-slider-tooltip-placement-top {
padding: 0;
}
}
`,
};
});
export const Slider: FunctionComponent<Props> = ({
min, min,
max, max,
onChange, onChange,
...@@ -106,33 +17,63 @@ export const Slider: FunctionComponent<Props> = ({ ...@@ -106,33 +17,63 @@ export const Slider: FunctionComponent<Props> = ({
orientation = 'horizontal', orientation = 'horizontal',
reverse, reverse,
step, step,
formatTooltipResult,
value, value,
tooltipAlwaysVisible = true,
}) => { }) => {
const isHorizontal = orientation === 'horizontal'; const isHorizontal = orientation === 'horizontal';
const theme = useTheme(); const theme = useTheme();
const styles = getStyles(theme, isHorizontal); const styles = getStyles(theme, isHorizontal);
const RangeWithTooltip = createSliderWithTooltip(Range); const SliderWithTooltip = SliderComponent;
const [slidervalue, setSliderValue] = useState<number>(value || min);
const onSliderChange = useCallback((v: number) => {
setSliderValue(v);
if (onChange) {
onChange(v);
}
}, []);
const onSliderInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
let v = +e.target.value;
v > max && (v = max);
v < min && (v = min);
setSliderValue(v);
if (onChange) {
onChange(v);
}
if (onAfterChange) {
onAfterChange(v);
}
}, []);
const sliderInputClassNames = !isHorizontal ? [styles.sliderInputVertical] : [];
const sliderInputFieldClassNames = !isHorizontal ? [styles.sliderInputFieldVertical] : [];
return ( return (
<div className={cx(styles.container, styles.slider)}> <div className={cx(styles.container, styles.slider)}>
{/** Slider tooltip's parent component is body and therefore we need Global component to do css overrides for it. */} {/** Slider tooltip's parent component is body and therefore we need Global component to do css overrides for it. */}
<Global styles={styles.tooltip} /> <Global styles={styles.tooltip} />
<RangeWithTooltip <label className={cx(styles.sliderInput, ...sliderInputClassNames)}>
tipProps={{ <SliderWithTooltip
visible: tooltipAlwaysVisible, min={min}
placement: isHorizontal ? 'top' : 'right', max={max}
}} step={step}
min={min} defaultValue={value}
max={max} value={slidervalue}
step={step} onChange={onSliderChange}
defaultValue={value || [min, max]} onAfterChange={onAfterChange}
tipFormatter={(value: number) => (formatTooltipResult ? formatTooltipResult(value) : value)} vertical={!isHorizontal}
onChange={onChange} reverse={reverse}
onAfterChange={onAfterChange} />
vertical={!isHorizontal} <input
reverse={reverse} className={cx(styles.sliderInputField, ...sliderInputFieldClassNames)}
/> type="number"
value={`${slidervalue}`} // to fix the react leading zero issue
onChange={onSliderInputChange}
min={min}
max={max}
/>
</label>
</div> </div>
); );
}; };
......
import { stylesFactory } from '../../themes';
import { GrafanaTheme } from '@grafana/data';
import { focusCss } from '../../themes/mixins';
import { css as cssCore } from '@emotion/core';
import { css } from 'emotion';
export const getFocusStyle = (theme: GrafanaTheme) => css`
&:focus {
${focusCss(theme)}
}
`;
export const getStyles = stylesFactory((theme: GrafanaTheme, isHorizontal: boolean) => {
const trackColor = theme.isLight ? theme.palette.gray5 : theme.palette.dark6;
const container = isHorizontal
? css`
width: 100%;
`
: css`
height: 100%;
margin: ${theme.spacing.sm} ${theme.spacing.lg} ${theme.spacing.sm} ${theme.spacing.sm};
`;
return {
container,
slider: css`
.rc-slider {
display: flex;
flex-grow: 1;
margin-left: 7px; // half the size of the handle to align handle to the left on 0 value
}
.rc-slider-vertical .rc-slider-handle {
margin-top: -10px;
}
.rc-slider-handle {
border: solid 2px ${theme.palette.blue77};
background-color: ${theme.palette.blue77};
}
.rc-slider-handle:hover {
border-color: ${theme.palette.blue77};
}
.rc-slider-handle:focus {
border-color: ${theme.palette.blue77};
box-shadow: none;
}
.rc-slider-handle:active {
border-color: ${theme.palette.blue77};
box-shadow: none;
}
.rc-slider-handle-click-focused:focus {
border-color: ${theme.palette.blue77};
}
.rc-slider-dot-active {
border-color: ${theme.palette.blue77};
}
.rc-slider-track {
background-color: ${theme.palette.blue77};
}
.rc-slider-rail {
background-color: ${trackColor};
border: 1px solid ${trackColor};
}
`,
/** Global component from @emotion/core doesn't accept computed classname string returned from css from emotion.
* It accepts object containing the computed name and flattened styles returned from css from @emotion/core
* */
tooltip: cssCore`
body {
.rc-slider-tooltip {
cursor: grab;
user-select: none;
z-index: ${theme.zIndex.tooltip};
}
.rc-slider-tooltip-inner {
color: ${theme.colors.text};
background-color: transparent !important;
border-radius: 0;
box-shadow: none;
}
.rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow {
display: none;
}
.rc-slider-tooltip-placement-top {
padding: 0;
}
}
`,
sliderInput: css`
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
`,
sliderInputVertical: css`
flex-direction: column;
height: 100%;
.rc-slider {
margin: 0;
order: 2;
}
`,
sliderInputField: css`
display: flex;
flex-grow: 0;
flex-basis: 50px;
margin-left: ${theme.spacing.lg};
height: ${theme.spacing.formInputHeight}px;
text-align: center;
border-radius: ${theme.border.radius.sm};
border: 1px solid ${theme.colors.formInputBorder};
${getFocusStyle(theme)};
`,
sliderInputFieldVertical: css`
margin: 0 0 ${theme.spacing.lg} 0;
order: 1;
`,
};
});
import { Orientation } from '../../types/orientation';
export interface SliderProps {
min: number;
max: number;
orientation?: Orientation;
/** Set current positions of handle(s). If only 1 value supplied, only 1 handle displayed. */
value?: number;
reverse?: boolean;
step?: number;
tooltipAlwaysVisible?: boolean;
formatTooltipResult?: (value: number) => number;
onChange?: (value: number) => void;
onAfterChange?: (value?: number) => void;
}
export interface RangeSliderProps {
min: number;
max: number;
orientation?: Orientation;
/** Set current positions of handle(s). If only 1 value supplied, only 1 handle displayed. */
value?: number[];
reverse?: boolean;
step?: number;
tooltipAlwaysVisible?: boolean;
formatTooltipResult?: (value: number) => number | string;
onChange?: (value: number[]) => void;
onAfterChange?: (value: number[]) => void;
}
...@@ -126,11 +126,13 @@ export { default as Chart } from './Chart'; ...@@ -126,11 +126,13 @@ export { default as Chart } from './Chart';
export { TooltipContainer } from './Chart/TooltipContainer'; export { TooltipContainer } from './Chart/TooltipContainer';
export { Drawer } from './Drawer/Drawer'; export { Drawer } from './Drawer/Drawer';
export { Slider } from './Slider/Slider'; export { Slider } from './Slider/Slider';
export { RangeSlider } from './Slider/RangeSlider';
// TODO: namespace!! // TODO: namespace!!
export { StringValueEditor } from './OptionsUI/string'; export { StringValueEditor } from './OptionsUI/string';
export { StringArrayEditor } from './OptionsUI/strings'; export { StringArrayEditor } from './OptionsUI/strings';
export { NumberValueEditor } from './OptionsUI/number'; export { NumberValueEditor } from './OptionsUI/number';
export { SliderValueEditor } from './OptionsUI/slider';
export { SelectValueEditor } from './OptionsUI/select'; export { SelectValueEditor } from './OptionsUI/select';
export { FieldConfigItemHeaderTitle } from './FieldConfigs/FieldConfigItemHeaderTitle'; export { FieldConfigItemHeaderTitle } from './FieldConfigs/FieldConfigItemHeaderTitle';
......
...@@ -25,6 +25,7 @@ import { ...@@ -25,6 +25,7 @@ import {
import { Switch } from '../components/Switch/Switch'; import { Switch } from '../components/Switch/Switch';
import { import {
NumberValueEditor, NumberValueEditor,
SliderValueEditor,
RadioButtonGroup, RadioButtonGroup,
StringValueEditor, StringValueEditor,
StringArrayEditor, StringArrayEditor,
...@@ -229,6 +230,13 @@ export const getStandardOptionEditors = () => { ...@@ -229,6 +230,13 @@ export const getStandardOptionEditors = () => {
editor: NumberValueEditor as any, editor: NumberValueEditor as any,
}; };
const slider: StandardEditorsRegistryItem<number> = {
id: 'slider',
name: 'Slider',
description: 'Allows numeric values input',
editor: SliderValueEditor as any,
};
const text: StandardEditorsRegistryItem<string> = { const text: StandardEditorsRegistryItem<string> = {
id: 'text', id: 'text',
name: 'Text', name: 'Text',
...@@ -323,6 +331,7 @@ export const getStandardOptionEditors = () => { ...@@ -323,6 +331,7 @@ export const getStandardOptionEditors = () => {
return [ return [
text, text,
number, number,
slider,
boolean, boolean,
radio, radio,
select, select,
......
...@@ -3,7 +3,7 @@ import { mount } from 'enzyme'; ...@@ -3,7 +3,7 @@ import { mount } from 'enzyme';
import { ExploreId } from '../../../types/explore'; import { ExploreId } from '../../../types/explore';
import { SortOrder } from 'app/core/utils/richHistory'; import { SortOrder } from 'app/core/utils/richHistory';
import { RichHistoryQueriesTab, Props } from './RichHistoryQueriesTab'; import { RichHistoryQueriesTab, Props } from './RichHistoryQueriesTab';
import { Slider } from '@grafana/ui'; import { RangeSlider } from '@grafana/ui';
jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() })); jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() }));
...@@ -30,7 +30,7 @@ describe('RichHistoryQueriesTab', () => { ...@@ -30,7 +30,7 @@ describe('RichHistoryQueriesTab', () => {
describe('slider', () => { describe('slider', () => {
it('should render slider', () => { it('should render slider', () => {
const wrapper = setup(); const wrapper = setup();
expect(wrapper.find(Slider)).toHaveLength(1); expect(wrapper.find(RangeSlider)).toHaveLength(1);
}); });
it('should render slider with correct timerange', () => { it('should render slider with correct timerange', () => {
const wrapper = setup(); const wrapper = setup();
......
...@@ -20,7 +20,7 @@ import { ...@@ -20,7 +20,7 @@ import {
// Components // Components
import RichHistoryCard from './RichHistoryCard'; import RichHistoryCard from './RichHistoryCard';
import { sortOrderOptions } from './RichHistory'; import { sortOrderOptions } from './RichHistory';
import { Slider, Select } from '@grafana/ui'; import { RangeSlider, Select } from '@grafana/ui';
import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
export interface Props { export interface Props {
...@@ -186,7 +186,7 @@ export function RichHistoryQueriesTab(props: Props) { ...@@ -186,7 +186,7 @@ export function RichHistoryQueriesTab(props: Props) {
<div className="label-slider">Filter history</div> <div className="label-slider">Filter history</div>
<div className="label-slider">{mapNumbertoTimeInSlider(timeFilter[0])}</div> <div className="label-slider">{mapNumbertoTimeInSlider(timeFilter[0])}</div>
<div className="slider"> <div className="slider">
<Slider <RangeSlider
tooltipAlwaysVisible={false} tooltipAlwaysVisible={false}
min={0} min={0}
max={retentionPeriod} max={retentionPeriod}
......
...@@ -24,23 +24,14 @@ export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPane ...@@ -24,23 +24,14 @@ export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPane
description: '', description: '',
defaultValue: true, defaultValue: true,
}) })
.addSelect({ .addSliderInput({
path: 'line.width', path: 'line.width',
name: 'Line width', name: 'Line width',
defaultValue: 1, defaultValue: 1,
settings: { settings: {
options: [ min: 1,
{ value: 1, label: '1 • thin' }, max: 10,
{ value: 2, label: '2' }, step: 1,
{ value: 3, label: '3' },
{ value: 4, label: '4' },
{ value: 5, label: '5' },
{ value: 6, label: '6' },
{ value: 7, label: '7' },
{ value: 8, label: '8' },
{ value: 9, label: '9' },
{ value: 10, label: '10 • thick' },
],
}, },
showIf: c => { showIf: c => {
return c.line.show; return c.line.show;
...@@ -52,23 +43,14 @@ export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPane ...@@ -52,23 +43,14 @@ export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPane
description: '', description: '',
defaultValue: false, defaultValue: false,
}) })
.addSelect({ .addSliderInput({
path: 'points.radius', path: 'points.radius',
name: 'Point radius', name: 'Point radius',
defaultValue: 4, defaultValue: 4,
settings: { settings: {
options: [ min: 1,
{ value: 1, label: '1 • thin' }, max: 10,
{ value: 2, label: '2' }, step: 1,
{ value: 3, label: '3' },
{ value: 4, label: '4' },
{ value: 5, label: '5' },
{ value: 6, label: '6' },
{ value: 7, label: '7' },
{ value: 8, label: '8' },
{ value: 9, label: '9' },
{ value: 10, label: '10 • thick' },
],
}, },
showIf: c => c.points.show, showIf: c => c.points.show,
}) })
...@@ -78,24 +60,14 @@ export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPane ...@@ -78,24 +60,14 @@ export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPane
description: '', description: '',
defaultValue: false, defaultValue: false,
}) })
.addSelect({ .addSliderInput({
path: 'fill.alpha', path: 'fill.alpha',
name: 'Fill area opacity', name: 'Fill area opacity',
defaultValue: 0.1, defaultValue: 0.1,
settings: { settings: {
options: [ min: 0,
{ value: 0, label: 'No Fill' }, max: 1,
{ value: 0.1, label: '10% • transparent' }, step: 0.1,
{ value: 0.2, label: '20%' },
{ value: 0.3, label: '30%' },
{ value: 0.4, label: '40% ' },
{ value: 0.5, label: '50%' },
{ value: 0.6, label: '60%' },
{ value: 0.7, label: '70%' },
{ value: 0.8, label: '80%' },
{ value: 0.9, label: '90%' },
{ value: 1, label: '100% • opaque' },
],
}, },
}) })
.addTextInput({ .addTextInput({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment