diff --git a/packages/grafana-data/src/utils/OptionsUIBuilders.ts b/packages/grafana-data/src/utils/OptionsUIBuilders.ts index e25d7e305ee..0c63d9f9292 100644 --- a/packages/grafana-data/src/utils/OptionsUIBuilders.ts +++ b/packages/grafana-data/src/utils/OptionsUIBuilders.ts @@ -181,6 +181,16 @@ export class PanelOptionsEditorBuilder extends OptionsUIRegistryBuilde }); } + addMultiSelect>( + config: PanelOptionsEditorConfig + ) { + return this.addCustomEditor({ + ...config, + id: config.path, + editor: standardEditorsRegistry.get('multi-select').editor as any, + }); + } + addRadio>( config: PanelOptionsEditorConfig ) { diff --git a/packages/grafana-ui/src/components/OptionsUI/multiSelect.tsx b/packages/grafana-ui/src/components/OptionsUI/multiSelect.tsx new file mode 100644 index 00000000000..258ed7b0316 --- /dev/null +++ b/packages/grafana-ui/src/components/OptionsUI/multiSelect.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { FieldConfigEditorProps, SelectFieldConfigSettings, SelectableValue } from '@grafana/data'; +import { MultiSelect } from '../Select/Select'; + +interface State { + isLoading: boolean; + options: Array>; +} + +type Props = FieldConfigEditorProps>; + +export class MultiSelectValueEditor extends React.PureComponent, State> { + state: State = { + isLoading: true, + options: [], + }; + + componentDidMount() { + this.updateOptions(); + } + + componentDidUpdate(oldProps: Props) { + const old = oldProps.item?.settings; + const now = this.props.item?.settings; + if (old !== now) { + this.updateOptions(); + } else if (now?.getOptions) { + const old = oldProps.context?.data; + const now = this.props.context?.data; + if (old !== now) { + this.updateOptions(); + } + } + } + + updateOptions = async () => { + const { item } = this.props; + const { settings } = item; + let options: Array> = item.settings?.options || []; + if (settings?.getOptions) { + options = await settings.getOptions(this.props.context); + } + if (this.state.options !== options) { + this.setState({ + isLoading: false, + options, + }); + } + }; + + render() { + const { options, isLoading } = this.state; + const { value, onChange, item } = this.props; + + const { settings } = item; + return ( + + isLoading={isLoading} + value={value} + defaultValue={value} + allowCustomValue={settings?.allowCustomValue} + onChange={(e) => { + onChange(e.map((v) => v.value).flatMap((v) => (v !== undefined ? [v] : []))); + }} + options={options} + /> + ); + } +} diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index e277e7dcc4e..2517906ece6 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -129,6 +129,7 @@ export { StringArrayEditor } from './OptionsUI/strings'; export { NumberValueEditor } from './OptionsUI/number'; export { SliderValueEditor } from './OptionsUI/slider'; export { SelectValueEditor } from './OptionsUI/select'; +export { MultiSelectValueEditor } from './OptionsUI/multiSelect'; // Next-gen forms export { Form } from './Forms/Form'; diff --git a/packages/grafana-ui/src/utils/standardEditors.tsx b/packages/grafana-ui/src/utils/standardEditors.tsx index 8b42780a158..5bf8794a30e 100644 --- a/packages/grafana-ui/src/utils/standardEditors.tsx +++ b/packages/grafana-ui/src/utils/standardEditors.tsx @@ -32,6 +32,7 @@ import { StringValueEditor, StringArrayEditor, SelectValueEditor, + MultiSelectValueEditor, TimeZonePicker, } from '../components'; import { ValueMappingsValueEditor } from '../components/OptionsUI/mappings'; @@ -271,6 +272,13 @@ export const getStandardOptionEditors = () => { editor: SelectValueEditor as any, }; + const multiSelect: StandardEditorsRegistryItem = { + id: 'multi-select', + name: 'Multi select', + description: 'Allows for multiple option selection', + editor: MultiSelectValueEditor as any, + }; + const radio: StandardEditorsRegistryItem = { id: 'radio', name: 'Radio', @@ -354,5 +362,6 @@ export const getStandardOptionEditors = () => { timeZone, fieldColor, color, + multiSelect, ]; };