Commit e0229045 by Erik Sundell Committed by GitHub

UI: Use SelectableValue as Segment value (#20867)

* Use SelectableValue for segment value

* Update cloudwatch components to use new segment props
parent b111fee6
...@@ -18,8 +18,9 @@ const toOption = (value: any) => ({ label: value, value: value }); ...@@ -18,8 +18,9 @@ const toOption = (value: any) => ({ label: value, value: value });
SegmentStories.add('Array Options', () => { SegmentStories.add('Array Options', () => {
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption); const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
options[0].label = 'Option1 Label';
return ( return (
<UseState initialState={options[0].value}> <UseState initialState={options[0] as SelectableValue}>
{(value, updateValue) => ( {(value, updateValue) => (
<> <>
<div className="gf-form-inline"> <div className="gf-form-inline">
...@@ -29,9 +30,9 @@ SegmentStories.add('Array Options', () => { ...@@ -29,9 +30,9 @@ SegmentStories.add('Array Options', () => {
<Segment <Segment
value={value} value={value}
options={options} options={options}
onChange={(value: SelectableValue<string>) => { onChange={item => {
updateValue(value); updateValue(item);
action('Segment value changed')(value); action('Segment value changed')(item.value);
}} }}
/> />
<Segment <Segment
...@@ -53,7 +54,7 @@ const groupedOptions = [ ...@@ -53,7 +54,7 @@ const groupedOptions = [
SegmentStories.add('Grouped Array Options', () => { SegmentStories.add('Grouped Array Options', () => {
return ( return (
<UseState initialState={groupedOptions[0].options[0].value}> <UseState initialState={groupedOptions[0].options[0] as SelectableValue}>
{(value, updateValue) => ( {(value, updateValue) => (
<> <>
<div className="gf-form-inline"> <div className="gf-form-inline">
...@@ -63,9 +64,9 @@ SegmentStories.add('Grouped Array Options', () => { ...@@ -63,9 +64,9 @@ SegmentStories.add('Grouped Array Options', () => {
<Segment <Segment
value={value} value={value}
options={groupedOptions} options={groupedOptions}
onChange={(value: SelectableValue<string>) => { onChange={item => {
updateValue(value); updateValue(item);
action('Segment value changed')(value); action('Segment value changed')(item.value);
}} }}
/> />
<Segment <Segment
...@@ -83,7 +84,7 @@ SegmentStories.add('Grouped Array Options', () => { ...@@ -83,7 +84,7 @@ SegmentStories.add('Grouped Array Options', () => {
SegmentStories.add('With custom options allowed', () => { SegmentStories.add('With custom options allowed', () => {
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption); const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
return ( return (
<UseState initialState={options[0].value}> <UseState initialState={options[0] as SelectableValue}>
{(value, updateValue) => ( {(value, updateValue) => (
<> <>
<div className="gf-form-inline"> <div className="gf-form-inline">
...@@ -94,9 +95,9 @@ SegmentStories.add('With custom options allowed', () => { ...@@ -94,9 +95,9 @@ SegmentStories.add('With custom options allowed', () => {
allowCustomValue allowCustomValue
value={value} value={value}
options={options} options={options}
onChange={(value: SelectableValue<string>) => { onChange={item => {
updateValue(value); updateValue(item);
action('Segment value changed')(value); action('Segment value changed')(item.value);
}} }}
/> />
<Segment <Segment
...@@ -112,11 +113,11 @@ SegmentStories.add('With custom options allowed', () => { ...@@ -112,11 +113,11 @@ SegmentStories.add('With custom options allowed', () => {
); );
}); });
const CustomLabelComponent = ({ value }: any) => <div className="gf-form-label">custom({value})</div>; const CustomLabelComponent = ({ value: { value } }: any) => <div className="gf-form-label">custom({value})</div>;
SegmentStories.add('Custom Label Field', () => { SegmentStories.add('Custom Label Field', () => {
return ( return (
<UseState initialState={groupedOptions[0].options[0].value}> <UseState initialState={groupedOptions[0].options[0] as SelectableValue}>
{(value, setValue) => ( {(value, setValue) => (
<> <>
<div className="gf-form-inline"> <div className="gf-form-inline">
...@@ -126,9 +127,9 @@ SegmentStories.add('Custom Label Field', () => { ...@@ -126,9 +127,9 @@ SegmentStories.add('Custom Label Field', () => {
<Segment <Segment
Component={<CustomLabelComponent value={value} />} Component={<CustomLabelComponent value={value} />}
options={groupedOptions} options={groupedOptions}
onChange={(value: SelectableValue<string>) => { onChange={item => {
setValue(value); setValue(item);
action('Segment value changed')(value); action('Segment value changed')(item.value);
}} }}
/> />
<Segment <Segment
......
...@@ -18,18 +18,23 @@ export function Segment<T>({ ...@@ -18,18 +18,23 @@ export function Segment<T>({
const [Label, width, expanded, setExpanded] = useExpandableLabel(false); const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
if (!expanded) { if (!expanded) {
return <Label Component={Component || <a className={cx('gf-form-label', 'query-part', className)}>{value}</a>} />; return (
<Label
Component={Component || <a className={cx('gf-form-label', 'query-part', className)}>{value && value.label}</a>}
/>
);
} }
return ( return (
<SegmentSelect <SegmentSelect
width={width} value={value}
options={options} options={options}
width={width}
onClickOutside={() => setExpanded(false)} onClickOutside={() => setExpanded(false)}
allowCustomValue={allowCustomValue} allowCustomValue={allowCustomValue}
onChange={value => { onChange={item => {
setExpanded(false); setExpanded(false);
onChange(value); onChange(item);
}} }}
/> />
); );
......
...@@ -20,7 +20,7 @@ const loadOptions = (options: any): Promise<Array<SelectableValue<string>>> => ...@@ -20,7 +20,7 @@ const loadOptions = (options: any): Promise<Array<SelectableValue<string>>> =>
SegmentStories.add('Array Options', () => { SegmentStories.add('Array Options', () => {
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption); const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
return ( return (
<UseState initialState={options[0].value}> <UseState initialState={options[0] as SelectableValue}>
{(value, updateValue) => ( {(value, updateValue) => (
<> <>
<div className="gf-form-inline"> <div className="gf-form-inline">
...@@ -30,9 +30,9 @@ SegmentStories.add('Array Options', () => { ...@@ -30,9 +30,9 @@ SegmentStories.add('Array Options', () => {
<SegmentAsync <SegmentAsync
value={value} value={value}
loadOptions={() => loadOptions(options)} loadOptions={() => loadOptions(options)}
onChange={value => { onChange={item => {
updateValue(value); updateValue(item);
action('Segment value changed')(value); action('Segment value changed')(item.value);
}} }}
/> />
<SegmentAsync <SegmentAsync
...@@ -54,7 +54,7 @@ const groupedOptions = [ ...@@ -54,7 +54,7 @@ const groupedOptions = [
SegmentStories.add('Grouped Array Options', () => { SegmentStories.add('Grouped Array Options', () => {
return ( return (
<UseState initialState={groupedOptions[0].options[0].value}> <UseState initialState={groupedOptions[0].options[0] as SelectableValue}>
{(value, updateValue) => ( {(value, updateValue) => (
<> <>
<div className="gf-form-inline"> <div className="gf-form-inline">
...@@ -64,9 +64,9 @@ SegmentStories.add('Grouped Array Options', () => { ...@@ -64,9 +64,9 @@ SegmentStories.add('Grouped Array Options', () => {
<SegmentAsync <SegmentAsync
value={value} value={value}
loadOptions={() => loadOptions(groupedOptions)} loadOptions={() => loadOptions(groupedOptions)}
onChange={value => { onChange={item => {
updateValue(value); updateValue(item);
action('Segment value changed')(value); action('Segment value changed')(item.value);
}} }}
/> />
<SegmentAsync <SegmentAsync
...@@ -84,7 +84,7 @@ SegmentStories.add('Grouped Array Options', () => { ...@@ -84,7 +84,7 @@ SegmentStories.add('Grouped Array Options', () => {
SegmentStories.add('With custom options allowed', () => { SegmentStories.add('With custom options allowed', () => {
const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption); const options = ['Option1', 'Option2', 'OptionWithLooongLabel', 'Option4'].map(toOption);
return ( return (
<UseState initialState={options[0].value}> <UseState initialState={options[0] as SelectableValue}>
{(value, updateValue) => ( {(value, updateValue) => (
<> <>
<div className="gf-form-inline"> <div className="gf-form-inline">
...@@ -95,9 +95,9 @@ SegmentStories.add('With custom options allowed', () => { ...@@ -95,9 +95,9 @@ SegmentStories.add('With custom options allowed', () => {
allowCustomValue allowCustomValue
value={value} value={value}
loadOptions={() => loadOptions(options)} loadOptions={() => loadOptions(options)}
onChange={value => { onChange={item => {
updateValue(value); updateValue(item);
action('Segment value changed')(value); action('Segment value changed')(item.value);
}} }}
/> />
<SegmentAsync <SegmentAsync
...@@ -113,10 +113,10 @@ SegmentStories.add('With custom options allowed', () => { ...@@ -113,10 +113,10 @@ SegmentStories.add('With custom options allowed', () => {
); );
}); });
const CustomLabelComponent = ({ value }: any) => <div className="gf-form-label">custom({value})</div>; const CustomLabelComponent = ({ value: { value } }: any) => <div className="gf-form-label">custom({value})</div>;
SegmentStories.add('Custom Label Field', () => { SegmentStories.add('Custom Label Field', () => {
return ( return (
<UseState initialState={groupedOptions[0].options[0].value}> <UseState initialState={groupedOptions[0].options[0] as SelectableValue}>
{(value, updateValue) => ( {(value, updateValue) => (
<> <>
<div className="gf-form-inline"> <div className="gf-form-inline">
...@@ -126,9 +126,9 @@ SegmentStories.add('Custom Label Field', () => { ...@@ -126,9 +126,9 @@ SegmentStories.add('Custom Label Field', () => {
<SegmentAsync <SegmentAsync
Component={<CustomLabelComponent value={value} />} Component={<CustomLabelComponent value={value} />}
loadOptions={() => loadOptions(groupedOptions)} loadOptions={() => loadOptions(groupedOptions)}
onChange={value => { onChange={item => {
updateValue(value); updateValue(item);
action('Segment value changed')(value); action('Segment value changed')(item.value);
}} }}
/> />
<SegmentAsync <SegmentAsync
......
...@@ -29,15 +29,16 @@ export function SegmentAsync<T>({ ...@@ -29,15 +29,16 @@ export function SegmentAsync<T>({
setLoadedOptions(opts); setLoadedOptions(opts);
setSelectPlaceholder(opts.length ? '' : 'No options found'); setSelectPlaceholder(opts.length ? '' : 'No options found');
}} }}
Component={Component || <a className={cx('gf-form-label', 'query-part', className)}>{value}</a>} Component={Component || <a className={cx('gf-form-label', 'query-part', className)}>{value && value.label}</a>}
/> />
); );
} }
return ( return (
<SegmentSelect <SegmentSelect
width={width} value={value}
options={loadedOptions} options={loadedOptions}
width={width}
noOptionsMessage={selectPlaceholder} noOptionsMessage={selectPlaceholder}
allowCustomValue={allowCustomValue} allowCustomValue={allowCustomValue}
onClickOutside={() => { onClickOutside={() => {
...@@ -45,11 +46,11 @@ export function SegmentAsync<T>({ ...@@ -45,11 +46,11 @@ export function SegmentAsync<T>({
setLoadedOptions([]); setLoadedOptions([]);
setExpanded(false); setExpanded(false);
}} }}
onChange={value => { onChange={item => {
setSelectPlaceholder(''); setSelectPlaceholder('');
setLoadedOptions([]); setLoadedOptions([]);
setExpanded(false); setExpanded(false);
onChange(value); onChange(item);
}} }}
/> />
); );
......
...@@ -5,8 +5,9 @@ import { SelectableValue } from '@grafana/data'; ...@@ -5,8 +5,9 @@ import { SelectableValue } from '@grafana/data';
import { Select } from '../Select/Select'; import { Select } from '../Select/Select';
export interface Props<T> { export interface Props<T> {
value?: SelectableValue<T>;
options: Array<SelectableValue<T>>; options: Array<SelectableValue<T>>;
onChange: (value: T) => void; onChange: (item: SelectableValue<T>) => void;
onClickOutside: () => void; onClickOutside: () => void;
width: number; width: number;
noOptionsMessage?: string; noOptionsMessage?: string;
...@@ -14,6 +15,7 @@ export interface Props<T> { ...@@ -14,6 +15,7 @@ export interface Props<T> {
} }
export function SegmentSelect<T>({ export function SegmentSelect<T>({
value,
options = [], options = [],
onChange, onChange,
onClickOutside, onClickOutside,
...@@ -39,8 +41,9 @@ export function SegmentSelect<T>({ ...@@ -39,8 +41,9 @@ export function SegmentSelect<T>({
placeholder="" placeholder=""
autoFocus={true} autoFocus={true}
isOpen={true} isOpen={true}
onChange={({ value }) => onChange(value!)} onChange={onChange}
options={options} options={options}
value={value}
allowCustomValue={allowCustomValue} allowCustomValue={allowCustomValue}
/> />
</div> </div>
......
import { ReactElement } from 'react'; import { ReactElement } from 'react';
import { SelectableValue } from '@grafana/data';
export interface SegmentProps<T> { export interface SegmentProps<T> {
onChange: (item: T) => void; onChange: (item: SelectableValue<T>) => void;
value?: T; value?: SelectableValue<T>;
Component?: ReactElement; Component?: ReactElement;
className?: string; className?: string;
allowCustomValue?: boolean; allowCustomValue?: boolean;
......
...@@ -34,15 +34,17 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l ...@@ -34,15 +34,17 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
return options.filter(({ value }) => !Object.keys(data).includes(value)); return options.filter(({ value }) => !Object.keys(data).includes(value));
}; };
const toOption = (value: any) => ({ label: value, value });
return ( return (
<> <>
{Object.entries(data).map(([key, value], index) => ( {Object.entries(data).map(([key, value], index) => (
<Fragment key={index}> <Fragment key={index}>
<SegmentAsync <SegmentAsync
allowCustomValue allowCustomValue
value={key} value={toOption(key)}
loadOptions={() => loadKeys().then(keys => [removeOption, ...excludeUsedKeys(keys)])} loadOptions={() => loadKeys().then(keys => [removeOption, ...excludeUsedKeys(keys)])}
onChange={newKey => { onChange={({ value: newKey }) => {
const { [key]: value, ...newDimensions } = data; const { [key]: value, ...newDimensions } = data;
if (newKey === removeText) { if (newKey === removeText) {
setData({ ...newDimensions }); setData({ ...newDimensions });
...@@ -54,9 +56,9 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l ...@@ -54,9 +56,9 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
<label className="gf-form-label query-segment-operator">=</label> <label className="gf-form-label query-segment-operator">=</label>
<SegmentAsync <SegmentAsync
allowCustomValue allowCustomValue
value={value || 'select dimension value'} value={toOption(value || 'select dimension value')}
loadOptions={() => loadValues(key)} loadOptions={() => loadValues(key)}
onChange={newValue => setData({ ...data, [key]: newValue })} onChange={({ value: newValue }) => setData({ ...data, [key]: newValue })}
/> />
{Object.values(data).length > 1 && index + 1 !== Object.values(data).length && ( {Object.values(data).length > 1 && index + 1 !== Object.values(data).length && (
<label className="gf-form-label query-keyword">AND</label> <label className="gf-form-label query-keyword">AND</label>
...@@ -72,7 +74,7 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l ...@@ -72,7 +74,7 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
</a> </a>
} }
loadOptions={() => loadKeys().then(excludeUsedKeys)} loadOptions={() => loadKeys().then(excludeUsedKeys)}
onChange={(newKey: string) => setData({ ...data, [newKey]: '' })} onChange={({ value: newKey }) => setData({ ...data, [newKey]: '' })}
/> />
)} )}
</> </>
......
...@@ -112,10 +112,10 @@ export class QueryEditor extends PureComponent<Props, State> { ...@@ -112,10 +112,10 @@ export class QueryEditor extends PureComponent<Props, State> {
<> <>
<QueryInlineField label="Region"> <QueryInlineField label="Region">
<Segment <Segment
value={query.region || 'Select region'} value={this.toOption(query.region || 'Select region')}
options={regions} options={regions}
allowCustomValue allowCustomValue
onChange={region => this.onChange({ ...query, region })} onChange={({ value: region }) => this.onChange({ ...query, region })}
/> />
</QueryInlineField> </QueryInlineField>
...@@ -123,19 +123,19 @@ export class QueryEditor extends PureComponent<Props, State> { ...@@ -123,19 +123,19 @@ export class QueryEditor extends PureComponent<Props, State> {
<> <>
<QueryInlineField label="Namespace"> <QueryInlineField label="Namespace">
<Segment <Segment
value={query.namespace || 'Select namespace'} value={this.toOption(query.namespace || 'Select namespace')}
allowCustomValue allowCustomValue
options={namespaces} options={namespaces}
onChange={namespace => this.onChange({ ...query, namespace })} onChange={({ value: namespace }) => this.onChange({ ...query, namespace })}
/> />
</QueryInlineField> </QueryInlineField>
<QueryInlineField label="Metric Name"> <QueryInlineField label="Metric Name">
<SegmentAsync <SegmentAsync
value={query.metricName || 'Select metric name'} value={this.toOption(query.metricName || 'Select metric name')}
allowCustomValue allowCustomValue
loadOptions={this.loadMetricNames} loadOptions={this.loadMetricNames}
onChange={metricName => this.onChange({ ...query, metricName })} onChange={({ value: metricName }) => this.onChange({ ...query, metricName })}
/> />
</QueryInlineField> </QueryInlineField>
......
...@@ -12,6 +12,7 @@ export interface Props { ...@@ -12,6 +12,7 @@ export interface Props {
const removeText = '-- remove stat --'; const removeText = '-- remove stat --';
const removeOption: SelectableValue<string> = { label: removeText, value: removeText }; const removeOption: SelectableValue<string> = { label: removeText, value: removeText };
const toOption = (value: any) => ({ label: value, value });
export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, variableOptionGroup }) => ( export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, variableOptionGroup }) => (
<> <>
...@@ -20,9 +21,9 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia ...@@ -20,9 +21,9 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia
<Segment <Segment
allowCustomValue allowCustomValue
key={value + index} key={value + index}
value={value} value={toOption(value)}
options={[removeOption, ...stats, variableOptionGroup]} options={[removeOption, ...stats, variableOptionGroup]}
onChange={value => onChange={({ value }) =>
onChange( onChange(
value === removeText value === removeText
? values.filter((_, i) => i !== index) ? values.filter((_, i) => i !== index)
...@@ -39,7 +40,7 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia ...@@ -39,7 +40,7 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia
</a> </a>
} }
allowCustomValue allowCustomValue
onChange={(value: string) => onChange([...values, value])} onChange={({ value }) => onChange([...values, value])}
options={[...stats.filter(({ value }) => !values.includes(value)), variableOptionGroup]} options={[...stats.filter(({ value }) => !values.includes(value)), variableOptionGroup]}
/> />
)} )}
......
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