feat(select): keep options order when in single mode (#19085)

This commit is contained in:
Jesse Yang 2022-03-11 13:01:38 -08:00 committed by GitHub
parent 124cb0dc66
commit ae13d8313b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 207 additions and 179 deletions

View File

@ -106,8 +106,6 @@ const groupByControl: SharedControlConfig<'SelectControl', ColumnMeta> = {
'One or many columns to group by. High cardinality groupings should include a sort by metric ' +
'and series limit to limit the number of fetched and rendered series.',
),
sortComparator: (a: { label: string }, b: { label: string }) =>
a.label.localeCompare(b.label),
optionRenderer: c => <ColumnOption showType column={c} />,
valueRenderer: c => <ColumnOption column={c} />,
valueKey: 'column_name',

View File

@ -21,6 +21,7 @@ export { default as ensureIsArray } from './ensureIsArray';
export { default as ensureIsInt } from './ensureIsInt';
export { default as isDefined } from './isDefined';
export { default as isRequired } from './isRequired';
export { default as isEqualArray } from './isEqualArray';
export { default as makeSingleton } from './makeSingleton';
export { default as promiseTimeout } from './promiseTimeout';
export { default as logging } from './logging';

View File

@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import isEqualArray from './isEqualArray';
test('isEqualArray', () => {
expect(isEqualArray([], [])).toBe(true);
expect(isEqualArray([1, 2], [1, 2])).toBe(true);
const item1 = { a: 1 };
expect(isEqualArray([item1], [item1])).toBe(true);
expect(isEqualArray(null, undefined)).toBe(true);
// compare is shallow
expect(isEqualArray([{ a: 1 }], [{ a: 1 }])).toBe(false);
expect(isEqualArray(null, [])).toBe(false);
expect(isEqualArray([1, 2], [])).toBe(false);
});

View File

@ -23,9 +23,11 @@ export default function isEqualArray<T extends unknown[] | undefined | null>(
return (
arrA === arrB ||
(!arrA && !arrB) ||
(arrA &&
!!(
arrA &&
arrB &&
arrA.length === arrB.length &&
arrA.every((x, i) => x === arrB[i]))
arrA.every((x, i) => x === arrB[i])
)
);
}

View File

@ -6,11 +6,7 @@
"rootDir": "."
},
"extends": "../../../tsconfig.json",
"include": [
"**/*",
"../types/**/*",
"../../../types/**/*"
],
"include": ["**/*", "../types/**/*", "../../../types/**/*"],
"references": [
{
"path": ".."

View File

@ -116,8 +116,6 @@ const all_columns: typeof sharedControls.groupby = {
? [t('must have a value')]
: [],
}),
sortComparator: (a: { label: string }, b: { label: string }) =>
a.label.localeCompare(b.label),
visibility: isRawMode,
};
@ -279,8 +277,6 @@ const config: ControlPanelConfig = {
choices: datasource?.order_by_choices || [],
}),
visibility: isRawMode,
sortComparator: (a: { label: string }, b: { label: string }) =>
a.label.localeCompare(b.label),
},
},
],

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import isEqualArray from './isEqualArray';
import { isEqualArray } from '@superset-ui/core';
import { TableChartProps } from '../types';
export default function isEqualColumns(

View File

@ -4,16 +4,9 @@
"outDir": "lib",
"rootDir": "src"
},
"exclude": [
"lib",
"test"
],
"exclude": ["lib", "test"],
"extends": "../../tsconfig.json",
"include": [
"src/**/*",
"types/**/*",
"../../types/**/*",
],
"include": ["src/**/*", "types/**/*", "../../types/**/*"],
"references": [
{
"path": "../../packages/superset-ui-chart-controls"

View File

@ -187,10 +187,10 @@ export const InteractiveSelect = ({
);
InteractiveSelect.args = {
autoFocus: false,
autoFocus: true,
allowNewOptions: false,
allowClear: false,
showSearch: false,
showSearch: true,
disabled: false,
invertSelection: false,
placeholder: 'Select ...',

View File

@ -99,6 +99,18 @@ const findAllSelectValues = () =>
const clearAll = () => userEvent.click(screen.getByLabelText('close-circle'));
const matchOrder = async (expectedLabels: string[]) => {
const actualLabels: string[] = [];
(await findAllSelectOptions()).forEach(option => {
actualLabels.push(option.textContent || '');
});
// menu is a virtual list, which means it may not render all options
expect(actualLabels.slice(0, expectedLabels.length)).toEqual(
expectedLabels.slice(0, actualLabels.length),
);
return true;
};
const type = (text: string) => {
const select = getSelect();
userEvent.clear(select);
@ -169,34 +181,64 @@ test('sort the options using a custom sort comparator', async () => {
});
});
test('displays the selected values first', async () => {
render(<Select {...defaultProps} mode="multiple" />);
const option3 = OPTIONS[2].label;
const option8 = OPTIONS[7].label;
test('should sort selected to top when in single mode', async () => {
render(<Select {...defaultProps} mode="single" />);
const originalLabels = OPTIONS.map(option => option.label);
await open();
userEvent.click(await findSelectOption(option3));
userEvent.click(await findSelectOption(option8));
userEvent.click(await findSelectOption(originalLabels[1]));
// after selection, keep the original order
expect(await matchOrder(originalLabels)).toBe(true);
// order selected to top when reopen
await type('{esc}');
await open();
const sortedOptions = await findAllSelectOptions();
expect(sortedOptions[0]).toHaveTextContent(option3);
expect(sortedOptions[1]).toHaveTextContent(option8);
let labels = originalLabels.slice();
labels = labels.splice(1, 1).concat(labels);
expect(await matchOrder(labels)).toBe(true);
// keep clicking other items, the updated order should still based on
// original order
userEvent.click(await findSelectOption(originalLabels[5]));
await matchOrder(labels);
await type('{esc}');
await open();
labels = originalLabels.slice();
labels = labels.splice(5, 1).concat(labels);
expect(await matchOrder(labels)).toBe(true);
// should revert to original order
clearAll();
await type('{esc}');
await open();
expect(await matchOrder(originalLabels)).toBe(true);
});
test('displays the original order when unselecting', async () => {
test('should sort selected to the top when in multi mode', async () => {
render(<Select {...defaultProps} mode="multiple" />);
const option3 = OPTIONS[2].label;
const option8 = OPTIONS[7].label;
const originalLabels = OPTIONS.map(option => option.label);
let labels = originalLabels.slice();
await open();
userEvent.click(await findSelectOption(option3));
userEvent.click(await findSelectOption(option8));
userEvent.click(await findSelectOption(labels[1]));
expect(await matchOrder(labels)).toBe(true);
await type('{esc}');
clearAll();
await open();
const options = await findAllSelectOptions();
options.forEach((option, key) =>
expect(option).toHaveTextContent(OPTIONS[key].label),
);
labels = labels.splice(1, 1).concat(labels);
expect(await matchOrder(labels)).toBe(true);
await open();
userEvent.click(await findSelectOption(labels[5]));
await type('{esc}');
await open();
labels = [labels.splice(0, 1)[0], labels.splice(4, 1)[0]].concat(labels);
expect(await matchOrder(labels)).toBe(true);
// should revert to original order
clearAll();
await type('{esc}');
await open();
expect(await matchOrder(originalLabels)).toBe(true);
});
test('searches for label or value', async () => {
@ -540,17 +582,35 @@ test('async - changes the selected item in single mode', async () => {
test('async - deselects an item in multiple mode', async () => {
render(<Select {...defaultProps} options={loadOptions} mode="multiple" />);
await open();
const [firstOption, secondOption] = OPTIONS;
userEvent.click(await findSelectOption(firstOption.label));
userEvent.click(await findSelectOption(secondOption.label));
const option3 = OPTIONS[2];
const option8 = OPTIONS[7];
userEvent.click(await findSelectOption(option8.label));
userEvent.click(await findSelectOption(option3.label));
let options = await findAllSelectOptions();
expect(options).toHaveLength(Math.min(defaultProps.pageSize, OPTIONS.length));
expect(options[0]).toHaveTextContent(OPTIONS[0].label);
expect(options[1]).toHaveTextContent(OPTIONS[1].label);
await type('{esc}');
await open();
// should rank selected options to the top after menu closes
options = await findAllSelectOptions();
expect(options).toHaveLength(Math.min(defaultProps.pageSize, OPTIONS.length));
expect(options[0]).toHaveTextContent(option3.label);
expect(options[1]).toHaveTextContent(option8.label);
let values = await findAllSelectValues();
expect(values.length).toBe(2);
expect(values[0]).toHaveTextContent(firstOption.label);
expect(values[1]).toHaveTextContent(secondOption.label);
userEvent.click(await findSelectOption(firstOption.label));
expect(values).toHaveLength(2);
// should keep the order by which the options were selected
expect(values[0]).toHaveTextContent(option8.label);
expect(values[1]).toHaveTextContent(option3.label);
userEvent.click(await findSelectOption(option3.label));
values = await findAllSelectValues();
expect(values.length).toBe(1);
expect(values[0]).toHaveTextContent(secondOption.label);
expect(values[0]).toHaveTextContent(option8.label);
});
test('async - adds a new option if none is available and allowNewOptions is true', async () => {

View File

@ -156,6 +156,10 @@ export interface SelectProps extends PickedSelectProps {
* Works in async mode only (See the options property).
*/
onError?: (error: string) => void;
/**
* Customize how filtered options are sorted while users search.
* Will not apply to predefined `options` array when users are not searching.
*/
sortComparator?: typeof DEFAULT_SORT_COMPARATOR;
}
@ -314,8 +318,6 @@ const Select = (
const isAsync = typeof options === 'function';
const isSingleMode = mode === 'single';
const shouldShowSearch = isAsync || allowNewOptions ? true : showSearch;
const initialOptions =
options && Array.isArray(options) ? options.slice() : EMPTY_OPTIONS;
const [selectValue, setSelectValue] = useState(value);
const [inputValue, setInputValue] = useState('');
const [isLoading, setIsLoading] = useState(loading);
@ -346,13 +348,27 @@ const Select = (
sortSelectedFirst(a, b) || sortComparator(a, b, inputValue),
[inputValue, sortComparator, sortSelectedFirst],
);
const sortComparatorWithoutSearch = useCallback(
const sortComparatorForNoSearch = useCallback(
(a: AntdLabeledValue, b: AntdLabeledValue) =>
sortSelectedFirst(a, b) || sortComparator(a, b, ''),
[sortComparator, sortSelectedFirst],
sortSelectedFirst(a, b) ||
// Only apply the custom sorter in async mode because we should
// preserve the options order as much as possible.
(isAsync ? sortComparator(a, b, '') : 0),
[isAsync, sortComparator, sortSelectedFirst],
);
const initialOptions = useMemo(
() => (options && Array.isArray(options) ? options.slice() : EMPTY_OPTIONS),
[options],
);
const initialOptionsSorted = useMemo(
() => initialOptions.slice().sort(sortComparatorForNoSearch),
[initialOptions, sortComparatorForNoSearch],
);
const [selectOptions, setSelectOptions] =
useState<OptionsType>(initialOptions);
useState<OptionsType>(initialOptionsSorted);
// add selected values to options list if they are not in it
const fullSelectOptions = useMemo(() => {
const missingValues: OptionsType = ensureIsArray(selectValue)
@ -433,13 +449,13 @@ const Select = (
mergedData = prevOptions
.filter(previousOption => !dataValues.has(previousOption.value))
.concat(data)
.sort(sortComparatorWithoutSearch);
.sort(sortComparatorForNoSearch);
return mergedData;
});
}
return mergedData;
},
[sortComparatorWithoutSearch],
[sortComparatorForNoSearch],
);
const fetchPage = useMemo(
@ -575,11 +591,13 @@ const Select = (
}
// if no search input value, force sort options because it won't be sorted by
// `filterSort`.
if (isDropdownVisible && !inputValue && fullSelectOptions.length > 0) {
const sortedOptions = [...fullSelectOptions].sort(
sortComparatorWithSearch,
);
if (!isEqual(sortedOptions, fullSelectOptions)) {
if (isDropdownVisible && !inputValue && selectOptions.length > 1) {
const sortedOptions = isAsync
? selectOptions.slice().sort(sortComparatorForNoSearch)
: // if not in async mode, revert to the original select options
// (with selected options still sorted to the top)
initialOptionsSorted;
if (!isEqual(sortedOptions, selectOptions)) {
setSelectOptions(sortedOptions);
}
}
@ -624,10 +642,8 @@ const Select = (
// when `options` list is updated from component prop, reset states
fetchedQueries.current.clear();
setAllValuesLoaded(false);
setSelectOptions(
options && Array.isArray(options) ? options : EMPTY_OPTIONS,
);
}, [options]);
setSelectOptions(initialOptions);
}, [initialOptions]);
useEffect(() => {
setSelectValue(value);

View File

@ -99,6 +99,7 @@ const TIMEZONE_OPTIONS_SORT_COMPARATOR = (
moment.tz(currentDate, a.timezoneName).utcOffset() -
moment.tz(currentDate, b.timezoneName).utcOffset();
// pre-sort timezone options by time offset
TIMEZONE_OPTIONS.sort(TIMEZONE_OPTIONS_SORT_COMPARATOR);
const matchTimezoneToOptions = (timezone: string) =>

View File

@ -510,7 +510,8 @@ export default function getNativeFilterConfig(
childComponent.filterType as FILTER_COMPONENT_FILTER_TYPES,
)
) {
childComponent.cascadeParentIds ||= [];
childComponent.cascadeParentIds =
childComponent.cascadeParentIds || [];
childComponent.cascadeParentIds.push(parentComponentId);
}
});

View File

@ -19,7 +19,7 @@
import React from 'react';
import { styled, t } from '@superset-ui/core';
import { Form, FormItem, FormProps } from 'src/components/Form';
import Select, { propertyComparator } from 'src/components/Select/Select';
import Select from 'src/components/Select/Select';
import { Col, Row } from 'src/components';
import { InputNumber } from 'src/components/Input';
import Button from 'src/components/Button';
@ -45,17 +45,17 @@ const colorSchemeOptions = [
];
const operatorOptions = [
{ value: COMPARATOR.NONE, label: 'None', order: 0 },
{ value: COMPARATOR.GREATER_THAN, label: '>', order: 1 },
{ value: COMPARATOR.LESS_THAN, label: '<', order: 2 },
{ value: COMPARATOR.GREATER_OR_EQUAL, label: '≥', order: 3 },
{ value: COMPARATOR.LESS_OR_EQUAL, label: '≤', order: 4 },
{ value: COMPARATOR.EQUAL, label: '=', order: 5 },
{ value: COMPARATOR.NOT_EQUAL, label: '≠', order: 6 },
{ value: COMPARATOR.BETWEEN, label: '< x <', order: 7 },
{ value: COMPARATOR.BETWEEN_OR_EQUAL, label: '≤ x ≤', order: 8 },
{ value: COMPARATOR.BETWEEN_OR_LEFT_EQUAL, label: '≤ x <', order: 9 },
{ value: COMPARATOR.BETWEEN_OR_RIGHT_EQUAL, label: '< x ≤', order: 10 },
{ value: COMPARATOR.NONE, label: 'None' },
{ value: COMPARATOR.GREATER_THAN, label: '>' },
{ value: COMPARATOR.LESS_THAN, label: '<' },
{ value: COMPARATOR.GREATER_OR_EQUAL, label: '≥' },
{ value: COMPARATOR.LESS_OR_EQUAL, label: '≤' },
{ value: COMPARATOR.EQUAL, label: '=' },
{ value: COMPARATOR.NOT_EQUAL, label: '≠' },
{ value: COMPARATOR.BETWEEN, label: '< x <' },
{ value: COMPARATOR.BETWEEN_OR_EQUAL, label: '≤ x ≤' },
{ value: COMPARATOR.BETWEEN_OR_LEFT_EQUAL, label: '≤ x <' },
{ value: COMPARATOR.BETWEEN_OR_RIGHT_EQUAL, label: '< x ≤' },
];
const targetValueValidator =
@ -127,11 +127,7 @@ const operatorField = (
rules={rulesRequired}
initialValue={operatorOptions[0].value}
>
<Select
ariaLabel={t('Operator')}
options={operatorOptions}
sortComparator={propertyComparator('order')}
/>
<Select ariaLabel={t('Operator')} options={operatorOptions} />
</FormItem>
);

View File

@ -34,7 +34,7 @@ import Label, { Type } from 'src/components/Label';
import Popover from 'src/components/Popover';
import { Divider } from 'src/components';
import Icons from 'src/components/Icons';
import Select, { propertyComparator } from 'src/components/Select/Select';
import Select from 'src/components/Select/Select';
import { Tooltip } from 'src/components/Tooltip';
import { DEFAULT_TIME_RANGE } from 'src/explore/constants';
import { useDebouncedEffect } from 'src/explore/exploreUtils';
@ -294,7 +294,6 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
options={FRAME_OPTIONS}
value={frame}
onChange={onChangeFrame}
sortComparator={propertyComparator('order')}
/>
{frame !== 'No filter' && <Divider />}
{frame === 'Common' && (

View File

@ -24,7 +24,7 @@ import { Col, Row } from 'src/components';
import { InputNumber } from 'src/components/Input';
import { DatePicker } from 'src/components/DatePicker';
import { Radio } from 'src/components/Radio';
import Select, { propertyComparator } from 'src/components/Select/Select';
import Select from 'src/components/Select/Select';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import {
SINCE_GRAIN_OPTIONS,
@ -42,8 +42,6 @@ import {
FrameComponentProps,
} from 'src/explore/components/controls/DateFilterControl/types';
const sortComparator = propertyComparator('order');
export function CustomFrame(props: FrameComponentProps) {
const { customRange, matchedFlag } = customTimeRangeDecode(props.value);
if (!matchedFlag) {
@ -124,7 +122,6 @@ export function CustomFrame(props: FrameComponentProps) {
options={SINCE_MODE_OPTIONS}
value={sinceMode}
onChange={(value: string) => onChange('sinceMode', value)}
sortComparator={sortComparator}
/>
{sinceMode === 'specific' && (
<Row>
@ -159,7 +156,6 @@ export function CustomFrame(props: FrameComponentProps) {
options={SINCE_GRAIN_OPTIONS}
value={sinceGrain}
onChange={(value: string) => onChange('sinceGrain', value)}
sortComparator={sortComparator}
/>
</Col>
</Row>
@ -178,7 +174,6 @@ export function CustomFrame(props: FrameComponentProps) {
options={UNTIL_MODE_OPTIONS}
value={untilMode}
onChange={(value: string) => onChange('untilMode', value)}
sortComparator={sortComparator}
/>
{untilMode === 'specific' && (
<Row>
@ -212,7 +207,6 @@ export function CustomFrame(props: FrameComponentProps) {
options={UNTIL_GRAIN_OPTIONS}
value={untilGrain}
onChange={(value: string) => onChange('untilGrain', value)}
sortComparator={sortComparator}
/>
</Col>
</Row>

View File

@ -19,7 +19,6 @@
export type SelectOptionType = {
value: string;
label: string;
order: number;
};
export type FrameType =

View File

@ -28,32 +28,31 @@ import {
} from 'src/explore/components/controls/DateFilterControl/types';
export const FRAME_OPTIONS: SelectOptionType[] = [
{ value: 'Common', label: t('Last'), order: 0 },
{ value: 'Calendar', label: t('Previous'), order: 1 },
{ value: 'Custom', label: t('Custom'), order: 2 },
{ value: 'Advanced', label: t('Advanced'), order: 3 },
{ value: 'No filter', label: t('No filter'), order: 4 },
{ value: 'Common', label: t('Last') },
{ value: 'Calendar', label: t('Previous') },
{ value: 'Custom', label: t('Custom') },
{ value: 'Advanced', label: t('Advanced') },
{ value: 'No filter', label: t('No filter') },
];
export const COMMON_RANGE_OPTIONS: SelectOptionType[] = [
{ value: 'Last day', label: t('last day'), order: 0 },
{ value: 'Last week', label: t('last week'), order: 1 },
{ value: 'Last month', label: t('last month'), order: 2 },
{ value: 'Last quarter', label: t('last quarter'), order: 3 },
{ value: 'Last year', label: t('last year'), order: 4 },
{ value: 'Last day', label: t('last day') },
{ value: 'Last week', label: t('last week') },
{ value: 'Last month', label: t('last month') },
{ value: 'Last quarter', label: t('last quarter') },
{ value: 'Last year', label: t('last year') },
];
export const COMMON_RANGE_VALUES_SET = new Set(
COMMON_RANGE_OPTIONS.map(({ value }) => value),
);
export const CALENDAR_RANGE_OPTIONS: SelectOptionType[] = [
{ value: PreviousCalendarWeek, label: t('previous calendar week'), order: 0 },
{ value: PreviousCalendarWeek, label: t('previous calendar week') },
{
value: PreviousCalendarMonth,
label: t('previous calendar month'),
order: 1,
},
{ value: PreviousCalendarYear, label: t('previous calendar year'), order: 2 },
{ value: PreviousCalendarYear, label: t('previous calendar year') },
];
export const CALENDAR_RANGE_VALUES_SET = new Set(
CALENDAR_RANGE_OPTIONS.map(({ value }) => value),
@ -71,26 +70,24 @@ const GRAIN_OPTIONS = [
];
export const SINCE_GRAIN_OPTIONS: SelectOptionType[] = GRAIN_OPTIONS.map(
(item, index) => ({
item => ({
value: item.value,
label: item.label(t('Before')),
order: index,
}),
);
export const UNTIL_GRAIN_OPTIONS: SelectOptionType[] = GRAIN_OPTIONS.map(
(item, index) => ({
item => ({
value: item.value,
label: item.label(t('After')),
order: index,
}),
);
export const SINCE_MODE_OPTIONS: SelectOptionType[] = [
{ value: 'specific', label: t('Specific Date/Time'), order: 0 },
{ value: 'relative', label: t('Relative Date/Time'), order: 1 },
{ value: 'now', label: t('Now'), order: 2 },
{ value: 'today', label: t('Midnight'), order: 3 },
{ value: 'specific', label: t('Specific Date/Time') },
{ value: 'relative', label: t('Relative Date/Time') },
{ value: 'now', label: t('Now') },
{ value: 'today', label: t('Midnight') },
];
export const UNTIL_MODE_OPTIONS: SelectOptionType[] =

View File

@ -37,7 +37,6 @@ import AdhocFilter, {
CLAUSES,
} from 'src/explore/components/controls/FilterControl/AdhocFilter';
import { Input } from 'src/components/Input';
import { propertyComparator } from 'src/components/Select/Select';
import { optionLabel } from 'src/utils/common';
const StyledInput = styled(Input)`
@ -405,16 +404,12 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
order: index,
}))}
{...operatorSelectProps}
sortComparator={propertyComparator('order')}
/>
{MULTI_OPERATORS.has(operatorId) || suggestions.length > 0 ? (
<SelectWithLabel
labelText={labelText}
options={suggestions}
{...comparatorSelectProps}
sortComparator={propertyComparator(
typeof suggestions[0] === 'number' ? 'value' : 'label',
)}
/>
) : (
<StyledInput

View File

@ -18,8 +18,8 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { css, t } from '@superset-ui/core';
import Select, { propertyComparator } from 'src/components/Select/Select';
import { css, isEqualArray, t } from '@superset-ui/core';
import Select from 'src/components/Select/Select';
import ControlHeader from 'src/explore/components/ControlHeader';
const propTypes = {
@ -94,8 +94,8 @@ export default class SelectControl extends React.PureComponent {
UNSAFE_componentWillReceiveProps(nextProps) {
if (
nextProps.choices !== this.props.choices ||
nextProps.options !== this.props.options
!isEqualArray(nextProps.choices, this.props.choices) ||
!isEqualArray(nextProps.options, this.props.options)
) {
const options = this.getOptions(nextProps);
this.setState({ options });
@ -133,10 +133,9 @@ export default class SelectControl extends React.PureComponent {
}));
} else if (choices) {
// Accepts different formats of input
options = choices.map((c, i) => {
options = choices.map(c => {
if (Array.isArray(c)) {
const [value, label] = c.length > 1 ? c : [c[0], c[0]];
if (!this.props.sortComparator) return { value, label, order: i };
return {
value,
label,
@ -241,7 +240,7 @@ export default class SelectControl extends React.PureComponent {
optionRenderer,
options: this.state.options,
placeholder,
sortComparator: this.props.sortComparator || propertyComparator('order'),
sortComparator: this.props.sortComparator,
value: getValue(),
};

View File

@ -37,9 +37,9 @@ const defaultProps = {
};
const options = [
{ value: '1 year ago', label: '1 year ago', order: 0 },
{ value: '1 week ago', label: '1 week ago', order: 1 },
{ value: 'today', label: 'today', order: 2 },
{ value: '1 year ago', label: '1 year ago' },
{ value: '1 week ago', label: '1 week ago' },
{ value: 'today', label: 'today' },
];
describe('SelectControl', () => {
@ -151,25 +151,6 @@ describe('SelectControl', () => {
expect(wrapper.html()).not.toContain('add something');
});
});
describe('when select has a sortComparator prop', () => {
it('does not add add order key', () => {
const sortComparator = (a, b) => a.label.localeCompare(b.label);
const optionsSortedByLabel = options.map(opt => ({
label: opt.label,
value: opt.value,
}));
wrapper = mount(
<SelectControl
{...defaultProps}
sortComparator={sortComparator}
value={50}
placeholder="add something"
/>,
);
expect(wrapper.state().options).toEqual(optionsSortedByLabel);
});
});
});
describe('getOptions', () => {
@ -178,23 +159,4 @@ describe('SelectControl', () => {
expect(wrapper.instance().getOptions(defaultProps)).toEqual(options);
});
});
describe('UNSAFE_componentWillReceiveProps', () => {
it('sets state.options if props.choices has changed', () => {
const updatedOptions = [
{ value: 'three', label: 'three', order: 0 },
{ value: 'four', label: 'four', order: 1 },
];
const newProps = {
choices: [
['three', 'three'],
['four', 'four'],
],
name: 'name',
freeForm: false,
value: null,
};
wrapper.setProps(newProps);
expect(wrapper.state().options).toEqual(updatedOptions);
});
});
});

View File

@ -87,37 +87,30 @@ const CONDITIONS = [
{
label: t('< (Smaller than)'),
value: '<',
order: 0,
},
{
label: t('> (Larger than)'),
value: '>',
order: 1,
},
{
label: t('<= (Smaller or equal)'),
value: '<=',
order: 2,
},
{
label: t('>= (Larger or equal)'),
value: '>=',
order: 3,
},
{
label: t('== (Is equal)'),
value: '==',
order: 4,
},
{
label: t('!= (Is not equal)'),
value: '!=',
order: 5,
},
{
label: t('Not null'),
value: 'not null',
order: 6,
},
];
@ -1194,7 +1187,6 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
currentAlert?.validator_config_json?.op || undefined
}
options={CONDITIONS}
sortComparator={propertyComparator('order')}
/>
</div>
</StyledInputContainer>