mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Add component: Cascader (#21410)
* Rename old cascader * Change name of old cascader * Add basic cascader without search * Add basic cascader without search * Flatten options to make it searchable * Add regex search and make backspace work * Add barebone search without styles * Add SearchResult list * Add search navigation * Rewrite of cascader and add some things to SelectBase * Make SelectBase controlllable * Cleanup * Add initial value functionality * Add onblur to hand caret direction * New storyboom format for ButtonCascader * Add knobs to story * Add story and docs for UnitPicker * Make UnitPicker use Cascader * Fix backspace issue and empty value * Fix backspace issue for real * Remove unused code * Fix focus issue * Change children to items and remove ButtonCascaderProps * Remove local CascaderOption * Fix failed test * Revert UnitPicker changes and change format for ButtonCascader * Fix failing tests
This commit is contained in:
parent
20aac7f04b
commit
aa0982da56
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { withKnobs, text, boolean, object } from '@storybook/addon-knobs';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { ButtonCascader } from './ButtonCascader';
|
||||
|
||||
export default {
|
||||
title: 'UI/ButtonCascader',
|
||||
component: ButtonCascader,
|
||||
decorators: [withKnobs, withCenteredStory],
|
||||
};
|
||||
|
||||
const getKnobs = () => {
|
||||
return {
|
||||
disabled: boolean('Disabled', false),
|
||||
text: text('Button Text', 'Click me!'),
|
||||
options: object('Options', [
|
||||
{
|
||||
label: 'A',
|
||||
value: 'A',
|
||||
children: [
|
||||
{ label: 'B', value: 'B' },
|
||||
{ label: 'C', value: 'C' },
|
||||
],
|
||||
},
|
||||
{ label: 'D', value: 'D' },
|
||||
]),
|
||||
};
|
||||
};
|
||||
|
||||
export const simple = () => {
|
||||
const { disabled, text, options } = getKnobs();
|
||||
return <ButtonCascader disabled={disabled} options={options} value={['A']} expandIcon={null} buttonText={text} />;
|
||||
};
|
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { Button } from '../Forms/Button';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
|
||||
// @ts-ignore
|
||||
import RCCascader from 'rc-cascader';
|
||||
import { CascaderOption } from '../Cascader/Cascader';
|
||||
|
||||
export interface ButtonCascaderProps {
|
||||
options: CascaderOption[];
|
||||
buttonText: string;
|
||||
disabled?: boolean;
|
||||
expandIcon?: React.ReactNode;
|
||||
value?: string[];
|
||||
|
||||
loadData?: (selectedOptions: CascaderOption[]) => void;
|
||||
onChange?: (value: string[], selectedOptions: CascaderOption[]) => void;
|
||||
onPopupVisibleChange?: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
export const ButtonCascader: React.FC<ButtonCascaderProps> = props => (
|
||||
<RCCascader {...props} fieldNames={{ label: 'label', value: 'value', children: 'items' }}>
|
||||
<Button variant="secondary" disabled={props.disabled}>
|
||||
{props.buttonText} <Icon name="caret-down" />
|
||||
</Button>
|
||||
</RCCascader>
|
||||
);
|
8
packages/grafana-ui/src/components/Cascader/Cascader.mdx
Normal file
8
packages/grafana-ui/src/components/Cascader/Cascader.mdx
Normal file
@ -0,0 +1,8 @@
|
||||
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
|
||||
import { Cascader } from './Cascader';
|
||||
|
||||
# Cascader with search
|
||||
|
||||
<Meta title="MDX|Cascader" component={Cascader} />
|
||||
|
||||
<Props of={Cascader}/>
|
@ -1,32 +1,49 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { text, boolean, object } from '@storybook/addon-knobs';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { Cascader } from './Cascader';
|
||||
// import { Button } from '../Button';
|
||||
import mdx from './Cascader.mdx';
|
||||
import React from 'react';
|
||||
|
||||
const getKnobs = () => {
|
||||
return {
|
||||
disabled: boolean('Disabled', false),
|
||||
text: text('Button Text', 'Click me!'),
|
||||
options: object('Options', [
|
||||
{
|
||||
label: 'A',
|
||||
value: 'A',
|
||||
children: [
|
||||
{ label: 'B', value: 'B' },
|
||||
{ label: 'C', value: 'C' },
|
||||
],
|
||||
},
|
||||
{ label: 'D', value: 'D' },
|
||||
]),
|
||||
};
|
||||
export default {
|
||||
title: 'UI/Cascader',
|
||||
component: Cascader,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const CascaderStories = storiesOf('UI/Cascader', module);
|
||||
const options = [
|
||||
{
|
||||
label: 'First',
|
||||
value: '1',
|
||||
items: [
|
||||
{
|
||||
label: 'Second',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: 'Third',
|
||||
value: '3',
|
||||
},
|
||||
{
|
||||
label: 'Fourth',
|
||||
value: '4',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'FirstFirst',
|
||||
value: '5',
|
||||
},
|
||||
];
|
||||
|
||||
CascaderStories.addDecorator(withCenteredStory);
|
||||
|
||||
CascaderStories.add('default', () => {
|
||||
const { disabled, text, options } = getKnobs();
|
||||
return <Cascader disabled={disabled} options={options} value={['A']} expandIcon={null} buttonText={text} />;
|
||||
});
|
||||
export const simple = () => (
|
||||
<Cascader separator={text('Separator', '')} options={options} onSelect={val => console.log(val)} />
|
||||
);
|
||||
export const withInitialValue = () => (
|
||||
<Cascader options={options} initialValue="3" onSelect={val => console.log(val)} />
|
||||
);
|
||||
|
@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import { Cascader } from './Cascader';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: 'First',
|
||||
value: '1',
|
||||
items: [
|
||||
{
|
||||
label: 'Second',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: 'Third',
|
||||
value: '3',
|
||||
},
|
||||
{
|
||||
label: 'Fourth',
|
||||
value: '4',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'FirstFirst',
|
||||
value: '5',
|
||||
},
|
||||
];
|
||||
|
||||
const flatOptions = [
|
||||
{
|
||||
label: 'First / Second',
|
||||
value: ['1', '2'],
|
||||
},
|
||||
{
|
||||
label: 'First / Third',
|
||||
value: ['1', '3'],
|
||||
},
|
||||
{
|
||||
label: 'First / Fourth',
|
||||
value: ['1', '4'],
|
||||
},
|
||||
{
|
||||
label: 'FirstFirst',
|
||||
value: ['5'],
|
||||
},
|
||||
];
|
||||
|
||||
describe('Cascader', () => {
|
||||
let cascader: any;
|
||||
beforeEach(() => {
|
||||
cascader = shallow(<Cascader options={options} onSelect={() => {}} />);
|
||||
});
|
||||
|
||||
it('Should convert options to searchable strings', () => {
|
||||
expect(cascader.state('searchableOptions')).toEqual(flatOptions);
|
||||
});
|
||||
});
|
@ -1,34 +1,204 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Icon } from '../Icon/Icon';
|
||||
// @ts-ignore
|
||||
import RCCascader from 'rc-cascader';
|
||||
|
||||
export interface CascaderOption {
|
||||
label: string;
|
||||
value: string;
|
||||
import { Select } from '../Forms/Select/Select';
|
||||
import { FormInputSize } from '../Forms/types';
|
||||
import { Input } from '../Forms/Input/Input';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
|
||||
children?: CascaderOption[];
|
||||
interface CascaderProps {
|
||||
separator?: string;
|
||||
options: CascaderOption[];
|
||||
onSelect(val: string): void;
|
||||
size?: FormInputSize;
|
||||
initialValue?: string;
|
||||
}
|
||||
|
||||
interface CascaderState {
|
||||
isSearching: boolean;
|
||||
searchableOptions: Array<SelectableValue<string[]>>;
|
||||
focusCascade: boolean;
|
||||
//Array for cascade navigation
|
||||
rcValue: SelectableValue<string[]>;
|
||||
activeLabel: string;
|
||||
}
|
||||
|
||||
export interface CascaderOption {
|
||||
value: any;
|
||||
label: string;
|
||||
items?: CascaderOption[];
|
||||
disabled?: boolean;
|
||||
// Undocumented tooltip API
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface CascaderProps {
|
||||
options: CascaderOption[];
|
||||
buttonText: string;
|
||||
disabled?: boolean;
|
||||
expandIcon?: React.ReactNode;
|
||||
value?: string[];
|
||||
|
||||
loadData?: (selectedOptions: CascaderOption[]) => void;
|
||||
onChange?: (value: string[], selectedOptions: CascaderOption[]) => void;
|
||||
onPopupVisibleChange?: (visible: boolean) => void;
|
||||
const disableDivFocus = css(`
|
||||
&:focus{
|
||||
outline: none;
|
||||
}
|
||||
`);
|
||||
|
||||
export const Cascader: React.FC<CascaderProps> = props => (
|
||||
<RCCascader {...props}>
|
||||
<button className="gf-form-label gf-form-label--btn" disabled={props.disabled}>
|
||||
{props.buttonText} <i className="fa fa-caret-down" />
|
||||
</button>
|
||||
</RCCascader>
|
||||
);
|
||||
export class Cascader extends React.PureComponent<CascaderProps, CascaderState> {
|
||||
constructor(props: CascaderProps) {
|
||||
super(props);
|
||||
const searchableOptions = this.flattenOptions(props.options);
|
||||
const { rcValue, activeLabel } = this.setInitialValue(searchableOptions, props.initialValue);
|
||||
this.state = {
|
||||
isSearching: false,
|
||||
focusCascade: false,
|
||||
searchableOptions,
|
||||
rcValue,
|
||||
activeLabel,
|
||||
};
|
||||
}
|
||||
|
||||
flattenOptions = (options: CascaderOption[], optionPath: CascaderOption[] = []) => {
|
||||
let selectOptions: Array<SelectableValue<string[]>> = [];
|
||||
for (const option of options) {
|
||||
const cpy = [...optionPath];
|
||||
cpy.push(option);
|
||||
if (!option.items) {
|
||||
selectOptions.push({
|
||||
label: cpy.map(o => o.label).join(this.props.separator || ' / '),
|
||||
value: cpy.map(o => o.value),
|
||||
});
|
||||
} else {
|
||||
selectOptions = [...selectOptions, ...this.flattenOptions(option.items, cpy)];
|
||||
}
|
||||
}
|
||||
return selectOptions;
|
||||
};
|
||||
|
||||
setInitialValue(searchableOptions: Array<SelectableValue<string[]>>, initValue?: string) {
|
||||
if (!initValue) {
|
||||
return { rcValue: [], activeLabel: '' };
|
||||
}
|
||||
for (const option of searchableOptions) {
|
||||
const optionPath = option.value || [];
|
||||
|
||||
if (optionPath.indexOf(initValue) === optionPath.length - 1) {
|
||||
return {
|
||||
rcValue: optionPath,
|
||||
activeLabel: option.label || '',
|
||||
};
|
||||
}
|
||||
}
|
||||
return { rcValue: [], activeLabel: '' };
|
||||
}
|
||||
|
||||
//For rc-cascader
|
||||
onChange = (value: string[], selectedOptions: CascaderOption[]) => {
|
||||
this.setState({
|
||||
rcValue: value,
|
||||
activeLabel: selectedOptions.map(o => o.label).join(this.props.separator || ' / '),
|
||||
});
|
||||
|
||||
this.props.onSelect(selectedOptions[selectedOptions.length - 1].value);
|
||||
};
|
||||
|
||||
//For select
|
||||
onSelect = (obj: SelectableValue<string[]>) => {
|
||||
this.setState({
|
||||
activeLabel: obj.label || '',
|
||||
rcValue: obj.value || [],
|
||||
isSearching: false,
|
||||
});
|
||||
this.props.onSelect(this.state.rcValue[this.state.rcValue.length - 1]);
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
this.setState({
|
||||
focusCascade: true,
|
||||
});
|
||||
};
|
||||
|
||||
onBlur = () => {
|
||||
this.setState({
|
||||
isSearching: false,
|
||||
focusCascade: false,
|
||||
});
|
||||
|
||||
if (this.state.activeLabel === '') {
|
||||
this.setState({
|
||||
rcValue: [],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onBlurCascade = () => {
|
||||
this.setState({
|
||||
focusCascade: false,
|
||||
});
|
||||
};
|
||||
|
||||
onInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (
|
||||
e.key !== 'ArrowDown' &&
|
||||
e.key !== 'ArrowUp' &&
|
||||
e.key !== 'Enter' &&
|
||||
e.key !== 'ArrowLeft' &&
|
||||
e.key !== 'ArrowRight'
|
||||
) {
|
||||
this.setState({
|
||||
focusCascade: false,
|
||||
isSearching: true,
|
||||
});
|
||||
if (e.key === 'Backspace') {
|
||||
const label = this.state.activeLabel || '';
|
||||
this.setState({
|
||||
activeLabel: label.slice(0, -1),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onInputChange = (value: string) => {
|
||||
this.setState({
|
||||
activeLabel: value,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { size } = this.props;
|
||||
const { focusCascade, isSearching, searchableOptions, rcValue, activeLabel } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isSearching ? (
|
||||
<Select
|
||||
inputValue={activeLabel}
|
||||
placeholder="Search"
|
||||
autoFocus={!focusCascade}
|
||||
onChange={this.onSelect}
|
||||
onInputChange={this.onInputChange}
|
||||
onBlur={this.onBlur}
|
||||
options={searchableOptions}
|
||||
size={size || 'md'}
|
||||
/>
|
||||
) : (
|
||||
<RCCascader
|
||||
onChange={this.onChange}
|
||||
onClick={this.onClick}
|
||||
options={this.props.options}
|
||||
isFocused={focusCascade}
|
||||
onBlur={this.onBlurCascade}
|
||||
value={rcValue}
|
||||
fieldNames={{ label: 'label', value: 'value', children: 'items' }}
|
||||
>
|
||||
<div className={disableDivFocus}>
|
||||
<Input
|
||||
value={activeLabel}
|
||||
onKeyDown={this.onInputKeyDown}
|
||||
onChange={() => {}}
|
||||
size={size || 'md'}
|
||||
suffix={focusCascade ? <Icon name="caret-up" /> : <Icon name="caret-down" />}
|
||||
/>
|
||||
</div>
|
||||
</RCCascader>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
import { Button, ButtonVariant, ButtonProps } from '../Button';
|
||||
import { ButtonSize } from '../../Button/types';
|
||||
import { SelectCommonProps, SelectBase } from './SelectBase';
|
||||
import { SelectCommonProps, SelectBase, CustomControlProps } from './SelectBase';
|
||||
import { css } from 'emotion';
|
||||
import { useTheme } from '../../../themes';
|
||||
import { Icon } from '../../Icon/Icon';
|
||||
@ -73,13 +73,13 @@ export function ButtonSelect<T>({
|
||||
return (
|
||||
<SelectBase
|
||||
{...selectProps}
|
||||
renderControl={({ onBlur, onClick, value, isOpen }) => {
|
||||
renderControl={React.forwardRef<any, CustomControlProps<T>>(({ onBlur, onClick, value, isOpen }, _ref) => {
|
||||
return (
|
||||
<SelectButton {...buttonProps} onBlur={onBlur} onClick={onClick} isOpen={isOpen}>
|
||||
{value ? value.label : placeholder}
|
||||
</SelectButton>
|
||||
);
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -27,10 +27,13 @@ export interface SelectCommonProps<T> {
|
||||
className?: string;
|
||||
options?: Array<SelectableValue<T>>;
|
||||
defaultValue?: any;
|
||||
inputValue?: string;
|
||||
value?: SelectValue<T>;
|
||||
getOptionLabel?: (item: SelectableValue<T>) => string;
|
||||
getOptionValue?: (item: SelectableValue<T>) => string;
|
||||
onChange: (value: SelectableValue<T>) => {} | void;
|
||||
onInputChange?: (label: string) => void;
|
||||
onKeyDown?: (event: React.KeyboardEvent) => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
isSearchable?: boolean;
|
||||
@ -131,9 +134,12 @@ const CustomControl = (props: any) => {
|
||||
export function SelectBase<T>({
|
||||
value,
|
||||
defaultValue,
|
||||
inputValue,
|
||||
onInputChange,
|
||||
options = [],
|
||||
onChange,
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
onCloseMenu,
|
||||
onOpenMenu,
|
||||
placeholder = 'Choose',
|
||||
@ -201,6 +207,8 @@ export function SelectBase<T>({
|
||||
isLoading,
|
||||
menuIsOpen: isOpen,
|
||||
defaultValue,
|
||||
inputValue,
|
||||
onInputChange,
|
||||
value: isMulti ? selectedValue : selectedValue[0],
|
||||
getOptionLabel,
|
||||
getOptionValue,
|
||||
@ -214,6 +222,7 @@ export function SelectBase<T>({
|
||||
options,
|
||||
onChange,
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
menuShouldScrollIntoView: false,
|
||||
renderControl,
|
||||
};
|
||||
|
@ -0,0 +1,6 @@
|
||||
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
|
||||
import { UnitPicker } from './UnitPicker';
|
||||
|
||||
# UnitPicker
|
||||
|
||||
<Props of={UnitPicker}/>
|
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { UnitPicker } from './UnitPicker';
|
||||
import mdx from './UnitPicker.mdx';
|
||||
|
||||
export default {
|
||||
title: 'UI/UnitPicker',
|
||||
component: UnitPicker,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {
|
||||
docs: mdx,
|
||||
},
|
||||
};
|
||||
|
||||
export const simple = () => <UnitPicker onChange={val => console.log(val)} />;
|
@ -1,5 +1,5 @@
|
||||
@import 'BarGauge/BarGauge';
|
||||
@import 'Cascader/Cascader';
|
||||
@import 'ButtonCascader/ButtonCascader';
|
||||
@import 'ColorPicker/ColorPicker';
|
||||
@import 'CustomScrollbar/CustomScrollbar';
|
||||
@import 'Drawer/Drawer';
|
||||
|
@ -14,6 +14,7 @@ export { IndicatorsContainer } from './Select/IndicatorsContainer';
|
||||
export { NoOptionsMessage } from './Select/NoOptionsMessage';
|
||||
export { default as resetSelectStyles } from './Forms/Select/resetSelectStyles';
|
||||
export { ButtonSelect } from './Select/ButtonSelect';
|
||||
export { ButtonCascader } from './ButtonCascader/ButtonCascader';
|
||||
export { Cascader, CascaderOption } from './Cascader/Cascader';
|
||||
|
||||
// Forms
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ExploreQueryFieldProps } from '@grafana/data';
|
||||
import { Cascader, CascaderOption } from '@grafana/ui';
|
||||
import { ButtonCascader, CascaderOption } from '@grafana/ui';
|
||||
|
||||
import InfluxQueryModel from '../influx_query_model';
|
||||
import { AdHocFilterField, KeyValuePair } from 'app/features/explore/AdHocFilterField';
|
||||
@ -75,7 +75,7 @@ export class InfluxLogsQueryField extends React.PureComponent<Props, State> {
|
||||
measurements.push({
|
||||
label: measurementObj.text,
|
||||
value: measurementObj.text,
|
||||
children: fields,
|
||||
items: fields,
|
||||
});
|
||||
}
|
||||
this.setState({ measurements });
|
||||
@ -134,7 +134,7 @@ export class InfluxLogsQueryField extends React.PureComponent<Props, State> {
|
||||
return (
|
||||
<div className="gf-form-inline gf-form-inline--nowrap">
|
||||
<div className="gf-form flex-shrink-0">
|
||||
<Cascader
|
||||
<ButtonCascader
|
||||
buttonText={cascadeText}
|
||||
options={measurements}
|
||||
disabled={!hasMeasurement}
|
||||
|
@ -2,7 +2,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Cascader,
|
||||
ButtonCascader,
|
||||
CascaderOption,
|
||||
SlatePrism,
|
||||
TypeaheadOutput,
|
||||
@ -148,7 +148,7 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<Cascader
|
||||
<ButtonCascader
|
||||
options={logLabelOptions || []}
|
||||
disabled={buttonDisabled}
|
||||
buttonText={chooserText}
|
||||
|
@ -9,7 +9,7 @@ describe('groupMetricsByPrefix()', () => {
|
||||
expect(groupMetricsByPrefix(['foo_metric'])).toMatchObject([
|
||||
{
|
||||
value: 'foo',
|
||||
children: [
|
||||
items: [
|
||||
{
|
||||
value: 'foo_metric',
|
||||
},
|
||||
@ -22,7 +22,7 @@ describe('groupMetricsByPrefix()', () => {
|
||||
expect(groupMetricsByPrefix(['foo_metric'], { foo_metric: [{ type: 'TYPE', help: 'my help' }] })).toMatchObject([
|
||||
{
|
||||
value: 'foo',
|
||||
children: [
|
||||
items: [
|
||||
{
|
||||
value: 'foo_metric',
|
||||
title: 'foo_metric\nTYPE\nmy help',
|
||||
@ -44,7 +44,7 @@ describe('groupMetricsByPrefix()', () => {
|
||||
expect(groupMetricsByPrefix([':foo_metric:'])).toMatchObject([
|
||||
{
|
||||
value: RECORDING_RULES_GROUP,
|
||||
children: [
|
||||
items: [
|
||||
{
|
||||
value: ':foo_metric:',
|
||||
},
|
||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||
|
||||
import { Plugin } from 'slate';
|
||||
import {
|
||||
Cascader,
|
||||
ButtonCascader,
|
||||
CascaderOption,
|
||||
SlatePrism,
|
||||
TypeaheadInput,
|
||||
@ -52,7 +52,7 @@ export function groupMetricsByPrefix(metrics: string[], metadata?: PromMetricsMe
|
||||
const rulesOption = {
|
||||
label: 'Recording rules',
|
||||
value: RECORDING_RULES_GROUP,
|
||||
children: ruleNames
|
||||
items: ruleNames
|
||||
.slice()
|
||||
.sort()
|
||||
.map(name => ({ label: name, value: name })),
|
||||
@ -69,7 +69,7 @@ export function groupMetricsByPrefix(metrics: string[], metadata?: PromMetricsMe
|
||||
const prefixIsMetric = metricsForPrefix.length === 1 && metricsForPrefix[0] === prefix;
|
||||
const children = prefixIsMetric ? [] : metricsForPrefix.sort().map(m => addMetricsMetadata(m, metadata));
|
||||
return {
|
||||
children,
|
||||
items: children,
|
||||
label: prefix,
|
||||
value: prefix,
|
||||
};
|
||||
@ -198,7 +198,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
||||
onChangeMetrics = (values: string[], selectedOptions: CascaderOption[]) => {
|
||||
let query;
|
||||
if (selectedOptions.length === 1) {
|
||||
if (selectedOptions[0].children.length === 0) {
|
||||
if (selectedOptions[0].items.length === 0) {
|
||||
query = selectedOptions[0].value;
|
||||
} else {
|
||||
// Ignore click on group
|
||||
@ -254,10 +254,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
||||
const histogramOptions = histogramMetrics.map((hm: any) => ({ label: hm, value: hm }));
|
||||
const metricsOptions =
|
||||
histogramMetrics.length > 0
|
||||
? [
|
||||
{ label: 'Histograms', value: HISTOGRAM_GROUP, children: histogramOptions, isLeaf: false },
|
||||
...metricsByPrefix,
|
||||
]
|
||||
? [{ label: 'Histograms', value: HISTOGRAM_GROUP, items: histogramOptions, isLeaf: false }, ...metricsByPrefix]
|
||||
: metricsByPrefix;
|
||||
|
||||
// Hint for big disabled lookups
|
||||
@ -302,7 +299,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
||||
<>
|
||||
<div className="gf-form-inline gf-form-inline--nowrap flex-grow-1">
|
||||
<div className="gf-form flex-shrink-0">
|
||||
<Cascader
|
||||
<ButtonCascader
|
||||
options={metricsOptions}
|
||||
buttonText={chooserText}
|
||||
disabled={buttonDisabled}
|
||||
|
Loading…
Reference in New Issue
Block a user