mirror of https://github.com/apache/superset.git
fix: Order of Select items when unselecting (#17169)
* fix: Order of Select items when unselecting * Adds a property comparator that supports strings and numbers * Uses a named export for propertyComparator
This commit is contained in:
parent
ef3afbde82
commit
55be249870
|
@ -256,15 +256,11 @@ export default class AddSliceContainer extends React.PureComponent<
|
|||
customLabel: ReactNode;
|
||||
label: string;
|
||||
value: string;
|
||||
}[] = response.json.result
|
||||
.map((item: Dataset) => ({
|
||||
value: `${item.id}__${item.datasource_type}`,
|
||||
customLabel: this.newLabel(item),
|
||||
label: item.table_name,
|
||||
}))
|
||||
.sort((a: { label: string }, b: { label: string }) =>
|
||||
a.label.localeCompare(b.label),
|
||||
);
|
||||
}[] = response.json.result.map((item: Dataset) => ({
|
||||
value: `${item.id}__${item.datasource_type}`,
|
||||
customLabel: this.newLabel(item),
|
||||
label: item.table_name,
|
||||
}));
|
||||
return {
|
||||
data: list,
|
||||
totalCount: response.json.count,
|
||||
|
|
|
@ -201,22 +201,18 @@ export default function DatabaseSelector({
|
|||
// TODO: Would be nice to add pagination in a follow-up. Needs endpoint changes.
|
||||
SupersetClient.get({ endpoint })
|
||||
.then(({ json }) => {
|
||||
const options = json.result
|
||||
.map((s: string) => ({
|
||||
value: s,
|
||||
label: s,
|
||||
title: s,
|
||||
}))
|
||||
.sort((a: { label: string }, b: { label: string }) =>
|
||||
a.label.localeCompare(b.label),
|
||||
);
|
||||
const options = json.result.map((s: string) => ({
|
||||
value: s,
|
||||
label: s,
|
||||
title: s,
|
||||
}));
|
||||
if (onSchemasLoad) {
|
||||
onSchemasLoad(options);
|
||||
}
|
||||
setSchemaOptions(options);
|
||||
setLoadingSchemas(false);
|
||||
})
|
||||
.catch(e => {
|
||||
.catch(() => {
|
||||
setLoadingSchemas(false);
|
||||
handleError(t('There was an error loading the schemas'));
|
||||
});
|
||||
|
|
|
@ -313,7 +313,7 @@ const USERS = [
|
|||
'Claire',
|
||||
'Benedetta',
|
||||
'Ilenia',
|
||||
];
|
||||
].sort();
|
||||
|
||||
export const AsyncSelect = ({
|
||||
fetchOnlyOnSearch,
|
||||
|
@ -429,6 +429,7 @@ export const AsyncSelect = ({
|
|||
};
|
||||
|
||||
AsyncSelect.args = {
|
||||
allowClear: false,
|
||||
allowNewOptions: false,
|
||||
fetchOnlyOnSearch: false,
|
||||
pageSize: 10,
|
||||
|
|
|
@ -52,7 +52,7 @@ const OPTIONS = [
|
|||
{ label: 'Irfan', value: 18, gender: 'Male' },
|
||||
{ label: 'George', value: 19, gender: 'Male' },
|
||||
{ label: 'Ashfaq', value: 20, gender: 'Male' },
|
||||
];
|
||||
].sort((option1, option2) => option1.label.localeCompare(option2.label));
|
||||
|
||||
const loadOptions = async (search: string, page: number, pageSize: number) => {
|
||||
const totalCount = OPTIONS.length;
|
||||
|
@ -100,6 +100,8 @@ const findSelectValue = () =>
|
|||
const findAllSelectValues = () =>
|
||||
waitFor(() => getElementsByClassName('.ant-select-selection-item'));
|
||||
|
||||
const clearAll = () => userEvent.click(screen.getByLabelText('close-circle'));
|
||||
|
||||
const type = (text: string) => {
|
||||
const select = getSelect();
|
||||
userEvent.clear(select);
|
||||
|
@ -127,6 +129,37 @@ test('inverts the selection', async () => {
|
|||
expect(await screen.findByLabelText('stop')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('sort the options by label if no sort comparator is provided', async () => {
|
||||
const unsortedOptions = [...OPTIONS].sort(() => Math.random());
|
||||
render(<Select {...defaultProps} options={unsortedOptions} />);
|
||||
await open();
|
||||
const options = await findAllSelectOptions();
|
||||
options.forEach((option, key) =>
|
||||
expect(option).toHaveTextContent(OPTIONS[key].label),
|
||||
);
|
||||
});
|
||||
|
||||
test('sort the options using a custom sort comparator', async () => {
|
||||
const sortComparator = (
|
||||
option1: typeof OPTIONS[0],
|
||||
option2: typeof OPTIONS[0],
|
||||
) => option1.gender.localeCompare(option2.gender);
|
||||
render(
|
||||
<Select
|
||||
{...defaultProps}
|
||||
options={loadOptions}
|
||||
sortComparator={sortComparator}
|
||||
/>,
|
||||
);
|
||||
await open();
|
||||
const options = await findAllSelectOptions();
|
||||
const optionsPage = OPTIONS.slice(0, defaultProps.pageSize);
|
||||
const sortedOptions = optionsPage.sort(sortComparator);
|
||||
options.forEach((option, key) =>
|
||||
expect(option).toHaveTextContent(sortedOptions[key].label),
|
||||
);
|
||||
});
|
||||
|
||||
test('displays the selected values first', async () => {
|
||||
render(<Select {...defaultProps} mode="multiple" />);
|
||||
const option3 = OPTIONS[2].label;
|
||||
|
@ -141,6 +174,22 @@ test('displays the selected values first', async () => {
|
|||
expect(sortedOptions[1]).toHaveTextContent(option8);
|
||||
});
|
||||
|
||||
test('displays the original order when unselecting', async () => {
|
||||
render(<Select {...defaultProps} mode="multiple" />);
|
||||
const option3 = OPTIONS[2].label;
|
||||
const option8 = OPTIONS[7].label;
|
||||
await open();
|
||||
userEvent.click(await findSelectOption(option3));
|
||||
userEvent.click(await findSelectOption(option8));
|
||||
await type('{esc}');
|
||||
clearAll();
|
||||
await open();
|
||||
const options = await findAllSelectOptions();
|
||||
options.forEach((option, key) =>
|
||||
expect(option).toHaveTextContent(OPTIONS[key].label),
|
||||
);
|
||||
});
|
||||
|
||||
test('searches for label or value', async () => {
|
||||
const option = OPTIONS[11];
|
||||
render(<Select {...defaultProps} />);
|
||||
|
@ -172,11 +221,11 @@ test('searches for custom fields', async () => {
|
|||
await type('Female');
|
||||
options = await findAllSelectOptions();
|
||||
expect(options.length).toBe(5);
|
||||
expect(options[0]).toHaveTextContent('Olivia');
|
||||
expect(options[1]).toHaveTextContent('Emma');
|
||||
expect(options[2]).toHaveTextContent('Ava');
|
||||
expect(options[3]).toHaveTextContent('Charlotte');
|
||||
expect(options[4]).toHaveTextContent('Nikole');
|
||||
expect(options[0]).toHaveTextContent('Ava');
|
||||
expect(options[1]).toHaveTextContent('Charlotte');
|
||||
expect(options[2]).toHaveTextContent('Emma');
|
||||
expect(options[3]).toHaveTextContent('Nikole');
|
||||
expect(options[4]).toHaveTextContent('Olivia');
|
||||
await type('1');
|
||||
expect(screen.getByText(NO_DATA)).toBeInTheDocument();
|
||||
});
|
||||
|
@ -227,7 +276,7 @@ test('clear all the values', async () => {
|
|||
onClear={onClear}
|
||||
/>,
|
||||
);
|
||||
userEvent.click(screen.getByLabelText('close-circle'));
|
||||
clearAll();
|
||||
expect(onClear).toHaveBeenCalled();
|
||||
const values = await findAllSelectValues();
|
||||
expect(values.length).toBe(0);
|
||||
|
@ -378,8 +427,8 @@ test('static - searches for an item', async () => {
|
|||
await type(search);
|
||||
const options = await findAllSelectOptions();
|
||||
expect(options.length).toBe(2);
|
||||
expect(options[0]).toHaveTextContent('Olivia');
|
||||
expect(options[1]).toHaveTextContent('Oliver');
|
||||
expect(options[0]).toHaveTextContent('Oliver');
|
||||
expect(options[1]).toHaveTextContent('Olivia');
|
||||
});
|
||||
|
||||
test('async - renders the select with default props', () => {
|
||||
|
@ -546,8 +595,8 @@ test('async - searches for an item already loaded', async () => {
|
|||
await waitForElementToBeRemoved(screen.getByText(LOADING));
|
||||
const options = await findAllSelectOptions();
|
||||
expect(options.length).toBe(2);
|
||||
expect(options[0]).toHaveTextContent('Olivia');
|
||||
expect(options[1]).toHaveTextContent('Oliver');
|
||||
expect(options[0]).toHaveTextContent('Oliver');
|
||||
expect(options[1]).toHaveTextContent('Olivia');
|
||||
});
|
||||
|
||||
test('async - searches for an item in a page not loaded', async () => {
|
||||
|
@ -599,7 +648,7 @@ test('async - does not fire a new request for the same search input', async () =
|
|||
await type('search');
|
||||
expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
|
||||
expect(loadOptions).toHaveBeenCalledTimes(1);
|
||||
userEvent.click(screen.getByLabelText('close-circle'));
|
||||
clearAll();
|
||||
await type('search');
|
||||
expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
|
||||
expect(loadOptions).toHaveBeenCalledTimes(1);
|
||||
|
|
|
@ -94,6 +94,7 @@ export interface SelectProps extends PickedSelectProps {
|
|||
invertSelection?: boolean;
|
||||
fetchOnlyOnSearch?: boolean;
|
||||
onError?: (error: string) => void;
|
||||
sortComparator?: (a: AntdLabeledValue, b: AntdLabeledValue) => number;
|
||||
}
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
|
@ -168,6 +169,30 @@ const Error = ({ error }: { error: string }) => (
|
|||
</StyledError>
|
||||
);
|
||||
|
||||
const defaultSortComparator = (a: AntdLabeledValue, b: AntdLabeledValue) => {
|
||||
if (typeof a.label === 'string' && typeof b.label === 'string') {
|
||||
return a.label.localeCompare(b.label);
|
||||
}
|
||||
if (typeof a.value === 'string' && typeof b.value === 'string') {
|
||||
return a.value.localeCompare(b.value);
|
||||
}
|
||||
return (a.value as number) - (b.value as number);
|
||||
};
|
||||
|
||||
/**
|
||||
* It creates a comparator to check for a specific property.
|
||||
* Can be used with string and number property values.
|
||||
* */
|
||||
export const propertyComparator = (property: string) => (
|
||||
a: AntdLabeledValue,
|
||||
b: AntdLabeledValue,
|
||||
) => {
|
||||
if (typeof a[property] === 'string' && typeof b[property] === 'string') {
|
||||
return a[property].localeCompare(b[property]);
|
||||
}
|
||||
return (a[property] as number) - (b[property] as number);
|
||||
};
|
||||
|
||||
const Select = ({
|
||||
allowNewOptions = false,
|
||||
ariaLabel,
|
||||
|
@ -189,6 +214,7 @@ const Select = ({
|
|||
pageSize = DEFAULT_PAGE_SIZE,
|
||||
placeholder = t('Select ...'),
|
||||
showSearch = true,
|
||||
sortComparator = defaultSortComparator,
|
||||
value,
|
||||
...props
|
||||
}: SelectProps) => {
|
||||
|
@ -277,14 +303,21 @@ const Select = ({
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
const sortedOptions = [...topOptions, ...otherOptions];
|
||||
const sortedOptions = [
|
||||
...topOptions.sort(sortComparator),
|
||||
...otherOptions.sort(sortComparator),
|
||||
];
|
||||
if (!isEqual(sortedOptions, selectOptions)) {
|
||||
setSelectOptions(sortedOptions);
|
||||
}
|
||||
} else {
|
||||
const sortedOptions = [...selectOptions].sort(sortComparator);
|
||||
if (!isEqual(sortedOptions, selectOptions)) {
|
||||
setSelectOptions(sortedOptions);
|
||||
}
|
||||
}
|
||||
},
|
||||
[isAsync, isSingleMode, labelInValue, selectOptions],
|
||||
[isAsync, isSingleMode, labelInValue, selectOptions, sortComparator],
|
||||
);
|
||||
|
||||
const handleOnSelect = (
|
||||
|
@ -342,28 +375,34 @@ const Select = ({
|
|||
[onError],
|
||||
);
|
||||
|
||||
const handleData = (data: OptionsType) => {
|
||||
let mergedData: OptionsType = [];
|
||||
if (data && Array.isArray(data) && data.length) {
|
||||
const dataValues = new Set();
|
||||
data.forEach(option =>
|
||||
dataValues.add(String(option.value).toLocaleLowerCase()),
|
||||
);
|
||||
const handleData = useCallback(
|
||||
(data: OptionsType) => {
|
||||
let mergedData: OptionsType = [];
|
||||
if (data && Array.isArray(data) && data.length) {
|
||||
const dataValues = new Set();
|
||||
data.forEach(option =>
|
||||
dataValues.add(String(option.value).toLocaleLowerCase()),
|
||||
);
|
||||
|
||||
// merges with existing and creates unique options
|
||||
setSelectOptions(prevOptions => {
|
||||
mergedData = [
|
||||
...prevOptions.filter(
|
||||
previousOption =>
|
||||
!dataValues.has(String(previousOption.value).toLocaleLowerCase()),
|
||||
),
|
||||
...data,
|
||||
];
|
||||
return mergedData;
|
||||
});
|
||||
}
|
||||
return mergedData;
|
||||
};
|
||||
// merges with existing and creates unique options
|
||||
setSelectOptions(prevOptions => {
|
||||
mergedData = [
|
||||
...prevOptions.filter(
|
||||
previousOption =>
|
||||
!dataValues.has(
|
||||
String(previousOption.value).toLocaleLowerCase(),
|
||||
),
|
||||
),
|
||||
...data,
|
||||
];
|
||||
mergedData.sort(sortComparator);
|
||||
return mergedData;
|
||||
});
|
||||
}
|
||||
return mergedData;
|
||||
},
|
||||
[sortComparator],
|
||||
);
|
||||
|
||||
const handlePaginatedFetch = useMemo(
|
||||
() => (value: string, page: number, pageSize: number) => {
|
||||
|
@ -401,7 +440,7 @@ const Select = ({
|
|||
setIsTyping(false);
|
||||
});
|
||||
},
|
||||
[allValuesLoaded, fetchOnlyOnSearch, internalOnError, options],
|
||||
[allValuesLoaded, fetchOnlyOnSearch, handleData, internalOnError, options],
|
||||
);
|
||||
|
||||
const handleOnSearch = useMemo(
|
||||
|
@ -474,8 +513,8 @@ const Select = ({
|
|||
}
|
||||
|
||||
// multiple or tags mode keep the dropdown visible while selecting options
|
||||
// this waits for the dropdown to be closed before sorting the top options
|
||||
if (!isSingleMode && !isDropdownVisible) {
|
||||
// this waits for the dropdown to be opened before sorting the top options
|
||||
if (!isSingleMode && isDropdownVisible) {
|
||||
handleTopOptions(selectValue);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -209,11 +209,7 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
|||
if (onTablesLoad) {
|
||||
onTablesLoad(json.options);
|
||||
}
|
||||
setTableOptions(
|
||||
options.sort((a: { text: string }, b: { text: string }) =>
|
||||
a.text.localeCompare(b.text),
|
||||
),
|
||||
);
|
||||
setTableOptions(options);
|
||||
setCurrentTable(currentTable);
|
||||
setLoadingTables(false);
|
||||
})
|
||||
|
|
|
@ -83,17 +83,13 @@ ALL_ZONES.forEach(zone => {
|
|||
}
|
||||
});
|
||||
|
||||
const TIMEZONE_OPTIONS = TIMEZONES.sort(
|
||||
// sort by offset
|
||||
(a, b) =>
|
||||
moment.tz(currentDate, a.name).utcOffset() -
|
||||
moment.tz(currentDate, b.name).utcOffset(),
|
||||
).map(zone => ({
|
||||
const TIMEZONE_OPTIONS = TIMEZONES.map(zone => ({
|
||||
label: `GMT ${moment
|
||||
.tz(currentDate, zone.name)
|
||||
.format('Z')} (${getTimezoneName(zone.name)})`,
|
||||
value: zone.name,
|
||||
offsets: getOffsetKey(zone.name),
|
||||
timezoneName: zone.name,
|
||||
}));
|
||||
|
||||
const TimezoneSelector = ({ onTimezoneChange, timezone }: TimezoneProps) => {
|
||||
|
@ -126,6 +122,13 @@ const TimezoneSelector = ({ onTimezoneChange, timezone }: TimezoneProps) => {
|
|||
onChange={onTimezoneChange}
|
||||
value={timezone || DEFAULT_TIMEZONE.value}
|
||||
options={TIMEZONE_OPTIONS}
|
||||
sortComparator={(
|
||||
a: typeof TIMEZONE_OPTIONS[number],
|
||||
b: typeof TIMEZONE_OPTIONS[number],
|
||||
) =>
|
||||
moment.tz(currentDate, a.timezoneName).utcOffset() -
|
||||
moment.tz(currentDate, b.timezoneName).utcOffset()
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React, { RefObject } from 'react';
|
||||
import { Select } from 'src/components';
|
||||
import Select, { propertyComparator } from 'src/components/Select/Select';
|
||||
import { t, styled } from '@superset-ui/core';
|
||||
import Alert from 'src/components/Alert';
|
||||
import Button from 'src/components/Button';
|
||||
|
@ -120,6 +120,7 @@ class RefreshIntervalModal extends React.PureComponent<
|
|||
options={options}
|
||||
value={refreshFrequency}
|
||||
onChange={this.handleFrequencyChange}
|
||||
sortComparator={propertyComparator('value')}
|
||||
/>
|
||||
{showRefreshWarning && (
|
||||
<RefreshWarningContainer>
|
||||
|
|
|
@ -73,7 +73,6 @@ export function ColumnSelect({
|
|||
ensureIsArray(columns)
|
||||
.filter(filterValues)
|
||||
.map((col: Column) => col.column_name)
|
||||
.sort((a: string, b: string) => a.localeCompare(b))
|
||||
.map((column: string) => ({ label: column, value: column })),
|
||||
[columns, filterValues],
|
||||
);
|
||||
|
|
|
@ -72,11 +72,7 @@ const DatasetSelect = ({ onChange, value }: DatasetSelectProps) => {
|
|||
const data: {
|
||||
label: string;
|
||||
value: string | number;
|
||||
}[] = response.json.result
|
||||
.map(datasetToSelectOption)
|
||||
.sort((a: { label: string }, b: { label: string }) =>
|
||||
a.label.localeCompare(b.label),
|
||||
);
|
||||
}[] = response.json.result.map(datasetToSelectOption);
|
||||
return {
|
||||
data,
|
||||
totalCount: response.json.count,
|
||||
|
|
|
@ -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 } from 'src/components';
|
||||
import Select, { propertyComparator } from 'src/components/Select/Select';
|
||||
import { Col, InputNumber, Row } from 'src/common/components';
|
||||
import Button from 'src/components/Button';
|
||||
import {
|
||||
|
@ -44,17 +44,17 @@ const colorSchemeOptions = [
|
|||
];
|
||||
|
||||
const operatorOptions = [
|
||||
{ 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 ≤' },
|
||||
{ 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 },
|
||||
];
|
||||
|
||||
const targetValueValidator = (
|
||||
|
@ -126,7 +126,11 @@ const operatorField = (
|
|||
rules={rulesRequired}
|
||||
initialValue={operatorOptions[0].value}
|
||||
>
|
||||
<Select ariaLabel={t('Operator')} options={operatorOptions} />
|
||||
<Select
|
||||
ariaLabel={t('Operator')}
|
||||
options={operatorOptions}
|
||||
sortComparator={propertyComparator('order')}
|
||||
/>
|
||||
</FormItem>
|
||||
);
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ import Label, { Type } from 'src/components/Label';
|
|||
import Popover from 'src/components/Popover';
|
||||
import { Divider } from 'src/common/components';
|
||||
import Icons from 'src/components/Icons';
|
||||
import { Select } from 'src/components';
|
||||
import Select, { propertyComparator } 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';
|
||||
|
@ -295,6 +295,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
options={FRAME_OPTIONS}
|
||||
value={frame}
|
||||
onChange={onChangeFrame}
|
||||
sortComparator={propertyComparator('order')}
|
||||
/>
|
||||
{frame !== 'No filter' && <Divider />}
|
||||
{frame === 'Common' && (
|
||||
|
|
|
@ -23,7 +23,7 @@ import { isInteger } from 'lodash';
|
|||
import { Col, InputNumber, Row } from 'src/common/components';
|
||||
import { DatePicker } from 'src/components/DatePicker';
|
||||
import { Radio } from 'src/components/Radio';
|
||||
import { Select } from 'src/components';
|
||||
import Select, { propertyComparator } from 'src/components/Select/Select';
|
||||
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
|
||||
import {
|
||||
SINCE_GRAIN_OPTIONS,
|
||||
|
@ -41,6 +41,8 @@ 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) {
|
||||
|
@ -121,6 +123,7 @@ export function CustomFrame(props: FrameComponentProps) {
|
|||
options={SINCE_MODE_OPTIONS}
|
||||
value={sinceMode}
|
||||
onChange={(value: string) => onChange('sinceMode', value)}
|
||||
sortComparator={sortComparator}
|
||||
/>
|
||||
{sinceMode === 'specific' && (
|
||||
<Row>
|
||||
|
@ -155,6 +158,7 @@ export function CustomFrame(props: FrameComponentProps) {
|
|||
options={SINCE_GRAIN_OPTIONS}
|
||||
value={sinceGrain}
|
||||
onChange={(value: string) => onChange('sinceGrain', value)}
|
||||
sortComparator={sortComparator}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -173,6 +177,7 @@ export function CustomFrame(props: FrameComponentProps) {
|
|||
options={UNTIL_MODE_OPTIONS}
|
||||
value={untilMode}
|
||||
onChange={(value: string) => onChange('untilMode', value)}
|
||||
sortComparator={sortComparator}
|
||||
/>
|
||||
{untilMode === 'specific' && (
|
||||
<Row>
|
||||
|
@ -206,6 +211,7 @@ export function CustomFrame(props: FrameComponentProps) {
|
|||
options={UNTIL_GRAIN_OPTIONS}
|
||||
value={untilGrain}
|
||||
onChange={(value: string) => onChange('untilGrain', value)}
|
||||
sortComparator={sortComparator}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
export type SelectOptionType = {
|
||||
value: string;
|
||||
label: string;
|
||||
order: number;
|
||||
};
|
||||
|
||||
export type FrameType =
|
||||
|
|
|
@ -28,28 +28,32 @@ import {
|
|||
} from 'src/explore/components/controls/DateFilterControl/types';
|
||||
|
||||
export const FRAME_OPTIONS: SelectOptionType[] = [
|
||||
{ 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') },
|
||||
{ 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 },
|
||||
];
|
||||
|
||||
export const COMMON_RANGE_OPTIONS: SelectOptionType[] = [
|
||||
{ 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') },
|
||||
{ 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 },
|
||||
];
|
||||
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') },
|
||||
{ value: PreviousCalendarMonth, label: t('previous calendar month') },
|
||||
{ value: PreviousCalendarYear, label: t('previous calendar year') },
|
||||
{ value: PreviousCalendarWeek, label: t('previous calendar week'), order: 0 },
|
||||
{
|
||||
value: PreviousCalendarMonth,
|
||||
label: t('previous calendar month'),
|
||||
order: 1,
|
||||
},
|
||||
{ value: PreviousCalendarYear, label: t('previous calendar year'), order: 2 },
|
||||
];
|
||||
export const CALENDAR_RANGE_VALUES_SET = new Set(
|
||||
CALENDAR_RANGE_OPTIONS.map(({ value }) => value),
|
||||
|
@ -67,24 +71,26 @@ const GRAIN_OPTIONS = [
|
|||
];
|
||||
|
||||
export const SINCE_GRAIN_OPTIONS: SelectOptionType[] = GRAIN_OPTIONS.map(
|
||||
item => ({
|
||||
(item, index) => ({
|
||||
value: item.value,
|
||||
label: item.label(t('Before')),
|
||||
order: index,
|
||||
}),
|
||||
);
|
||||
|
||||
export const UNTIL_GRAIN_OPTIONS: SelectOptionType[] = GRAIN_OPTIONS.map(
|
||||
item => ({
|
||||
(item, index) => ({
|
||||
value: item.value,
|
||||
label: item.label(t('After')),
|
||||
order: index,
|
||||
}),
|
||||
);
|
||||
|
||||
export const SINCE_MODE_OPTIONS: SelectOptionType[] = [
|
||||
{ value: 'specific', label: t('Specific Date/Time') },
|
||||
{ value: 'relative', label: t('Relative Date/Time') },
|
||||
{ value: 'now', label: t('Now') },
|
||||
{ value: 'today', label: t('Midnight') },
|
||||
{ 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 },
|
||||
];
|
||||
|
||||
export const UNTIL_MODE_OPTIONS: SelectOptionType[] = SINCE_MODE_OPTIONS.slice();
|
||||
|
|
|
@ -44,6 +44,7 @@ jest.mock('src/components/Select/Select', () => ({
|
|||
</button>
|
||||
</div>
|
||||
),
|
||||
propertyComparator: jest.fn(),
|
||||
}));
|
||||
|
||||
fetchMock.get(datasetsOwnersEndpoint, {
|
||||
|
|
|
@ -38,7 +38,7 @@ import { Switch } from 'src/components/Switch';
|
|||
import Modal from 'src/components/Modal';
|
||||
import TimezoneSelector from 'src/components/TimezoneSelector';
|
||||
import { Radio } from 'src/components/Radio';
|
||||
import { Select } from 'src/components';
|
||||
import Select, { propertyComparator } from 'src/components/Select/Select';
|
||||
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import Owner from 'src/types/Owner';
|
||||
|
@ -85,30 +85,37 @@ 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,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -1147,6 +1154,7 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
|||
currentAlert?.validator_config_json?.op || undefined
|
||||
}
|
||||
options={CONDITIONS}
|
||||
sortComparator={propertyComparator('order')}
|
||||
/>
|
||||
</div>
|
||||
</StyledInputContainer>
|
||||
|
@ -1214,6 +1222,7 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
|||
onChange={onLogRetentionChange}
|
||||
value={currentAlert?.log_retention || DEFAULT_RETENTION}
|
||||
options={RETENTION_OPTIONS}
|
||||
sortComparator={propertyComparator('value')}
|
||||
/>
|
||||
</div>
|
||||
</StyledInputContainer>
|
||||
|
|
Loading…
Reference in New Issue