From 55be249870251b4fa6186d2fa25177e77719335c Mon Sep 17 00:00:00 2001
From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com>
Date: Mon, 25 Oct 2021 08:47:01 -0300
Subject: [PATCH] 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
---
.../src/addSlice/AddSliceContainer.tsx | 14 +--
.../src/components/DatabaseSelector/index.tsx | 16 ++--
.../src/components/Select/Select.stories.tsx | 3 +-
.../src/components/Select/Select.test.tsx | 73 ++++++++++++---
.../src/components/Select/Select.tsx | 93 +++++++++++++------
.../src/components/TableSelector/index.tsx | 6 +-
.../src/components/TimezoneSelector/index.tsx | 15 +--
.../components/RefreshIntervalModal.tsx | 3 +-
.../FiltersConfigForm/ColumnSelect.tsx | 1 -
.../FiltersConfigForm/DatasetSelect.tsx | 6 +-
.../FormattingPopoverContent.tsx | 30 +++---
.../DateFilterControl/DateFilterLabel.tsx | 3 +-
.../components/CustomFrame.tsx | 8 +-
.../controls/DateFilterControl/types.ts | 1 +
.../DateFilterControl/utils/constants.ts | 44 +++++----
.../SelectAsyncControl.test.tsx | 1 +
.../src/views/CRUD/alert/AlertReportModal.tsx | 11 ++-
17 files changed, 216 insertions(+), 112 deletions(-)
diff --git a/superset-frontend/src/addSlice/AddSliceContainer.tsx b/superset-frontend/src/addSlice/AddSliceContainer.tsx
index 8dc1cab8f4..db8da84beb 100644
--- a/superset-frontend/src/addSlice/AddSliceContainer.tsx
+++ b/superset-frontend/src/addSlice/AddSliceContainer.tsx
@@ -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,
diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx
index dd6b824c89..45d91f1c97 100644
--- a/superset-frontend/src/components/DatabaseSelector/index.tsx
+++ b/superset-frontend/src/components/DatabaseSelector/index.tsx
@@ -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'));
});
diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx
index 938cc040ca..af79b175af 100644
--- a/superset-frontend/src/components/Select/Select.stories.tsx
+++ b/superset-frontend/src/components/Select/Select.stories.tsx
@@ -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,
diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx
index 3756d4a5d4..c9982e5ef9 100644
--- a/superset-frontend/src/components/Select/Select.test.tsx
+++ b/superset-frontend/src/components/Select/Select.test.tsx
@@ -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();
+ 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(
+ ,
+ );
+ 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();
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();
+ 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();
@@ -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);
diff --git a/superset-frontend/src/components/Select/Select.tsx b/superset-frontend/src/components/Select/Select.tsx
index 1578562875..46b96666bc 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -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 }) => (
);
+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);
}
};
diff --git a/superset-frontend/src/components/TableSelector/index.tsx b/superset-frontend/src/components/TableSelector/index.tsx
index e7a99cc71c..952cf124bd 100644
--- a/superset-frontend/src/components/TableSelector/index.tsx
+++ b/superset-frontend/src/components/TableSelector/index.tsx
@@ -209,11 +209,7 @@ const TableSelector: FunctionComponent = ({
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);
})
diff --git a/superset-frontend/src/components/TimezoneSelector/index.tsx b/superset-frontend/src/components/TimezoneSelector/index.tsx
index a028991f1d..a566073f03 100644
--- a/superset-frontend/src/components/TimezoneSelector/index.tsx
+++ b/superset-frontend/src/components/TimezoneSelector/index.tsx
@@ -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()
+ }
/>
);
};
diff --git a/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx b/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx
index 731572b8d7..3a8aef0372 100644
--- a/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx
+++ b/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx
@@ -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 && (
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/ColumnSelect.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/ColumnSelect.tsx
index 693e604109..92d98bbe4c 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/ColumnSelect.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/ColumnSelect.tsx
@@ -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],
);
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx
index 1fdadcbd0a..8ecb75777f 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx
@@ -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,
diff --git a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx
index 85f406c08d..252feff4ee 100644
--- a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx
+++ b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx
@@ -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}
>
-
+
);
diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx
index 71f84a8459..e5324a926b 100644
--- a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx
+++ b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx
@@ -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' && }
{frame === 'Common' && (
diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.tsx b/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.tsx
index 8125f0e9a6..a26d7ca765 100644
--- a/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.tsx
+++ b/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.tsx
@@ -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' && (
@@ -155,6 +158,7 @@ export function CustomFrame(props: FrameComponentProps) {
options={SINCE_GRAIN_OPTIONS}
value={sinceGrain}
onChange={(value: string) => onChange('sinceGrain', value)}
+ sortComparator={sortComparator}
/>
@@ -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' && (
@@ -206,6 +211,7 @@ export function CustomFrame(props: FrameComponentProps) {
options={UNTIL_GRAIN_OPTIONS}
value={untilGrain}
onChange={(value: string) => onChange('untilGrain', value)}
+ sortComparator={sortComparator}
/>
diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/types.ts b/superset-frontend/src/explore/components/controls/DateFilterControl/types.ts
index 0fcdfca786..fb34ed4fa7 100644
--- a/superset-frontend/src/explore/components/controls/DateFilterControl/types.ts
+++ b/superset-frontend/src/explore/components/controls/DateFilterControl/types.ts
@@ -19,6 +19,7 @@
export type SelectOptionType = {
value: string;
label: string;
+ order: number;
};
export type FrameType =
diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/utils/constants.ts b/superset-frontend/src/explore/components/controls/DateFilterControl/utils/constants.ts
index 22ccd5b5ed..613179d3d5 100644
--- a/superset-frontend/src/explore/components/controls/DateFilterControl/utils/constants.ts
+++ b/superset-frontend/src/explore/components/controls/DateFilterControl/utils/constants.ts
@@ -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();
diff --git a/superset-frontend/src/explore/components/controls/SelectAsyncControl/SelectAsyncControl.test.tsx b/superset-frontend/src/explore/components/controls/SelectAsyncControl/SelectAsyncControl.test.tsx
index 0b0b3034a6..bc78434850 100644
--- a/superset-frontend/src/explore/components/controls/SelectAsyncControl/SelectAsyncControl.test.tsx
+++ b/superset-frontend/src/explore/components/controls/SelectAsyncControl/SelectAsyncControl.test.tsx
@@ -44,6 +44,7 @@ jest.mock('src/components/Select/Select', () => ({
),
+ propertyComparator: jest.fn(),
}));
fetchMock.get(datasetsOwnersEndpoint, {
diff --git a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
index a3944184d6..c8c4df66a5 100644
--- a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
+++ b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
@@ -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 = ({
currentAlert?.validator_config_json?.op || undefined
}
options={CONDITIONS}
+ sortComparator={propertyComparator('order')}
/>
@@ -1214,6 +1222,7 @@ const AlertReportModal: FunctionComponent = ({
onChange={onLogRetentionChange}
value={currentAlert?.log_retention || DEFAULT_RETENTION}
options={RETENTION_OPTIONS}
+ sortComparator={propertyComparator('value')}
/>