Skip to content

Commit

Permalink
Add Multi Select element (#217)
Browse files Browse the repository at this point in the history
* Add Multi Select element

* Update CHANGELOG.md

---------

Co-authored-by: Mikhail <[email protected]>
  • Loading branch information
asimonok and mikhail-vl authored Aug 2, 2023
1 parent 20f9dae commit 57b367e
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Update jest selectors to use npm package (#209)
- Add onChange to update elements in local state within custom code (#214)
- Update ESLint configuration (#215)
- Add Multi Select element (#217)

## 3.0.0 (2023-07-15)

Expand Down
1 change: 1 addition & 0 deletions src/components/ElementEditor/ElementEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ export const ElementEditor: React.FC<Props> = ({ element, onChange, onChangeOpti

{(element.type === FormElementType.RADIO ||
element.type === FormElementType.SELECT ||
element.type === FormElementType.MULTISELECT ||
element.type === FormElementType.DISABLED) && (
<div>
{element.options?.map((option, index) => (
Expand Down
31 changes: 27 additions & 4 deletions src/components/FormElements/FormElements.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,23 @@ jest.mock('@grafana/ui', () => ({
/**
* Mock Select component
*/
Select: jest.fn().mockImplementation(({ options, onChange, value, ...restProps }) => (
Select: jest.fn().mockImplementation(({ options, onChange, value, isMulti, ...restProps }) => (
<select
onChange={(event: any) => {
if (onChange) {
onChange(options.find((option: any) => option.value === event.target.value));
if (isMulti) {
onChange(options.filter((option: any) => event.target.values.includes(option.value)));
} else {
onChange(options.find((option: any) => option.value === event.target.value));
}
}
}}
/**
* Fix jest warnings because null value.
* For Select component in @grafana/io should be used null to reset value.
*/
value={value === null ? '' : value}
multiple={isMulti}
{...restProps}
>
{options.map(({ label, value }: any) => (
Expand Down Expand Up @@ -318,7 +323,7 @@ describe('Form Elements', () => {
}: {
name: string;
getField: () => HTMLElement;
newValue: string;
newValue: unknown;
elements?: any[];
expectedValue: any;
}) => {
Expand All @@ -344,7 +349,11 @@ describe('Form Elements', () => {
/**
* Change field value
*/
await act(() => fireEvent.change(getField(), { target: { value: newValue } }));
await act(() =>
fireEvent.change(getField(), {
target: { value: newValue, values: Array.isArray(newValue) ? newValue : [newValue] },
})
);

/**
* Rerender with updated elements
Expand Down Expand Up @@ -439,6 +448,20 @@ describe('Form Elements', () => {
newValue: '123',
expectedValue: '123',
},
{
name: 'Should update multi select value',
elements: [
{
id: 'number',
type: FormElementType.MULTISELECT,
value: ['111'],
options: [{ value: '111' }, { value: '123' }],
},
],
getField: selectors.fieldSelect,
newValue: ['111', '123'],
expectedValue: ['111', '123'],
},
].forEach((scenario) => runFieldChangeScenario(scenario));

/**
Expand Down
9 changes: 5 additions & 4 deletions src/components/FormElements/FormElements.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Slider from 'rc-slider';
import React, { ChangeEvent } from 'react';
import { css, cx } from '@emotion/css';
import { dateTime, DateTime, SelectableValue } from '@grafana/data';
import { dateTime, DateTime } from '@grafana/data';
import {
CodeEditor,
DateTimePicker,
Expand Down Expand Up @@ -382,7 +382,7 @@ export const FormElements: React.FC<Props> = ({ options, elements, onChangeEleme
</InlineField>
)}

{element.type === FormElementType.SELECT && (
{(element.type === FormElementType.SELECT || element.type === FormElementType.MULTISELECT) && (
<InlineField
label={element.title}
grow={!element.width}
Expand All @@ -391,12 +391,13 @@ export const FormElements: React.FC<Props> = ({ options, elements, onChangeEleme
transparent={!element.title}
>
<Select
isMulti={element.type === FormElementType.MULTISELECT}
aria-label={TestIds.formElements.fieldSelect}
value={element.value !== undefined ? element.value : null}
onChange={(event: SelectableValue) => {
onChange={(event) => {
onChangeElement({
...element,
value: event?.value,
value: Array.isArray(event) ? event.map(({ value }) => value) : event.value,
});
}}
width={ApplyWidth(element.width)}
Expand Down
27 changes: 27 additions & 0 deletions src/components/FormElementsEditor/FormElementsEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,33 @@ describe('Form Elements Editor', () => {
expect(elementSelectors.buttonRemoveOption()).toBeInTheDocument();
});

/**
* Multi Select
*/
it('Should find component with Multi Select', () => {
const elements = [
{
...FormElementDefault,
id: 'select',
type: FormElementType.MULTISELECT,
options: [{ id: 'id', label: 'label', type: FormElementType.NUMBER }],
},
];

render(getComponent({ value: elements, onChange }));
expect(selectors.root()).toBeInTheDocument();

/**
* Make Select Element is opened
*/
const elementSelectors = openElement('select', FormElementType.MULTISELECT);

expect(elementSelectors.fieldType()).toBeInTheDocument();
expect(elementSelectors.fieldOptionType()).toBeInTheDocument();
expect(elementSelectors.fieldOptionLabel()).toBeInTheDocument();
expect(elementSelectors.buttonRemoveOption()).toBeInTheDocument();
});

/**
* Two elements
*/
Expand Down
5 changes: 5 additions & 0 deletions src/constants/form-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const enum FormElementType {
RADIO = 'radio',
SECRET = 'secret',
SELECT = 'select',
MULTISELECT = 'multiselect',
SLIDER = 'slider',
STRING = 'string',
TEXTAREA = 'textarea',
Expand Down Expand Up @@ -59,6 +60,10 @@ export const FormElementTypeOptions: SelectableValue[] = [
value: FormElementType.SELECT,
label: 'Select with Custom options',
},
{
value: FormElementType.MULTISELECT,
label: 'Multi Select with Custom options',
},
{
value: FormElementType.STRING,
label: 'String Input',
Expand Down
1 change: 1 addition & 0 deletions src/types/form-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export type FormElement = FormElementBase &
| ({ type: FormElementType.NUMBER } & NumberOptions)
| ({ type: FormElementType.TEXTAREA } & TextareaOptions)
| ({ type: FormElementType.SELECT } & SelectOptions)
| ({ type: FormElementType.MULTISELECT } & SelectOptions)
| ({ type: FormElementType.RADIO } & SelectOptions)
| ({ type: FormElementType.DISABLED } & SelectOptions)
| { type: FormElementType.PASSWORD }
Expand Down
1 change: 1 addition & 0 deletions src/utils/form-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const GetElementWithNewType = (
};
}
case FormElementType.SELECT:
case FormElementType.MULTISELECT:
case FormElementType.DISABLED:
case FormElementType.RADIO: {
return {
Expand Down

0 comments on commit 57b367e

Please sign in to comment.