From cdab55b06ab6ec4a04b09875ff7eef3aed59ab07 Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Fri, 17 Jun 2022 16:25:00 -0400
Subject: [PATCH 01/20] Created AsyncSelect Component Changed files to
reference AsyncSelect if needed
---
.../src/addSlice/AddSliceContainer.tsx | 3 +-
.../src/components/DatabaseSelector/index.tsx | 3 +-
.../Datasource/DatasourceEditor.jsx | 3 +-
.../components/Select/AsyncSelect.test.tsx | 687 ++++++++++++++++
.../src/components/Select/AsyncSelect.tsx | 736 ++++++++++++++++++
.../components/PropertiesModal/index.tsx | 7 +-
.../FiltersConfigForm/DatasetSelect.tsx | 3 +-
.../components/PropertiesModal/index.tsx | 3 +-
.../src/views/CRUD/alert/AlertReportModal.tsx | 9 +-
9 files changed, 1442 insertions(+), 12 deletions(-)
create mode 100644 superset-frontend/src/components/Select/AsyncSelect.test.tsx
create mode 100644 superset-frontend/src/components/Select/AsyncSelect.tsx
diff --git a/superset-frontend/src/addSlice/AddSliceContainer.tsx b/superset-frontend/src/addSlice/AddSliceContainer.tsx
index 7e9f0a1a2ed5..82e4e7c77c7c 100644
--- a/superset-frontend/src/addSlice/AddSliceContainer.tsx
+++ b/superset-frontend/src/addSlice/AddSliceContainer.tsx
@@ -27,6 +27,7 @@ import { Tooltip } from 'src/components/Tooltip';
import VizTypeGallery, {
MAX_ADVISABLE_VIZ_GALLERY_WIDTH,
} from 'src/explore/components/controls/VizTypeControl/VizTypeGallery';
+import AsyncSelect from 'src/components/Select/AsyncSelect';
type Dataset = {
id: number;
@@ -280,7 +281,7 @@ export default class AddSliceContainer extends React.PureComponent<
status={this.state.datasource?.value ? 'finish' : 'process'}
description={
-
- = ({
*
-
= ({
{t('Dashboard')}
{t('Chart')}
- = ({
options={loadChartOptions}
onChange={onChartChange}
/>
-
Date: Thu, 23 Jun 2022 08:47:24 -0400
Subject: [PATCH 02/20] modified import of AsyncSelect, removed async tests and
prefixes from select tests
---
.../src/addSlice/AddSliceContainer.tsx | 3 +-
.../src/components/DatabaseSelector/index.tsx | 3 +-
.../Datasource/DatasourceEditor.jsx | 2 +-
.../components/Select/AsyncSelect.test.tsx | 44 +--
.../src/components/Select/AsyncSelect.tsx | 5 +-
.../src/components/Select/Select.stories.tsx | 17 +-
.../src/components/Select/Select.test.tsx | 310 +-----------------
superset-frontend/src/components/index.ts | 1 +
.../components/PropertiesModal/index.tsx | 3 +-
.../FiltersConfigForm/DatasetSelect.tsx | 3 +-
.../components/PropertiesModal/index.tsx | 3 +-
.../src/views/CRUD/alert/AlertReportModal.tsx | 2 +-
12 files changed, 57 insertions(+), 339 deletions(-)
diff --git a/superset-frontend/src/addSlice/AddSliceContainer.tsx b/superset-frontend/src/addSlice/AddSliceContainer.tsx
index 94dcf561b945..1654c10345fc 100644
--- a/superset-frontend/src/addSlice/AddSliceContainer.tsx
+++ b/superset-frontend/src/addSlice/AddSliceContainer.tsx
@@ -23,13 +23,12 @@ import { getUrlParam } from 'src/utils/urlUtils';
import { URL_PARAMS } from 'src/constants';
import { isNullish } from 'src/utils/common';
import Button from 'src/components/Button';
-import { Select, Steps } from 'src/components';
+import { AsyncSelect, Steps } from 'src/components';
import { Tooltip } from 'src/components/Tooltip';
import VizTypeGallery, {
MAX_ADVISABLE_VIZ_GALLERY_WIDTH,
} from 'src/explore/components/controls/VizTypeControl/VizTypeGallery';
-import AsyncSelect from 'src/components/Select/AsyncSelect';
import findPermission from 'src/dashboard/util/findPermission';
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx
index 81a896473851..de50f61bd18a 100644
--- a/superset-frontend/src/components/DatabaseSelector/index.tsx
+++ b/superset-frontend/src/components/DatabaseSelector/index.tsx
@@ -19,12 +19,11 @@
import React, { ReactNode, useState, useMemo, useEffect } from 'react';
import { styled, SupersetClient, t } from '@superset-ui/core';
import rison from 'rison';
-import { Select } from 'src/components';
+import { AsyncSelect, Select } from 'src/components';
import Label from 'src/components/Label';
import { FormLabel } from 'src/components/Form';
import RefreshLabel from 'src/components/RefreshLabel';
import { useToasts } from 'src/components/MessageToasts/withToasts';
-import AsyncSelect from '../Select/AsyncSelect';
const DatabaseSelectorWrapper = styled.div`
${({ theme }) => `
diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
index 26843b1a3870..10ee402d27f1 100644
--- a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
@@ -25,7 +25,7 @@ import Alert from 'src/components/Alert';
import Badge from 'src/components/Badge';
import shortid from 'shortid';
import { styled, SupersetClient, t, withTheme } from '@superset-ui/core';
-import { Select, Row, Col } from 'src/components';
+import { Select, AsyncSelect, Row, Col } from 'src/components';
import { FormLabel } from 'src/components/Form';
import Button from 'src/components/Button';
import Tabs from 'src/components/Tabs';
diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
index e929af503ea1..2094252d5d27 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
@@ -412,12 +412,12 @@ test('adds the null option when selected in multiple mode', async () => {
expect(values[1]).toHaveTextContent(NULL_OPTION.label);
});
-test('async - renders the select with default props', () => {
+test('renders the select with default props', () => {
render();
expect(getSelect()).toBeInTheDocument();
});
-test('async - opens the select without any data', async () => {
+test('opens the select without any data', async () => {
render(
{
expect(await screen.findByText(/no data/i)).toBeInTheDocument();
});
-test('async - displays the loading indicator when opening', async () => {
+test('displays the loading indicator when opening', async () => {
render();
await waitFor(() => {
userEvent.click(getSelect());
@@ -437,7 +437,7 @@ test('async - displays the loading indicator when opening', async () => {
expect(screen.queryByText(LOADING)).not.toBeInTheDocument();
});
-test('async - makes a selection in single mode', async () => {
+test('makes a selection in single mode', async () => {
render();
const optionText = 'Emma';
await open();
@@ -445,7 +445,7 @@ test('async - makes a selection in single mode', async () => {
expect(await findSelectValue()).toHaveTextContent(optionText);
});
-test('async - multiple selections in multiple mode', async () => {
+test('multiple selections in multiple mode', async () => {
render();
await open();
const [firstOption, secondOption] = OPTIONS;
@@ -456,7 +456,7 @@ test('async - multiple selections in multiple mode', async () => {
expect(values[1]).toHaveTextContent(secondOption.label);
});
-test('async - changes the selected item in single mode', async () => {
+test('changes the selected item in single mode', async () => {
const onChange = jest.fn();
render(
,
@@ -483,7 +483,7 @@ test('async - changes the selected item in single mode', async () => {
expect(await findSelectValue()).toHaveTextContent(secondOption.label);
});
-test('async - deselects an item in multiple mode', async () => {
+test('deselects an item in multiple mode', async () => {
render();
await open();
const option3 = OPTIONS[2];
@@ -517,14 +517,14 @@ test('async - deselects an item in multiple mode', async () => {
expect(values[0]).toHaveTextContent(option8.label);
});
-test('async - adds a new option if none is available and allowNewOptions is true', async () => {
+test('adds a new option if none is available and allowNewOptions is true', async () => {
render();
await open();
await type(NEW_OPTION);
expect(await findSelectOption(NEW_OPTION)).toBeInTheDocument();
});
-test('async - does not add a new option if the option already exists', async () => {
+test('does not add a new option if the option already exists', async () => {
render();
const option = OPTIONS[0].label;
await open();
@@ -537,7 +537,7 @@ test('async - does not add a new option if the option already exists', async ()
});
});
-test('async - shows "No data" when allowNewOptions is false and a new option is entered', async () => {
+test('shows "No data" when allowNewOptions is false and a new option is entered', async () => {
render(
{
+test('does not show "No data" when allowNewOptions is true and a new option is entered', async () => {
render();
await open();
await type(NEW_OPTION);
expect(screen.queryByText(NO_DATA)).not.toBeInTheDocument();
});
-test('async - sets a initial value in single mode', async () => {
+test('sets a initial value in single mode', async () => {
render();
expect(await findSelectValue()).toHaveTextContent(OPTIONS[0].label);
});
-test('async - sets a initial value in multiple mode', async () => {
+test('sets a initial value in multiple mode', async () => {
render(
{
expect(values[1]).toHaveTextContent(OPTIONS[1].label);
});
-test('async - searches for matches in both loaded and unloaded pages', async () => {
+test('searches for matches in both loaded and unloaded pages', async () => {
render();
await open();
await type('and');
@@ -593,7 +593,7 @@ test('async - searches for matches in both loaded and unloaded pages', async ()
expect(options[1]).toHaveTextContent('Sandro');
});
-test('async - searches for an item in a page not loaded', async () => {
+test('searches for an item in a page not loaded', async () => {
const mock = jest.fn(loadOptions);
render();
const search = 'Sandro';
@@ -605,20 +605,20 @@ test('async - searches for an item in a page not loaded', async () => {
expect(options[0]).toHaveTextContent(search);
});
-test('async - does not fetches data when rendering', async () => {
+test('does not fetches data when rendering', async () => {
const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
render();
expect(loadOptions).not.toHaveBeenCalled();
});
-test('async - fetches data when opening', async () => {
+test('fetches data when opening', async () => {
const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
render();
await open();
expect(loadOptions).toHaveBeenCalled();
});
-test('async - fetches data only after a search input is entered if fetchOnlyOnSearch is true', async () => {
+test('fetches data only after a search input is entered if fetchOnlyOnSearch is true', async () => {
const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
render();
await open();
@@ -627,7 +627,7 @@ test('async - fetches data only after a search input is entered if fetchOnlyOnSe
await waitFor(() => expect(loadOptions).toHaveBeenCalled());
});
-test('async - displays an error message when an exception is thrown while fetching', async () => {
+test('displays an error message when an exception is thrown while fetching', async () => {
const error = 'Fetch error';
const loadOptions = async () => {
throw new Error(error);
@@ -637,7 +637,7 @@ test('async - displays an error message when an exception is thrown while fetchi
expect(screen.getByText(error)).toBeInTheDocument();
});
-test('async - does not fire a new request for the same search input', async () => {
+test('does not fire a new request for the same search input', async () => {
const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
render();
await type('search');
@@ -649,7 +649,7 @@ test('async - does not fire a new request for the same search input', async () =
expect(loadOptions).toHaveBeenCalledTimes(1);
});
-test('async - does not fire a new request if all values have been fetched', async () => {
+test('does not fire a new request if all values have been fetched', async () => {
const mock = jest.fn(loadOptions);
const search = 'George';
const pageSize = OPTIONS.length;
@@ -661,7 +661,7 @@ test('async - does not fire a new request if all values have been fetched', asyn
expect(mock).toHaveBeenCalledTimes(1);
});
-test('async - fires a new request if all values have not been fetched', async () => {
+test('fires a new request if all values have not been fetched', async () => {
const mock = jest.fn(loadOptions);
const pageSize = OPTIONS.length / 2;
render();
diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx
index 8642f80a3c61..a7a85bb39c82 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.tsx
@@ -81,6 +81,9 @@ export type OptionsPagePromise = (
pageSize: number,
) => Promise;
+export type AsyncSelectRef = HTMLInputElement & { clearCache: () => void };
+
+
export interface AsyncSelectProps extends PickedSelectProps {
/**
* It enables the user to create new options.
@@ -315,7 +318,7 @@ const AsyncSelect = (
value,
...props
}: AsyncSelectProps,
- ref: RefObject,
+ ref: RefObject,
) => {
const isAsync = typeof options === 'function';
const isSingleMode = mode === 'single';
diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx
index 9b38586467ab..8844de8856d1 100644
--- a/superset-frontend/src/components/Select/Select.stories.tsx
+++ b/superset-frontend/src/components/Select/Select.stories.tsx
@@ -19,6 +19,7 @@
import React, { ReactNode, useState, useCallback, useRef } from 'react';
import Button from 'src/components/Button';
import ControlHeader from 'src/explore/components/ControlHeader';
+import AsyncSelect, { AsyncSelectProps, AsyncSelectRef } from './AsyncSelect';
import Select, {
SelectProps,
OptionsTypePage,
@@ -381,19 +382,19 @@ const USERS = [
'Ilenia',
].sort();
-export const AsyncSelect = ({
+export const AsynchronousSelect = ({
fetchOnlyOnSearch,
withError,
withInitialValue,
responseTime,
...rest
-}: SelectProps & {
+}: AsyncSelectProps & {
withError: boolean;
withInitialValue: boolean;
responseTime: number;
}) => {
const [requests, setRequests] = useState([]);
- const ref = useRef(null);
+ const ref = useRef(null);
const getResults = (username?: string) => {
let results: { label: string; value: string }[] = [];
@@ -463,12 +464,12 @@ export const AsyncSelect = ({
width: DEFAULT_WIDTH,
}}
>
- {
expect(values[1]).toHaveTextContent(NULL_OPTION.label);
});
-test('static - renders the select with default props', () => {
+test('renders the select with default props', () => {
render();
expect(getSelect()).toBeInTheDocument();
});
-test('static - opens the select without any data', async () => {
+test('opens the select without any data', async () => {
render();
await open();
expect(screen.getByText(NO_DATA)).toBeInTheDocument();
});
-test('static - makes a selection in single mode', async () => {
+test('makes a selection in single mode', async () => {
render();
const optionText = 'Emma';
await open();
@@ -432,7 +432,7 @@ test('static - makes a selection in single mode', async () => {
expect(await findSelectValue()).toHaveTextContent(optionText);
});
-test('static - multiple selections in multiple mode', async () => {
+test('multiple selections in multiple mode', async () => {
render();
await open();
const [firstOption, secondOption] = OPTIONS;
@@ -443,7 +443,7 @@ test('static - multiple selections in multiple mode', async () => {
expect(values[1]).toHaveTextContent(secondOption.label);
});
-test('static - changes the selected item in single mode', async () => {
+test('changes the selected item in single mode', async () => {
const onChange = jest.fn();
render();
await open();
@@ -468,7 +468,7 @@ test('static - changes the selected item in single mode', async () => {
expect(await findSelectValue()).toHaveTextContent(secondOption.label);
});
-test('static - deselects an item in multiple mode', async () => {
+test('deselects an item in multiple mode', async () => {
render();
await open();
const [firstOption, secondOption] = OPTIONS;
@@ -484,35 +484,35 @@ test('static - deselects an item in multiple mode', async () => {
expect(values[0]).toHaveTextContent(secondOption.label);
});
-test('static - adds a new option if none is available and allowNewOptions is true', async () => {
+test('adds a new option if none is available and allowNewOptions is true', async () => {
render();
await open();
await type(NEW_OPTION);
expect(await findSelectOption(NEW_OPTION)).toBeInTheDocument();
});
-test('static - shows "No data" when allowNewOptions is false and a new option is entered', async () => {
+test('shows "No data" when allowNewOptions is false and a new option is entered', async () => {
render();
await open();
await type(NEW_OPTION);
expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
});
-test('static - does not show "No data" when allowNewOptions is true and a new option is entered', async () => {
+test('does not show "No data" when allowNewOptions is true and a new option is entered', async () => {
render();
await open();
await type(NEW_OPTION);
expect(screen.queryByText(NO_DATA)).not.toBeInTheDocument();
});
-test('static - does not show "Loading..." when allowNewOptions is false and a new option is entered', async () => {
+test('does not show "Loading..." when allowNewOptions is false and a new option is entered', async () => {
render();
await open();
await type(NEW_OPTION);
expect(screen.queryByText(LOADING)).not.toBeInTheDocument();
});
-test('static - does not add a new option if the option already exists', async () => {
+test('does not add a new option if the option already exists', async () => {
render();
const option = OPTIONS[0].label;
await open();
@@ -520,12 +520,12 @@ test('static - does not add a new option if the option already exists', async ()
expect(await findSelectOption(option)).toBeInTheDocument();
});
-test('static - sets a initial value in single mode', async () => {
+test('sets a initial value in single mode', async () => {
render();
expect(await findSelectValue()).toHaveTextContent(OPTIONS[0].label);
});
-test('static - sets a initial value in multiple mode', async () => {
+test('sets a initial value in multiple mode', async () => {
render(
{
expect(values[1]).toHaveTextContent(OPTIONS[1].label);
});
-test('static - searches for an item', async () => {
+test('searches for an item', async () => {
render();
const search = 'Oli';
await type(search);
@@ -547,288 +547,6 @@ test('static - searches for an item', async () => {
expect(options[0]).toHaveTextContent('Oliver');
expect(options[1]).toHaveTextContent('Olivia');
});
-
-test('async - renders the select with default props', () => {
- render();
- expect(getSelect()).toBeInTheDocument();
-});
-
-test('async - opens the select without any data', async () => {
- render(
- ({ data: [], totalCount: 0 })}
- />,
- );
- await open();
- expect(await screen.findByText(/no data/i)).toBeInTheDocument();
-});
-
-test('async - displays the loading indicator when opening', async () => {
- render();
- await waitFor(() => {
- userEvent.click(getSelect());
- expect(screen.getByText(LOADING)).toBeInTheDocument();
- });
- expect(screen.queryByText(LOADING)).not.toBeInTheDocument();
-});
-
-test('async - makes a selection in single mode', async () => {
- render();
- const optionText = 'Emma';
- await open();
- userEvent.click(await findSelectOption(optionText));
- expect(await findSelectValue()).toHaveTextContent(optionText);
-});
-
-test('async - multiple selections in multiple mode', async () => {
- render();
- await open();
- const [firstOption, secondOption] = OPTIONS;
- userEvent.click(await findSelectOption(firstOption.label));
- userEvent.click(await findSelectOption(secondOption.label));
- const values = await findAllSelectValues();
- expect(values[0]).toHaveTextContent(firstOption.label);
- expect(values[1]).toHaveTextContent(secondOption.label);
-});
-
-test('async - changes the selected item in single mode', async () => {
- const onChange = jest.fn();
- render(
- ,
- );
- await open();
- const [firstOption, secondOption] = OPTIONS;
- userEvent.click(await findSelectOption(firstOption.label));
- expect(onChange).toHaveBeenCalledWith(
- expect.objectContaining({
- label: firstOption.label,
- value: firstOption.value,
- }),
- firstOption,
- );
- expect(await findSelectValue()).toHaveTextContent(firstOption.label);
- userEvent.click(await findSelectOption(secondOption.label));
- expect(onChange).toHaveBeenCalledWith(
- expect.objectContaining({
- label: secondOption.label,
- value: secondOption.value,
- }),
- secondOption,
- );
- expect(await findSelectValue()).toHaveTextContent(secondOption.label);
-});
-
-test('async - deselects an item in multiple mode', async () => {
- render();
- await open();
- 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).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(option8.label);
-});
-
-test('async - adds a new option if none is available and allowNewOptions is true', async () => {
- render();
- await open();
- await type(NEW_OPTION);
- expect(await findSelectOption(NEW_OPTION)).toBeInTheDocument();
-});
-
-test('async - does not add a new option if the option already exists', async () => {
- render();
- const option = OPTIONS[0].label;
- await open();
- await type(option);
- await waitFor(() => {
- const array = within(
- getElementByClassName('.rc-virtual-list'),
- ).getAllByText(option);
- expect(array.length).toBe(1);
- });
-});
-
-test('async - shows "No data" when allowNewOptions is false and a new option is entered', async () => {
- render(
- ,
- );
- await open();
- await type(NEW_OPTION);
- expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
-});
-
-test('async - does not show "No data" when allowNewOptions is true and a new option is entered', async () => {
- render();
- await open();
- await type(NEW_OPTION);
- expect(screen.queryByText(NO_DATA)).not.toBeInTheDocument();
-});
-
-test('async - sets a initial value in single mode', async () => {
- render();
- expect(await findSelectValue()).toHaveTextContent(OPTIONS[0].label);
-});
-
-test('async - sets a initial value in multiple mode', async () => {
- render(
- ,
- );
- const values = await findAllSelectValues();
- expect(values[0]).toHaveTextContent(OPTIONS[0].label);
- expect(values[1]).toHaveTextContent(OPTIONS[1].label);
-});
-
-test('async - searches for matches in both loaded and unloaded pages', async () => {
- render();
- await open();
- await type('and');
-
- let options = await findAllSelectOptions();
- expect(options.length).toBe(1);
- expect(options[0]).toHaveTextContent('Alehandro');
-
- await screen.findByText('Sandro');
- options = await findAllSelectOptions();
- expect(options.length).toBe(2);
- expect(options[0]).toHaveTextContent('Alehandro');
- expect(options[1]).toHaveTextContent('Sandro');
-});
-
-test('async - searches for an item in a page not loaded', async () => {
- const mock = jest.fn(loadOptions);
- render();
- const search = 'Sandro';
- await open();
- await type(search);
- await waitFor(() => expect(mock).toHaveBeenCalledTimes(2));
- const options = await findAllSelectOptions();
- expect(options.length).toBe(1);
- expect(options[0]).toHaveTextContent(search);
-});
-
-test('async - does not fetches data when rendering', async () => {
- const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
- render();
- expect(loadOptions).not.toHaveBeenCalled();
-});
-
-test('async - fetches data when opening', async () => {
- const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
- render();
- await open();
- expect(loadOptions).toHaveBeenCalled();
-});
-
-test('async - fetches data only after a search input is entered if fetchOnlyOnSearch is true', async () => {
- const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
- render();
- await open();
- await waitFor(() => expect(loadOptions).not.toHaveBeenCalled());
- await type('search');
- await waitFor(() => expect(loadOptions).toHaveBeenCalled());
-});
-
-test('async - displays an error message when an exception is thrown while fetching', async () => {
- const error = 'Fetch error';
- const loadOptions = async () => {
- throw new Error(error);
- };
- render();
- await open();
- expect(screen.getByText(error)).toBeInTheDocument();
-});
-
-test('async - does not fire a new request for the same search input', async () => {
- const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
- render();
- await type('search');
- expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
- expect(loadOptions).toHaveBeenCalledTimes(1);
- clearAll();
- await type('search');
- expect(await screen.findByText(LOADING)).toBeInTheDocument();
- expect(loadOptions).toHaveBeenCalledTimes(1);
-});
-
-test('async - does not fire a new request if all values have been fetched', async () => {
- const mock = jest.fn(loadOptions);
- const search = 'George';
- const pageSize = OPTIONS.length;
- render();
- await open();
- expect(mock).toHaveBeenCalledTimes(1);
- await type(search);
- expect(await findSelectOption(search)).toBeInTheDocument();
- expect(mock).toHaveBeenCalledTimes(1);
-});
-
-test('async - fires a new request if all values have not been fetched', async () => {
- const mock = jest.fn(loadOptions);
- const pageSize = OPTIONS.length / 2;
- render();
- await open();
- expect(mock).toHaveBeenCalledTimes(1);
- await type('or');
-
- // `George` is on the first page so when it appears the API has not been called again
- expect(await findSelectOption('George')).toBeInTheDocument();
- expect(mock).toHaveBeenCalledTimes(1);
-
- // `Igor` is on the second paged API request
- expect(await findSelectOption('Igor')).toBeInTheDocument();
- expect(mock).toHaveBeenCalledTimes(2);
-});
-
-test('async - requests the options again after clearing the cache', async () => {
- const ref: RefObject = { current: null };
- const mock = jest.fn(loadOptions);
- const pageSize = OPTIONS.length;
- render(
- ,
- );
- await open();
- expect(mock).toHaveBeenCalledTimes(1);
- ref.current?.clearCache();
- await type('{esc}');
- await open();
- expect(mock).toHaveBeenCalledTimes(2);
-});
-
/*
TODO: Add tests that require scroll interaction. Needs further investigation.
- Fetches more data when scrolling and more data is available
diff --git a/superset-frontend/src/components/index.ts b/superset-frontend/src/components/index.ts
index a370836fcfb8..bfa341a9ddd1 100644
--- a/superset-frontend/src/components/index.ts
+++ b/superset-frontend/src/components/index.ts
@@ -23,6 +23,7 @@
* E.g. import { Select } from 'src/components'
*/
export { default as Select } from './Select/Select';
+export { default as AsyncSelect } from './Select/AsyncSelect';
/*
* Components that don't conflict with the ones in src/components.
diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
index ded3177bd193..749411f2e95f 100644
--- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
+++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
@@ -21,7 +21,7 @@ import { Input } from 'src/components/Input';
import { FormItem } from 'src/components/Form';
import jsonStringify from 'json-stringify-pretty-compact';
import Button from 'src/components/Button';
-import { Select, Row, Col, AntdForm } from 'src/components';
+import { AsyncSelect, Row, Col, AntdForm } from 'src/components';
import rison from 'rison';
import {
styled,
@@ -39,7 +39,6 @@ import ColorSchemeControlWrapper from 'src/dashboard/components/ColorSchemeContr
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
import withToasts from 'src/components/MessageToasts/withToasts';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
-import AsyncSelect from 'src/components/Select/AsyncSelect';
const StyledFormItem = styled(FormItem)`
margin-bottom: 0;
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 8fa8584a2cc1..ed69c6734f1f 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx
@@ -19,14 +19,13 @@
import React, { useCallback, useMemo } from 'react';
import rison from 'rison';
import { t, SupersetClient } from '@superset-ui/core';
-import { Select } from 'src/components';
+import { AsyncSelect } from 'src/components';
import { cacheWrapper } from 'src/utils/cacheWrapper';
import {
ClientErrorObject,
getClientErrorObject,
} from 'src/utils/getClientErrorObject';
import { datasetToSelectOption } from './utils';
-import AsyncSelect from 'src/components/Select/AsyncSelect';
const localCache = new Map();
diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx
index 025567fda32e..3aa0fa60d2f9 100644
--- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx
+++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx
@@ -20,14 +20,13 @@ import React, { useMemo, useState, useCallback, useEffect } from 'react';
import Modal from 'src/components/Modal';
import { Input, TextArea } from 'src/components/Input';
import Button from 'src/components/Button';
-import { Select, Row, Col, AntdForm } from 'src/components';
+import { AsyncSelect, Row, Col, AntdForm } from 'src/components';
import { SelectValue } from 'antd/lib/select';
import rison from 'rison';
import { t, SupersetClient, styled } from '@superset-ui/core';
import Chart, { Slice } from 'src/types/Chart';
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
import withToasts from 'src/components/MessageToasts/withToasts';
-import AsyncSelect from 'src/components/Select/AsyncSelect';
export type PropertiesModalProps = {
slice: Slice;
diff --git a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
index a330e4e08d7a..fe0c091539fc 100644
--- a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
+++ b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
@@ -58,7 +58,7 @@ import {
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import { AlertReportCronScheduler } from './components/AlertReportCronScheduler';
import { NotificationMethod } from './components/NotificationMethod';
-import AsyncSelect from 'src/components/Select/AsyncSelect';
+import { AsyncSelect } from 'src/components';
const TIMEOUT_MIN = 1;
const TEXT_BASED_VISUALIZATION_TYPES = [
From 967f1b5e04eaa6b6f87699d5819c2b61261c420e Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Mon, 27 Jun 2022 11:38:08 -0400
Subject: [PATCH 03/20] fixed various import and lint warnings
---
.../Datasource/DatasourceEditor.jsx | 1 -
.../src/components/Select/AsyncSelect.tsx | 1 -
.../src/components/Select/Select.stories.tsx | 1 -
.../src/components/Select/Select.test.tsx | 22 +++----------------
.../src/views/CRUD/alert/AlertReportModal.tsx | 3 +--
5 files changed, 4 insertions(+), 24 deletions(-)
diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
index 10ee402d27f1..7985165f7338 100644
--- a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
@@ -47,7 +47,6 @@ import Icons from 'src/components/Icons';
import CollectionTable from './CollectionTable';
import Fieldset from './Fieldset';
import Field from './Field';
-import AsyncSelect from '../AsyncSelect';
const DatasourceContainer = styled.div`
.change-warning {
diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx
index a7a85bb39c82..f9ca861fb903 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.tsx
@@ -83,7 +83,6 @@ export type OptionsPagePromise = (
export type AsyncSelectRef = HTMLInputElement & { clearCache: () => void };
-
export interface AsyncSelectProps extends PickedSelectProps {
/**
* It enables the user to create new options.
diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx
index 8844de8856d1..b8b08b7f1233 100644
--- a/superset-frontend/src/components/Select/Select.stories.tsx
+++ b/superset-frontend/src/components/Select/Select.stories.tsx
@@ -24,7 +24,6 @@ import Select, {
SelectProps,
OptionsTypePage,
OptionsType,
- SelectRef,
} from './Select';
export default {
diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx
index 5655b12608dd..5a61e55d01c8 100644
--- a/superset-frontend/src/components/Select/Select.test.tsx
+++ b/superset-frontend/src/components/Select/Select.test.tsx
@@ -16,11 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { RefObject } from 'react';
+import React from 'react';
import { render, screen, waitFor, within } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { Select } from 'src/components';
-import { SelectRef } from './Select';
const ARIA_LABEL = 'Test';
const NEW_OPTION = 'Kyle';
@@ -56,21 +55,6 @@ const NULL_OPTION = { label: '', value: null } as unknown as {
value: number;
};
-const loadOptions = async (search: string, page: number, pageSize: number) => {
- const totalCount = OPTIONS.length;
- const start = page * pageSize;
- const deleteCount =
- start + pageSize < totalCount ? pageSize : totalCount - start;
- const data = OPTIONS.filter(option => option.label.match(search)).splice(
- start,
- deleteCount,
- );
- return {
- data,
- totalCount: OPTIONS.length,
- };
-};
-
const defaultProps = {
allowClear: true,
ariaLabel: ARIA_LABEL,
@@ -173,7 +157,7 @@ test('sort the options using a custom sort comparator', async () => {
render(
,
);
@@ -383,7 +367,7 @@ test('clear all the values', async () => {
});
test('does not add a new option if allowNewOptions is false', async () => {
- render();
+ render();
await open();
await type(NEW_OPTION);
expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
diff --git a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
index fe0c091539fc..e278d7239867 100644
--- a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
+++ b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
@@ -42,7 +42,7 @@ 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';
-import { AntdCheckbox } from 'src/components';
+import { AntdCheckbox, AsyncSelect } from 'src/components';
import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
import { useCommonConf } from 'src/views/CRUD/data/database/state';
import {
@@ -58,7 +58,6 @@ import {
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import { AlertReportCronScheduler } from './components/AlertReportCronScheduler';
import { NotificationMethod } from './components/NotificationMethod';
-import { AsyncSelect } from 'src/components';
const TIMEOUT_MIN = 1;
const TEXT_BASED_VISUALIZATION_TYPES = [
From cc549a5d8989991cc58f0574a52332395e386ee0 Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Wed, 29 Jun 2022 15:24:09 -0400
Subject: [PATCH 04/20] fixing lint errors
---
superset-frontend/src/components/Select/Select.stories.tsx | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx
index b8b08b7f1233..efcd91c0c38f 100644
--- a/superset-frontend/src/components/Select/Select.stories.tsx
+++ b/superset-frontend/src/components/Select/Select.stories.tsx
@@ -20,11 +20,7 @@ import React, { ReactNode, useState, useCallback, useRef } from 'react';
import Button from 'src/components/Button';
import ControlHeader from 'src/explore/components/ControlHeader';
import AsyncSelect, { AsyncSelectProps, AsyncSelectRef } from './AsyncSelect';
-import Select, {
- SelectProps,
- OptionsTypePage,
- OptionsType,
-} from './Select';
+import Select, { SelectProps, OptionsTypePage, OptionsType } from './Select';
export default {
title: 'Select',
From 7a165b49980797661724a57acb6394d4dbe8e5c5 Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Wed, 6 Jul 2022 10:00:59 -0400
Subject: [PATCH 05/20] fixed frontend test errors
---
.../src/addSlice/AddSliceContainer.test.tsx | 4 +-
.../components/Select/AsyncSelect.test.tsx | 90 +++++++++----------
.../src/components/Select/Select.test.tsx | 22 +----
3 files changed, 48 insertions(+), 68 deletions(-)
diff --git a/superset-frontend/src/addSlice/AddSliceContainer.test.tsx b/superset-frontend/src/addSlice/AddSliceContainer.test.tsx
index 6187f574867c..5cf855558d16 100644
--- a/superset-frontend/src/addSlice/AddSliceContainer.test.tsx
+++ b/superset-frontend/src/addSlice/AddSliceContainer.test.tsx
@@ -19,7 +19,7 @@
import React from 'react';
import { ReactWrapper } from 'enzyme';
import Button from 'src/components/Button';
-import { Select } from 'src/components';
+import { AsyncSelect } from 'src/components';
import AddSliceContainer, {
AddSliceContainerProps,
AddSliceContainerState,
@@ -72,7 +72,7 @@ async function getWrapper(user = mockUser) {
test('renders a select and a VizTypeControl', async () => {
const wrapper = await getWrapper();
- expect(wrapper.find(Select)).toExist();
+ expect(wrapper.find(AsyncSelect)).toExist();
expect(wrapper.find(VizTypeGallery)).toExist();
});
diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
index 2094252d5d27..82398b507bc6 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
@@ -19,7 +19,7 @@
import React from 'react';
import { render, screen, waitFor, within } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
-import { Select } from 'src/components';
+import { AsyncSelect } from 'src/components';
const ARIA_LABEL = 'Test';
const NEW_OPTION = 'Kyle';
@@ -125,19 +125,19 @@ const open = () => waitFor(() => userEvent.click(getSelect()));
test('displays a header', async () => {
const headerText = 'Header';
- render();
+ render();
expect(screen.getByText(headerText)).toBeInTheDocument();
});
test('adds a new option if the value is not in the options', async () => {
const { rerender } = render(
- ,
+ ,
);
await open();
expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();
rerender(
- ,
+ ,
);
await open();
const options = await findAllSelectOptions();
@@ -148,7 +148,7 @@ test('adds a new option if the value is not in the options', async () => {
});
test('inverts the selection', async () => {
- render();
+ render();
await open();
userEvent.click(await findSelectOption(OPTIONS[0].label));
expect(await screen.findByLabelText('stop')).toBeInTheDocument();
@@ -156,7 +156,7 @@ test('inverts the selection', async () => {
test('sort the options by label if no sort comparator is provided', async () => {
const unsortedOptions = [...OPTIONS].sort(() => Math.random());
- render();
+ render();
await open();
const options = await findAllSelectOptions();
options.forEach((option, key) =>
@@ -170,7 +170,7 @@ test('sort the options using a custom sort comparator', async () => {
option2: typeof OPTIONS[0],
) => option1.gender.localeCompare(option2.gender);
render(
- {
});
test('should sort selected to top when in single mode', async () => {
- render();
+ render();
const originalLabels = OPTIONS.map(option => option.label);
await open();
userEvent.click(await findSelectOption(originalLabels[1]));
@@ -218,7 +218,7 @@ test('should sort selected to top when in single mode', async () => {
});
test('should sort selected to the top when in multi mode', async () => {
- render();
+ render();
const originalLabels = OPTIONS.map(option => option.label);
let labels = originalLabels.slice();
@@ -247,7 +247,7 @@ test('should sort selected to the top when in multi mode', async () => {
test('searches for label or value', async () => {
const option = OPTIONS[11];
- render();
+ render();
const search = option.value;
await type(search.toString());
const options = await findAllSelectOptions();
@@ -256,7 +256,7 @@ test('searches for label or value', async () => {
});
test('search order exact and startWith match first', async () => {
- render();
+ render();
await type('Her');
const options = await findAllSelectOptions();
expect(options.length).toBe(4);
@@ -267,14 +267,14 @@ test('search order exact and startWith match first', async () => {
});
test('ignores case when searching', async () => {
- render();
+ render();
await type('george');
expect(await findSelectOption('George')).toBeInTheDocument();
});
test('same case should be ranked to the top', async () => {
render(
- {
});
test('ignores special keys when searching', async () => {
- render();
+ render();
await type('{shift}');
expect(screen.queryByText(LOADING)).not.toBeInTheDocument();
});
test('searches for custom fields', async () => {
- render();
+ render();
await type('Liam');
let options = await findAllSelectOptions();
expect(options.length).toBe(1);
@@ -319,7 +319,7 @@ test('searches for custom fields', async () => {
});
test('removes duplicated values', async () => {
- render();
+ render();
await type('a,b,b,b,c,d,d');
const values = await findAllSelectValues();
expect(values.length).toBe(4);
@@ -335,7 +335,7 @@ test('renders a custom label', async () => {
{ label: 'Liam', value: 2, customLabel: Liam
},
{ label: 'Olivia', value: 3, customLabel: Olivia
},
];
- render();
+ render();
await open();
expect(screen.getByRole('heading', { name: 'John' })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Liam' })).toBeInTheDocument();
@@ -348,7 +348,7 @@ test('searches for a word with a custom label', async () => {
{ label: 'Liam', value: 2, customLabel: Liam
},
{ label: 'Olivia', value: 3, customLabel: Olivia
},
];
- render();
+ render();
await type('Liam');
const selectOptions = await findAllSelectOptions();
expect(selectOptions.length).toBe(1);
@@ -356,7 +356,7 @@ test('searches for a word with a custom label', async () => {
});
test('removes a new option if the user does not select it', async () => {
- render();
+ render();
await type(NEW_OPTION);
expect(await findSelectOption(NEW_OPTION)).toBeInTheDocument();
await type('k');
@@ -368,7 +368,7 @@ test('removes a new option if the user does not select it', async () => {
test('clear all the values', async () => {
const onClear = jest.fn();
render(
- {
});
test('does not add a new option if allowNewOptions is false', async () => {
- render();
+ render();
await open();
await type(NEW_OPTION);
expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
});
test('adds the null option when selected in single mode', async () => {
- render();
+ render();
await open();
userEvent.click(await findSelectOption(NULL_OPTION.label));
const values = await findAllSelectValues();
@@ -398,7 +398,7 @@ test('adds the null option when selected in single mode', async () => {
test('adds the null option when selected in multiple mode', async () => {
render(
- {
});
test('renders the select with default props', () => {
- render();
+ render();
expect(getSelect()).toBeInTheDocument();
});
test('opens the select without any data', async () => {
render(
- ({ data: [], totalCount: 0 })}
/>,
@@ -429,7 +429,7 @@ test('opens the select without any data', async () => {
});
test('displays the loading indicator when opening', async () => {
- render();
+ render();
await waitFor(() => {
userEvent.click(getSelect());
expect(screen.getByText(LOADING)).toBeInTheDocument();
@@ -438,7 +438,7 @@ test('displays the loading indicator when opening', async () => {
});
test('makes a selection in single mode', async () => {
- render();
+ render();
const optionText = 'Emma';
await open();
userEvent.click(await findSelectOption(optionText));
@@ -446,7 +446,7 @@ test('makes a selection in single mode', async () => {
});
test('multiple selections in multiple mode', async () => {
- render();
+ render();
await open();
const [firstOption, secondOption] = OPTIONS;
userEvent.click(await findSelectOption(firstOption.label));
@@ -459,7 +459,7 @@ test('multiple selections in multiple mode', async () => {
test('changes the selected item in single mode', async () => {
const onChange = jest.fn();
render(
- ,
+ ,
);
await open();
const [firstOption, secondOption] = OPTIONS;
@@ -484,7 +484,7 @@ test('changes the selected item in single mode', async () => {
});
test('deselects an item in multiple mode', async () => {
- render();
+ render();
await open();
const option3 = OPTIONS[2];
const option8 = OPTIONS[7];
@@ -518,14 +518,14 @@ test('deselects an item in multiple mode', async () => {
});
test('adds a new option if none is available and allowNewOptions is true', async () => {
- render();
+ render();
await open();
await type(NEW_OPTION);
expect(await findSelectOption(NEW_OPTION)).toBeInTheDocument();
});
test('does not add a new option if the option already exists', async () => {
- render();
+ render();
const option = OPTIONS[0].label;
await open();
await type(option);
@@ -539,7 +539,7 @@ test('does not add a new option if the option already exists', async () => {
test('shows "No data" when allowNewOptions is false and a new option is entered', async () => {
render(
- {
- render();
+ render();
await open();
await type(NEW_OPTION);
expect(screen.queryByText(NO_DATA)).not.toBeInTheDocument();
});
test('sets a initial value in single mode', async () => {
- render();
+ render();
expect(await findSelectValue()).toHaveTextContent(OPTIONS[0].label);
});
test('sets a initial value in multiple mode', async () => {
render(
- {
});
test('searches for matches in both loaded and unloaded pages', async () => {
- render();
+ render();
await open();
await type('and');
@@ -595,7 +595,7 @@ test('searches for matches in both loaded and unloaded pages', async () => {
test('searches for an item in a page not loaded', async () => {
const mock = jest.fn(loadOptions);
- render();
+ render();
const search = 'Sandro';
await open();
await type(search);
@@ -607,20 +607,20 @@ test('searches for an item in a page not loaded', async () => {
test('does not fetches data when rendering', async () => {
const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
- render();
+ render();
expect(loadOptions).not.toHaveBeenCalled();
});
test('fetches data when opening', async () => {
const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
- render();
+ render();
await open();
expect(loadOptions).toHaveBeenCalled();
});
test('fetches data only after a search input is entered if fetchOnlyOnSearch is true', async () => {
const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
- render();
+ render();
await open();
await waitFor(() => expect(loadOptions).not.toHaveBeenCalled());
await type('search');
@@ -632,14 +632,14 @@ test('displays an error message when an exception is thrown while fetching', asy
const loadOptions = async () => {
throw new Error(error);
};
- render();
+ render();
await open();
expect(screen.getByText(error)).toBeInTheDocument();
});
test('does not fire a new request for the same search input', async () => {
const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
- render();
+ render();
await type('search');
expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
expect(loadOptions).toHaveBeenCalledTimes(1);
@@ -653,7 +653,7 @@ test('does not fire a new request if all values have been fetched', async () =>
const mock = jest.fn(loadOptions);
const search = 'George';
const pageSize = OPTIONS.length;
- render();
+ render();
await open();
expect(mock).toHaveBeenCalledTimes(1);
await type(search);
@@ -664,7 +664,7 @@ test('does not fire a new request if all values have been fetched', async () =>
test('fires a new request if all values have not been fetched', async () => {
const mock = jest.fn(loadOptions);
const pageSize = OPTIONS.length / 2;
- render();
+ render();
await open();
expect(mock).toHaveBeenCalledTimes(1);
await type('or');
diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx
index 5a61e55d01c8..8c0d6e6f15de 100644
--- a/superset-frontend/src/components/Select/Select.test.tsx
+++ b/superset-frontend/src/components/Select/Select.test.tsx
@@ -20,6 +20,7 @@ import React from 'react';
import { render, screen, waitFor, within } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { Select } from 'src/components';
+import { loadOptions } from '@babel/core';
const ARIA_LABEL = 'Test';
const NEW_OPTION = 'Kyle';
@@ -149,27 +150,6 @@ test('sort the options by label if no sort comparator is provided', async () =>
);
});
-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('should sort selected to top when in single mode', async () => {
render();
const originalLabels = OPTIONS.map(option => option.label);
From 684852a2848813b1c08cb2a2af174e876d6e66fd Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Wed, 6 Jul 2022 15:12:48 -0400
Subject: [PATCH 06/20] fixed alertreportmodel tests
---
.../src/components/Select/AsyncSelect.tsx | 14 +++++++++++++-
.../src/views/CRUD/alert/AlertReportModal.test.jsx | 14 ++++++++++----
.../src/views/CRUD/alert/AlertReportModal.tsx | 4 ++--
3 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx
index f9ca861fb903..2512e86e015e 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, {
+ import React, {
forwardRef,
ReactElement,
ReactNode,
@@ -27,6 +27,7 @@ import React, {
useState,
useRef,
useCallback,
+ useImperativeHandle,
} from 'react';
import { ensureIsArray, styled, t } from '@superset-ui/core';
import AntdSelect, {
@@ -680,6 +681,17 @@ const AsyncSelect = (
}
}, [isLoading, loading]);
+ const clearCache = () => fetchedQueries.current.clear();
+
+ useImperativeHandle(
+ ref,
+ () => ({
+ ...(ref.current as HTMLInputElement),
+ clearCache,
+ }),
+ [ref],
+ );
+
return (
{header}
diff --git a/superset-frontend/src/views/CRUD/alert/AlertReportModal.test.jsx b/superset-frontend/src/views/CRUD/alert/AlertReportModal.test.jsx
index 1598e5a926f3..8b3ff1d016ab 100644
--- a/superset-frontend/src/views/CRUD/alert/AlertReportModal.test.jsx
+++ b/superset-frontend/src/views/CRUD/alert/AlertReportModal.test.jsx
@@ -24,7 +24,7 @@ import fetchMock from 'fetch-mock';
import { act } from 'react-dom/test-utils';
import AlertReportModal from 'src/views/CRUD/alert/AlertReportModal';
import Modal from 'src/components/Modal';
-import { Select } from 'src/components';
+import { Select, AsyncSelect } from 'src/components';
import { Switch } from 'src/components/Switch';
import { Radio } from 'src/components/Radio';
import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
@@ -182,7 +182,9 @@ describe('AlertReportModal', () => {
it('renders five select elements when in report mode', () => {
expect(wrapper.find(Select)).toExist();
- expect(wrapper.find(Select)).toHaveLength(5);
+ expect(wrapper.find(AsyncSelect)).toExist();
+ expect(wrapper.find(Select)).toHaveLength(2);
+ expect(wrapper.find(AsyncSelect)).toHaveLength(3);
});
it('renders Switch element', () => {
@@ -220,7 +222,9 @@ describe('AlertReportModal', () => {
it('renders five select element when in report mode', () => {
expect(wrapper.find(Select)).toExist();
- expect(wrapper.find(Select)).toHaveLength(5);
+ expect(wrapper.find(AsyncSelect)).toExist();
+ expect(wrapper.find(Select)).toHaveLength(2);
+ expect(wrapper.find(AsyncSelect)).toHaveLength(3);
});
it('renders seven select elements when in alert mode', async () => {
@@ -232,7 +236,9 @@ describe('AlertReportModal', () => {
const addWrapper = await mountAndWait(props);
expect(addWrapper.find(Select)).toExist();
- expect(addWrapper.find(Select)).toHaveLength(7);
+ expect(addWrapper.find(AsyncSelect)).toExist();
+ expect(addWrapper.find(Select)).toHaveLength(3);
+ expect(addWrapper.find(AsyncSelect)).toHaveLength(4);
});
it('renders value input element when in alert mode', async () => {
diff --git a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
index e278d7239867..820a83b8c337 100644
--- a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
+++ b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx
@@ -38,11 +38,11 @@ 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, { propertyComparator } from 'src/components/Select/Select';
+import { 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';
-import { AntdCheckbox, AsyncSelect } from 'src/components';
+import { AntdCheckbox, AsyncSelect, Select } from 'src/components';
import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
import { useCommonConf } from 'src/views/CRUD/data/database/state';
import {
From 405e8e4371d02642ee487a8420a14723eaec401b Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Thu, 7 Jul 2022 09:04:44 -0400
Subject: [PATCH 07/20] removed accidental import
---
superset-frontend/src/components/Select/Select.test.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx
index 3b5bd219653f..0b353bde38ac 100644
--- a/superset-frontend/src/components/Select/Select.test.tsx
+++ b/superset-frontend/src/components/Select/Select.test.tsx
@@ -20,7 +20,6 @@ import React from 'react';
import { render, screen, waitFor, within } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { Select } from 'src/components';
-import { loadOptions } from '@babel/core';
const ARIA_LABEL = 'Test';
const NEW_OPTION = 'Kyle';
From 593c521fbc04a0ae8499b16572a68a111db5ed8b Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Thu, 7 Jul 2022 09:53:02 -0400
Subject: [PATCH 08/20] fixed lint errors
---
.../components/Select/AsyncSelect.test.tsx | 36 ++++++++++++++-----
.../src/components/Select/AsyncSelect.tsx | 2 +-
2 files changed, 28 insertions(+), 10 deletions(-)
diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
index 82398b507bc6..dc6eff35d942 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
@@ -300,7 +300,9 @@ test('ignores special keys when searching', async () => {
});
test('searches for custom fields', async () => {
- render();
+ render(
+ ,
+ );
await type('Liam');
let options = await findAllSelectOptions();
expect(options.length).toBe(1);
@@ -446,7 +448,9 @@ test('makes a selection in single mode', async () => {
});
test('multiple selections in multiple mode', async () => {
- render();
+ render(
+ ,
+ );
await open();
const [firstOption, secondOption] = OPTIONS;
userEvent.click(await findSelectOption(firstOption.label));
@@ -484,7 +488,9 @@ test('changes the selected item in single mode', async () => {
});
test('deselects an item in multiple mode', async () => {
- render();
+ render(
+ ,
+ );
await open();
const option3 = OPTIONS[2];
const option8 = OPTIONS[7];
@@ -518,14 +524,18 @@ test('deselects an item in multiple mode', async () => {
});
test('adds a new option if none is available and allowNewOptions is true', async () => {
- render();
+ render(
+ ,
+ );
await open();
await type(NEW_OPTION);
expect(await findSelectOption(NEW_OPTION)).toBeInTheDocument();
});
test('does not add a new option if the option already exists', async () => {
- render();
+ render(
+ ,
+ );
const option = OPTIONS[0].label;
await open();
await type(option);
@@ -552,14 +562,18 @@ test('shows "No data" when allowNewOptions is false and a new option is entered'
});
test('does not show "No data" when allowNewOptions is true and a new option is entered', async () => {
- render();
+ render(
+ ,
+ );
await open();
await type(NEW_OPTION);
expect(screen.queryByText(NO_DATA)).not.toBeInTheDocument();
});
test('sets a initial value in single mode', async () => {
- render();
+ render(
+ ,
+ );
expect(await findSelectValue()).toHaveTextContent(OPTIONS[0].label);
});
@@ -620,7 +634,9 @@ test('fetches data when opening', async () => {
test('fetches data only after a search input is entered if fetchOnlyOnSearch is true', async () => {
const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
- render();
+ render(
+ ,
+ );
await open();
await waitFor(() => expect(loadOptions).not.toHaveBeenCalled());
await type('search');
@@ -639,7 +655,9 @@ test('displays an error message when an exception is thrown while fetching', asy
test('does not fire a new request for the same search input', async () => {
const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
- render();
+ render(
+ ,
+ );
await type('search');
expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
expect(loadOptions).toHaveBeenCalledTimes(1);
diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx
index 2512e86e015e..41b060d5ba83 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
- import React, {
+import React, {
forwardRef,
ReactElement,
ReactNode,
From 0483fe7ecd8f96772e4a787edb979b4d51cd4f02 Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Thu, 7 Jul 2022 13:57:07 -0400
Subject: [PATCH 09/20] updated async select
---
superset-frontend/src/components/Select/AsyncSelect.tsx | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx
index 41b060d5ba83..98f146f15f2a 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.tsx
@@ -67,6 +67,7 @@ type PickedSelectProps = Pick<
| 'showSearch'
| 'tokenSeparators'
| 'value'
+ | 'getPopupContainer'
>;
export type OptionsType = Exclude;
@@ -316,6 +317,7 @@ const AsyncSelect = (
sortComparator = DEFAULT_SORT_COMPARATOR,
tokenSeparators,
value,
+ getPopupContainer,
...props
}: AsyncSelectProps,
ref: RefObject,
@@ -701,7 +703,9 @@ const AsyncSelect = (
dropdownRender={dropdownRender}
filterOption={handleFilterOption}
filterSort={sortComparatorWithSearch}
- getPopupContainer={triggerNode => triggerNode.parentNode}
+ getPopupContainer={
+ getPopupContainer || (triggerNode => triggerNode.parentNode)
+ }
labelInValue={isAsync || labelInValue}
maxTagCount={MAX_TAG_COUNT}
mode={mappedMode}
From 251b0ea58863428b9c081033554f3b014df14a10 Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Mon, 11 Jul 2022 11:25:01 -0400
Subject: [PATCH 10/20] removed code from select component
---
.../components/Select/AsyncSelect.test.tsx | 104 ++++++----
.../src/components/Select/AsyncSelect.tsx | 56 +++---
.../src/components/Select/Select.test.tsx | 24 ++-
.../src/components/Select/Select.tsx | 187 ++----------------
4 files changed, 132 insertions(+), 239 deletions(-)
diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
index dc6eff35d942..0239fa902fa9 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
@@ -60,7 +60,17 @@ const loadOptions = async (search: string, page: number, pageSize: number) => {
const start = page * pageSize;
const deleteCount =
start + pageSize < totalCount ? pageSize : totalCount - start;
- const data = OPTIONS.filter(option => option.label.match(search)).splice(
+ const searchValue = search.trim().toLowerCase();
+ const optionFilterProps = ['label', 'value', 'gender'];
+
+ const data = OPTIONS.filter(option => {
+ return optionFilterProps.some(prop => {
+ const optionProp = option?.[prop]
+ ? String(option[prop]).trim().toLowerCase()
+ : '';
+ return optionProp.includes(searchValue);
+ });
+ }).splice(
start,
deleteCount,
);
@@ -74,7 +84,7 @@ const defaultProps = {
allowClear: true,
ariaLabel: ARIA_LABEL,
labelInValue: true,
- options: OPTIONS,
+ options: loadOptions,
pageSize: 10,
showSearch: true,
};
@@ -130,17 +140,18 @@ test('displays a header', async () => {
});
test('adds a new option if the value is not in the options', async () => {
+ let loadOptions = jest.fn(async () => ({ data: [OPTIONS[1]], totalCount: 0 }));
const { rerender } = render(
- ,
+ ,
);
await open();
expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();
-
+ let reloadOptions = jest.fn(async () => ({ data: [], totalCount: 2}));
rerender(
- ,
+ ,
);
- await open();
const options = await findAllSelectOptions();
+ expect(await findSelectOption(OPTIONS[1].label)).toBeInTheDocument();
expect(options).toHaveLength(2);
options.forEach((option, i) =>
expect(option).toHaveTextContent(OPTIONS[i].label),
@@ -155,8 +166,8 @@ test('inverts the selection', async () => {
});
test('sort the options by label if no sort comparator is provided', async () => {
- const unsortedOptions = [...OPTIONS].sort(() => Math.random());
- render();
+ const loadUnsortedOptions = jest.fn(async () => ({ data: [...OPTIONS].sort(() => Math.random()), totalCount: 2 }));
+ render();
await open();
const options = await findAllSelectOptions();
options.forEach((option, key) =>
@@ -250,20 +261,23 @@ test('searches for label or value', async () => {
render();
const search = option.value;
await type(search.toString());
+ expect(await findSelectOption(option.label)).toBeInTheDocument();
const options = await findAllSelectOptions();
expect(options.length).toBe(1);
expect(options[0]).toHaveTextContent(option.label);
});
test('search order exact and startWith match first', async () => {
- render();
+ render();
+ await open();
await type('Her');
+ expect(await findSelectOption('Guilherme')).toBeInTheDocument();
const options = await findAllSelectOptions();
expect(options.length).toBe(4);
- expect(options[0]?.textContent).toEqual('Her');
- expect(options[1]?.textContent).toEqual('Herme');
- expect(options[2]?.textContent).toEqual('Cher');
- expect(options[3]?.textContent).toEqual('Guilherme');
+ expect(options[0]).toHaveTextContent('Her');
+ expect(options[1]).toHaveTextContent('Herme');
+ expect(options[2]).toHaveTextContent('Cher');
+ expect(options[3]).toHaveTextContent('Guilherme');
});
test('ignores case when searching', async () => {
@@ -273,15 +287,19 @@ test('ignores case when searching', async () => {
});
test('same case should be ranked to the top', async () => {
- render(
- (
+ { data: [
{ value: 'Cac' },
{ value: 'abac' },
{ value: 'acbc' },
{ value: 'CAc' },
- ]}
+ ], totalCount: 4
+ }
+ ));
+ render(
+ ,
);
await type('Ac');
@@ -294,20 +312,28 @@ test('same case should be ranked to the top', async () => {
});
test('ignores special keys when searching', async () => {
- render();
+ render();
await type('{shift}');
expect(screen.queryByText(LOADING)).not.toBeInTheDocument();
});
test('searches for custom fields', async () => {
render(
- ,
+ ,
);
+ await open();
await type('Liam');
+ expect(await findSelectOption('Liam')).toBeInTheDocument();
let options = await findAllSelectOptions();
expect(options.length).toBe(1);
expect(options[0]).toHaveTextContent('Liam');
await type('Female');
+ // expect(await findSelectOption('Ava')).toBeInTheDocument();
+ // expect(await findSelectOption('Charlotte')).toBeInTheDocument();
+ // expect(await findSelectOption('Cher')).toBeInTheDocument();
+ // expect(await findSelectOption('Emma')).toBeInTheDocument();
+ // expect(await findSelectOption('Nikole')).toBeInTheDocument();
+ expect(await findSelectOption('Olivia')).toBeInTheDocument();
options = await findAllSelectOptions();
expect(options.length).toBe(6);
expect(options[0]).toHaveTextContent('Ava');
@@ -317,7 +343,7 @@ test('searches for custom fields', async () => {
expect(options[4]).toHaveTextContent('Nikole');
expect(options[5]).toHaveTextContent('Olivia');
await type('1');
- expect(screen.getByText(NO_DATA)).toBeInTheDocument();
+ expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
});
test('removes duplicated values', async () => {
@@ -332,12 +358,15 @@ test('removes duplicated values', async () => {
});
test('renders a custom label', async () => {
- const options = [
- { label: 'John', value: 1, customLabel: John
},
- { label: 'Liam', value: 2, customLabel: Liam
},
- { label: 'Olivia', value: 3, customLabel: Olivia
},
- ];
- render();
+ const loadOptions = jest.fn(async () => (
+ { data: [
+ { label: 'John', value: 1, customLabel: John
},
+ { label: 'Liam', value: 2, customLabel: Liam
},
+ { label: 'Olivia', value: 3, customLabel: Olivia
},
+ ], totalCount: 3
+ }
+ ));
+ render();
await open();
expect(screen.getByRole('heading', { name: 'John' })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Liam' })).toBeInTheDocument();
@@ -345,12 +374,15 @@ test('renders a custom label', async () => {
});
test('searches for a word with a custom label', async () => {
- const options = [
- { label: 'John', value: 1, customLabel: John
},
- { label: 'Liam', value: 2, customLabel: Liam
},
- { label: 'Olivia', value: 3, customLabel: Olivia
},
- ];
- render();
+ const loadOptions = jest.fn(async () => (
+ { data: [
+ { label: 'John', value: 1, customLabel: John
},
+ { label: 'Liam', value: 2, customLabel: Liam
},
+ { label: 'Olivia', value: 3, customLabel: Olivia
},
+ ], totalCount: 3
+ }
+ ));
+ render();
await type('Liam');
const selectOptions = await findAllSelectOptions();
expect(selectOptions.length).toBe(1);
@@ -391,7 +423,8 @@ test('does not add a new option if allowNewOptions is false', async () => {
});
test('adds the null option when selected in single mode', async () => {
- render();
+ const loadOptions = jest.fn(async () => ({ data: [OPTIONS[0], NULL_OPTION], totalCount: 2 }));
+ render();
await open();
userEvent.click(await findSelectOption(NULL_OPTION.label));
const values = await findAllSelectValues();
@@ -399,10 +432,11 @@ test('adds the null option when selected in single mode', async () => {
});
test('adds the null option when selected in multiple mode', async () => {
+ const loadOptions = jest.fn(async () => ({ data: [OPTIONS[0], NULL_OPTION], totalCount: 2 }));
render(
,
);
diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx
index 98f146f15f2a..00681341385f 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.tsx
@@ -129,11 +129,10 @@ export interface AsyncSelectProps extends PickedSelectProps {
optionFilterProps?: string[];
/**
* It defines the options of the Select.
- * The options can be static, an array of options.
- * The options can also be async, a promise that returns
+ * The options are async, a promise that returns
* an array of options.
*/
- options: OptionsType | OptionsPagePromise;
+ options: OptionsPagePromise;
/**
* It defines how many results should be included
* in the query response.
@@ -322,9 +321,8 @@ const AsyncSelect = (
}: AsyncSelectProps,
ref: RefObject,
) => {
- const isAsync = typeof options === 'function';
const isSingleMode = mode === 'single';
- const shouldShowSearch = isAsync || allowNewOptions ? true : showSearch;
+ const shouldShowSearch = allowNewOptions ? true : showSearch;
const [selectValue, setSelectValue] = useState(value);
const [inputValue, setInputValue] = useState('');
const [isLoading, setIsLoading] = useState(loading);
@@ -360,8 +358,8 @@ const AsyncSelect = (
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],
+ sortComparator(a, b, ''),
+ [sortComparator, sortSelectedFirst],
);
const initialOptions = useMemo(
@@ -528,7 +526,6 @@ const AsyncSelect = (
setSelectOptions(newOptions);
}
if (
- isAsync &&
!allValuesLoaded &&
loadingEnabled &&
!fetchedQueries.current.has(getQueryCacheKey(searchValue, 0, pageSize))
@@ -546,7 +543,7 @@ const AsyncSelect = (
vScroll.scrollTop > (vScroll.scrollHeight - vScroll.offsetHeight) * 0.7;
const hasMoreData = page * pageSize + pageSize < totalCount;
- if (!isLoading && isAsync && hasMoreData && thresholdReached) {
+ if (!isLoading && hasMoreData && thresholdReached) {
const newPage = page + 1;
fetchPage(inputValue, newPage);
}
@@ -575,30 +572,24 @@ const AsyncSelect = (
const handleOnDropdownVisibleChange = (isDropdownVisible: boolean) => {
setIsDropdownVisible(isDropdownVisible);
- if (isAsync) {
- // loading is enabled when dropdown is open,
- // disabled when dropdown is closed
- if (loadingEnabled !== isDropdownVisible) {
- setLoadingEnabled(isDropdownVisible);
- }
- // when closing dropdown, always reset loading state
- if (!isDropdownVisible && isLoading) {
- // delay is for the animation of closing the dropdown
- // so the dropdown doesn't flash between "Loading..." and "No data"
- // before closing.
- setTimeout(() => {
- setIsLoading(false);
- }, 250);
- }
+ // loading is enabled when dropdown is open,
+ // disabled when dropdown is closed
+ if (loadingEnabled !== isDropdownVisible) {
+ setLoadingEnabled(isDropdownVisible);
+ }
+ // when closing dropdown, always reset loading state
+ if (!isDropdownVisible && isLoading) {
+ // delay is for the animation of closing the dropdown
+ // so the dropdown doesn't flash between "Loading..." and "No data"
+ // before closing.
+ setTimeout(() => {
+ setIsLoading(false);
+ }, 250);
}
// if no search input value, force sort options because it won't be sorted by
// `filterSort`.
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;
+ const sortedOptions = selectOptions.slice().sort(sortComparatorForNoSearch);
if (!isEqual(sortedOptions, selectOptions)) {
setSelectOptions(sortedOptions);
}
@@ -660,7 +651,7 @@ const AsyncSelect = (
);
useEffect(() => {
- if (isAsync && loadingEnabled && allowFetch) {
+ if (loadingEnabled && allowFetch) {
// trigger fetch every time inputValue changes
if (inputValue) {
debouncedFetchPage(inputValue, 0);
@@ -669,7 +660,6 @@ const AsyncSelect = (
}
}
}, [
- isAsync,
loadingEnabled,
fetchPage,
allowFetch,
@@ -706,13 +696,13 @@ const AsyncSelect = (
getPopupContainer={
getPopupContainer || (triggerNode => triggerNode.parentNode)
}
- labelInValue={isAsync || labelInValue}
+ labelInValue={labelInValue}
maxTagCount={MAX_TAG_COUNT}
mode={mappedMode}
notFoundContent={isLoading ? t('Loading...') : notFoundContent}
onDeselect={handleOnDeselect}
onDropdownVisibleChange={handleOnDropdownVisibleChange}
- onPopupScroll={isAsync ? handlePagination : undefined}
+ onPopupScroll={handlePagination}
onSearch={shouldShowSearch ? handleOnSearch : undefined}
onSelect={handleOnSelect}
onClear={handleClear}
diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx
index 0b353bde38ac..fc6920eea67a 100644
--- a/superset-frontend/src/components/Select/Select.test.tsx
+++ b/superset-frontend/src/components/Select/Select.test.tsx
@@ -114,24 +114,46 @@ test('displays a header', async () => {
expect(screen.getByText(headerText)).toBeInTheDocument();
});
+// test('adds a new option if the value is not in the options', async () => {
+// const { rerender } = render(
+// ,
+// );
+// await open();
+// let options = await findAllSelectOptions();
+// expect(await findSelectOption(OPTIONS[2].label)).toBeInTheDocument();
+// expect(options).toHaveLength(1);
+
+// rerender(
+// ,
+// );
+// await open();
+// options = await findAllSelectOptions();
+// expect(await findSelectOption(OPTIONS[1].label)).toBeInTheDocument();
+// // expect(options).toHaveLength(2);
+// options.forEach((option, i) =>
+// expect(option).toHaveTextContent(OPTIONS[i].label),
+// );
+// });
+
test('adds a new option if the value is not in the options', async () => {
const { rerender } = render(
,
);
await open();
expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();
-
rerender(
,
);
await open();
const options = await findAllSelectOptions();
expect(options).toHaveLength(2);
+ expect(await findSelectOption(OPTIONS[1].label)).toBeInTheDocument();
options.forEach((option, i) =>
expect(option).toHaveTextContent(OPTIONS[i].label),
);
});
+
test('inverts the selection', async () => {
render();
await open();
diff --git a/superset-frontend/src/components/Select/Select.tsx b/superset-frontend/src/components/Select/Select.tsx
index 9cea5a726df0..2c76176864bc 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -77,12 +77,6 @@ export type OptionsTypePage = {
totalCount: number;
};
-export type OptionsPagePromise = (
- search: string,
- page: number,
- pageSize: number,
-) => Promise;
-
export type SelectRef = HTMLInputElement & { clearCache: () => void };
export interface SelectProps extends PickedSelectProps {
@@ -133,7 +127,7 @@ export interface SelectProps extends PickedSelectProps {
* The options can also be async, a promise that returns
* an array of options.
*/
- options: OptionsType | OptionsPagePromise;
+ options: OptionsType;
/**
* It defines how many results should be included
* in the query response.
@@ -322,25 +316,19 @@ const Select = (
}: SelectProps,
ref: RefObject,
) => {
- const isAsync = typeof options === 'function';
const isSingleMode = mode === 'single';
- const shouldShowSearch = isAsync || allowNewOptions ? true : showSearch;
+ const shouldShowSearch = allowNewOptions ? true : showSearch;
const [selectValue, setSelectValue] = useState(value);
const [inputValue, setInputValue] = useState('');
const [isLoading, setIsLoading] = useState(loading);
const [error, setError] = useState('');
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
- const [page, setPage] = useState(0);
- const [totalCount, setTotalCount] = useState(0);
- const [loadingEnabled, setLoadingEnabled] = useState(!lazyLoading);
- const [allValuesLoaded, setAllValuesLoaded] = useState(false);
const fetchedQueries = useRef(new Map());
const mappedMode = isSingleMode
? undefined
: allowNewOptions
? 'tags'
: 'multiple';
- const allowFetch = !fetchOnlyOnSearch || inputValue;
const sortSelectedFirst = useCallback(
(a: AntdLabeledValue, b: AntdLabeledValue) =>
@@ -357,11 +345,10 @@ const Select = (
);
const sortComparatorForNoSearch = useCallback(
(a: AntdLabeledValue, b: AntdLabeledValue) =>
- 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],
+ sortSelectedFirst(a, b) ||
+ // we should preserve the options order as much as possible.
+ 0,
+ [sortComparator, sortSelectedFirst],
);
const initialOptions = useMemo(
@@ -439,77 +426,7 @@ const Select = (
}),
[onError],
);
-
- const mergeData = useCallback(
- (data: OptionsType) => {
- let mergedData: OptionsType = [];
- if (data && Array.isArray(data) && data.length) {
- // unique option values should always be case sensitive so don't lowercase
- const dataValues = new Set(data.map(opt => opt.value));
- // merges with existing and creates unique options
- setSelectOptions(prevOptions => {
- mergedData = prevOptions
- .filter(previousOption => !dataValues.has(previousOption.value))
- .concat(data)
- .sort(sortComparatorForNoSearch);
- return mergedData;
- });
- }
- return mergedData;
- },
- [sortComparatorForNoSearch],
- );
-
- const fetchPage = useMemo(
- () => (search: string, page: number) => {
- setPage(page);
- if (allValuesLoaded) {
- setIsLoading(false);
- return;
- }
- const key = getQueryCacheKey(search, page, pageSize);
- const cachedCount = fetchedQueries.current.get(key);
- if (cachedCount !== undefined) {
- setTotalCount(cachedCount);
- setIsLoading(false);
- return;
- }
- setIsLoading(true);
- const fetchOptions = options as OptionsPagePromise;
- fetchOptions(search, page, pageSize)
- .then(({ data, totalCount }: OptionsTypePage) => {
- const mergedData = mergeData(data);
- fetchedQueries.current.set(key, totalCount);
- setTotalCount(totalCount);
- if (
- !fetchOnlyOnSearch &&
- value === '' &&
- mergedData.length >= totalCount
- ) {
- setAllValuesLoaded(true);
- }
- })
- .catch(internalOnError)
- .finally(() => {
- setIsLoading(false);
- });
- },
- [
- allValuesLoaded,
- fetchOnlyOnSearch,
- mergeData,
- internalOnError,
- options,
- pageSize,
- value,
- ],
- );
-
- const debouncedFetchPage = useMemo(
- () => debounce(fetchPage, SLOW_DEBOUNCE),
- [fetchPage],
- );
-
+
const handleOnSearch = (search: string) => {
const searchValue = search.trim();
if (allowNewOptions && isSingleMode) {
@@ -527,31 +444,9 @@ const Select = (
: cleanSelectOptions;
setSelectOptions(newOptions);
}
- if (
- isAsync &&
- !allValuesLoaded &&
- loadingEnabled &&
- !fetchedQueries.current.has(getQueryCacheKey(searchValue, 0, pageSize))
- ) {
- // if fetch only on search but search value is empty, then should not be
- // in loading state
- setIsLoading(!(fetchOnlyOnSearch && !searchValue));
- }
setInputValue(search);
};
- const handlePagination = (e: UIEvent) => {
- const vScroll = e.currentTarget;
- const thresholdReached =
- vScroll.scrollTop > (vScroll.scrollHeight - vScroll.offsetHeight) * 0.7;
- const hasMoreData = page * pageSize + pageSize < totalCount;
-
- if (!isLoading && isAsync && hasMoreData && thresholdReached) {
- const newPage = page + 1;
- fetchPage(inputValue, newPage);
- }
- };
-
const handleFilterOption = (search: string, option: AntdLabeledValue) => {
if (typeof filterOption === 'function') {
return filterOption(search, option);
@@ -575,35 +470,13 @@ const Select = (
const handleOnDropdownVisibleChange = (isDropdownVisible: boolean) => {
setIsDropdownVisible(isDropdownVisible);
- if (isAsync) {
- // loading is enabled when dropdown is open,
- // disabled when dropdown is closed
- if (loadingEnabled !== isDropdownVisible) {
- setLoadingEnabled(isDropdownVisible);
- }
- // when closing dropdown, always reset loading state
- if (!isDropdownVisible && isLoading) {
- // delay is for the animation of closing the dropdown
- // so the dropdown doesn't flash between "Loading..." and "No data"
- // before closing.
- setTimeout(() => {
- setIsLoading(false);
- }, 250);
- }
- }
// if no search input value, force sort options because it won't be sorted by
// `filterSort`.
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);
+ if (!isEqual(initialOptionsSorted, selectOptions)) {
+ setSelectOptions(initialOptionsSorted);
}
}
-
if (onDropdownVisibleChange) {
onDropdownVisibleChange(isDropdownVisible);
}
@@ -640,43 +513,17 @@ const Select = (
}
};
- useEffect(() => {
- // when `options` list is updated from component prop, reset states
- fetchedQueries.current.clear();
- setAllValuesLoaded(false);
- setSelectOptions(initialOptions);
- }, [initialOptions]);
+ // useEffect(() => {
+ // // when `options` list is updated from component prop, reset states
+ // fetchedQueries.current.clear();
+ // setAllValuesLoaded(false);
+ // setSelectOptions(initialOptions);
+ // }, [initialOptions]);
useEffect(() => {
setSelectValue(value);
}, [value]);
- // Stop the invocation of the debounced function after unmounting
- useEffect(
- () => () => {
- debouncedFetchPage.cancel();
- },
- [debouncedFetchPage],
- );
-
- useEffect(() => {
- if (isAsync && loadingEnabled && allowFetch) {
- // trigger fetch every time inputValue changes
- if (inputValue) {
- debouncedFetchPage(inputValue, 0);
- } else {
- fetchPage('', 0);
- }
- }
- }, [
- isAsync,
- loadingEnabled,
- fetchPage,
- allowFetch,
- inputValue,
- debouncedFetchPage,
- ]);
-
useEffect(() => {
if (loading !== undefined && loading !== isLoading) {
setIsLoading(loading);
@@ -706,13 +553,13 @@ const Select = (
getPopupContainer={
getPopupContainer || (triggerNode => triggerNode.parentNode)
}
- labelInValue={isAsync || labelInValue}
+ labelInValue={labelInValue}
maxTagCount={MAX_TAG_COUNT}
mode={mappedMode}
notFoundContent={isLoading ? t('Loading...') : notFoundContent}
onDeselect={handleOnDeselect}
onDropdownVisibleChange={handleOnDropdownVisibleChange}
- onPopupScroll={isAsync ? handlePagination : undefined}
+ onPopupScroll={undefined}
onSearch={shouldShowSearch ? handleOnSearch : undefined}
onSelect={handleOnSelect}
onClear={handleClear}
From 519ad3812605bf773424d63d38e318bb49ff8b67 Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Mon, 11 Jul 2022 16:02:36 -0400
Subject: [PATCH 11/20] fixed select test
---
.../components/Select/AsyncSelect.test.tsx | 36 +++++++++++---
.../src/components/Select/Select.test.tsx | 47 +++++++++----------
2 files changed, 51 insertions(+), 32 deletions(-)
diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
index ef3367479994..a84e689d81dc 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
@@ -139,25 +139,47 @@ test('displays a header', async () => {
expect(screen.getByText(headerText)).toBeInTheDocument();
});
-test('adds a new option if the value is not in the options', async () => {
- let loadOptions = jest.fn(async () => ({ data: [OPTIONS[1]], totalCount: 0 }));
- const { rerender } = render(
+test('adds a new option if the value is not in the options, when options are empty', async () => {
+ const loadOptions = jest.fn(async () => ({ data: [OPTIONS[1]], totalCount: 0 }));
+ render(
,
);
await open();
expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();
- let reloadOptions = jest.fn(async () => ({ data: [], totalCount: 2}));
- rerender(
- ,
- );
const options = await findAllSelectOptions();
+ expect(options).toHaveLength(1);
+ options.forEach((option, i) =>
+ expect(option).toHaveTextContent(OPTIONS[i].label),
+ );
+});
+
+test('adds a new option if the value is not in the options, when options have values', async () => {
+ const loadOptions = jest.fn(async () => ({ data: [OPTIONS[1]], totalCount: 1}));
+ render(
+ ,
+ );
+ await open();
+ expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();
expect(await findSelectOption(OPTIONS[1].label)).toBeInTheDocument();
+ const options = await findAllSelectOptions();
expect(options).toHaveLength(2);
options.forEach((option, i) =>
expect(option).toHaveTextContent(OPTIONS[i].label),
);
});
+test('does not add a new option if the value is already in the options', async () => {
+ const loadOptions = jest.fn(async () => ({ data: [OPTIONS[0]], totalCount: 1}));
+ render(
+ ,
+ );
+ await open();
+ expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();
+ const options = await findAllSelectOptions();
+ expect(options).toHaveLength(1);
+});
+
+
test('inverts the selection', async () => {
render();
await open();
diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx
index fc6920eea67a..b6c101efb1e5 100644
--- a/superset-frontend/src/components/Select/Select.test.tsx
+++ b/superset-frontend/src/components/Select/Select.test.tsx
@@ -114,45 +114,42 @@ test('displays a header', async () => {
expect(screen.getByText(headerText)).toBeInTheDocument();
});
-// test('adds a new option if the value is not in the options', async () => {
-// const { rerender } = render(
-// ,
-// );
-// await open();
-// let options = await findAllSelectOptions();
-// expect(await findSelectOption(OPTIONS[2].label)).toBeInTheDocument();
-// expect(options).toHaveLength(1);
-
-// rerender(
-// ,
-// );
-// await open();
-// options = await findAllSelectOptions();
-// expect(await findSelectOption(OPTIONS[1].label)).toBeInTheDocument();
-// // expect(options).toHaveLength(2);
-// options.forEach((option, i) =>
-// expect(option).toHaveTextContent(OPTIONS[i].label),
-// );
-// });
-
-test('adds a new option if the value is not in the options', async () => {
- const { rerender } = render(
+test('adds a new option if the value is not in the options, when options are empty', async () => {
+ render(
,
);
await open();
expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();
- rerender(
+ const options = await findAllSelectOptions();
+ expect(options).toHaveLength(1);
+ options.forEach((option, i) =>
+ expect(option).toHaveTextContent(OPTIONS[i].label),
+ );
+});
+
+test('adds a new option if the value is not in the options, when options have values', async () => {
+ render(
,
);
await open();
+ expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();
+ expect(await findSelectOption(OPTIONS[1].label)).toBeInTheDocument();
const options = await findAllSelectOptions();
expect(options).toHaveLength(2);
- expect(await findSelectOption(OPTIONS[1].label)).toBeInTheDocument();
options.forEach((option, i) =>
expect(option).toHaveTextContent(OPTIONS[i].label),
);
});
+test('does not add a new option if the value is already in the options', async () => {
+ render(
+ ,
+ );
+ await open();
+ expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();
+ const options = await findAllSelectOptions();
+ expect(options).toHaveLength(1);
+});
test('inverts the selection', async () => {
render();
From 6ee9849670d7cd08cfb900064e59bfa9421df587 Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Tue, 12 Jul 2022 16:10:11 -0400
Subject: [PATCH 12/20] fixed async label value and select initial values
---
.../src/components/Select/AsyncSelect.test.tsx | 2 +-
.../src/components/Select/AsyncSelect.tsx | 2 +-
superset-frontend/src/components/Select/Select.tsx | 11 +++++------
3 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
index a84e689d81dc..8e4eea428da1 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
@@ -140,7 +140,7 @@ test('displays a header', async () => {
});
test('adds a new option if the value is not in the options, when options are empty', async () => {
- const loadOptions = jest.fn(async () => ({ data: [OPTIONS[1]], totalCount: 0 }));
+ const loadOptions = jest.fn(async () => ({ data: [], totalCount: 0 }));
render(
,
);
diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx
index 00681341385f..0d64e634e768 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.tsx
@@ -298,7 +298,7 @@ const AsyncSelect = (
filterOption = true,
header = null,
invertSelection = false,
- labelInValue = false,
+ labelInValue = true,
lazyLoading = true,
loading,
mode = 'single',
diff --git a/superset-frontend/src/components/Select/Select.tsx b/superset-frontend/src/components/Select/Select.tsx
index 2c76176864bc..46e37cfdbb0d 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -513,12 +513,11 @@ const Select = (
}
};
- // useEffect(() => {
- // // when `options` list is updated from component prop, reset states
- // fetchedQueries.current.clear();
- // setAllValuesLoaded(false);
- // setSelectOptions(initialOptions);
- // }, [initialOptions]);
+ useEffect(() => {
+ // when `options` list is updated from component prop, reset states
+ fetchedQueries.current.clear();
+ setSelectOptions(initialOptions);
+ }, [initialOptions]);
useEffect(() => {
setSelectValue(value);
From a97fca5850883f7d6c787321d108b7cb990d4b62 Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Tue, 12 Jul 2022 16:24:26 -0400
Subject: [PATCH 13/20] cleaned up async test
---
superset-frontend/src/components/Select/AsyncSelect.test.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
index 8e4eea428da1..a35fe3bb79db 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
@@ -341,15 +341,17 @@ test('ignores special keys when searching', async () => {
test('searches for custom fields', async () => {
render(
- ,
+ ,
);
await open();
await type('Liam');
+ // Liam is on the second page. need to wait to fetch options
expect(await findSelectOption('Liam')).toBeInTheDocument();
let options = await findAllSelectOptions();
expect(options.length).toBe(1);
expect(options[0]).toHaveTextContent('Liam');
await type('Female');
+ // Olivia is on the second page. need to wait to fetch options
expect(await findSelectOption('Olivia')).toBeInTheDocument();
options = await findAllSelectOptions();
expect(options.length).toBe(6);
From a8bc98f419eaeb935a5e8e1a782607b5dadb16f6 Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Tue, 12 Jul 2022 16:51:58 -0400
Subject: [PATCH 14/20] fixed lint errors
---
.../components/Select/AsyncSelect.test.tsx | 101 +++++++++---------
.../src/components/Select/AsyncSelect.tsx | 12 +--
.../src/components/Select/Select.test.tsx | 4 +-
.../src/components/Select/Select.tsx | 34 +-----
4 files changed, 58 insertions(+), 93 deletions(-)
diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
index a35fe3bb79db..8a50002c866f 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
@@ -62,18 +62,14 @@ const loadOptions = async (search: string, page: number, pageSize: number) => {
start + pageSize < totalCount ? pageSize : totalCount - start;
const searchValue = search.trim().toLowerCase();
const optionFilterProps = ['label', 'value', 'gender'];
-
- const data = OPTIONS.filter(option => {
- return optionFilterProps.some(prop => {
+ const data = OPTIONS.filter(option =>
+ optionFilterProps.some(prop => {
const optionProp = option?.[prop]
? String(option[prop]).trim().toLowerCase()
: '';
return optionProp.includes(searchValue);
- });
- }).splice(
- start,
- deleteCount,
- );
+ }),
+ ).splice(start, deleteCount);
return {
data,
totalCount: OPTIONS.length,
@@ -154,7 +150,10 @@ test('adds a new option if the value is not in the options, when options are emp
});
test('adds a new option if the value is not in the options, when options have values', async () => {
- const loadOptions = jest.fn(async () => ({ data: [OPTIONS[1]], totalCount: 1}));
+ const loadOptions = jest.fn(async () => ({
+ data: [OPTIONS[1]],
+ totalCount: 1,
+ }));
render(
,
);
@@ -169,7 +168,10 @@ test('adds a new option if the value is not in the options, when options have va
});
test('does not add a new option if the value is already in the options', async () => {
- const loadOptions = jest.fn(async () => ({ data: [OPTIONS[0]], totalCount: 1}));
+ const loadOptions = jest.fn(async () => ({
+ data: [OPTIONS[0]],
+ totalCount: 1,
+ }));
render(
,
);
@@ -179,7 +181,6 @@ test('does not add a new option if the value is already in the options', async (
expect(options).toHaveLength(1);
});
-
test('inverts the selection', async () => {
render();
await open();
@@ -188,7 +189,10 @@ test('inverts the selection', async () => {
});
test('sort the options by label if no sort comparator is provided', async () => {
- const loadUnsortedOptions = jest.fn(async () => ({ data: [...OPTIONS].sort(() => Math.random()), totalCount: 2 }));
+ const loadUnsortedOptions = jest.fn(async () => ({
+ data: [...OPTIONS].sort(() => Math.random()),
+ totalCount: 2,
+ }));
render();
await open();
const options = await findAllSelectOptions();
@@ -309,21 +313,16 @@ test('ignores case when searching', async () => {
});
test('same case should be ranked to the top', async () => {
- const loadOptions = jest.fn(async () => (
- { data: [
- { value: 'Cac' },
- { value: 'abac' },
- { value: 'acbc' },
- { value: 'CAc' },
- ], totalCount: 4
- }
- ));
- render(
- ,
- );
+ const loadOptions = jest.fn(async () => ({
+ data: [
+ { value: 'Cac' },
+ { value: 'abac' },
+ { value: 'acbc' },
+ { value: 'CAc' },
+ ],
+ totalCount: 4,
+ }));
+ render();
await type('Ac');
const options = await findAllSelectOptions();
expect(options.length).toBe(4);
@@ -377,14 +376,14 @@ test('removes duplicated values', async () => {
});
test('renders a custom label', async () => {
- const loadOptions = jest.fn(async () => (
- { data: [
- { label: 'John', value: 1, customLabel: John
},
- { label: 'Liam', value: 2, customLabel: Liam
},
- { label: 'Olivia', value: 3, customLabel: Olivia
},
- ], totalCount: 3
- }
- ));
+ const loadOptions = jest.fn(async () => ({
+ data: [
+ { label: 'John', value: 1, customLabel: John
},
+ { label: 'Liam', value: 2, customLabel: Liam
},
+ { label: 'Olivia', value: 3, customLabel: Olivia
},
+ ],
+ totalCount: 3,
+ }));
render();
await open();
expect(screen.getByRole('heading', { name: 'John' })).toBeInTheDocument();
@@ -393,14 +392,14 @@ test('renders a custom label', async () => {
});
test('searches for a word with a custom label', async () => {
- const loadOptions = jest.fn(async () => (
- { data: [
- { label: 'John', value: 1, customLabel: John
},
- { label: 'Liam', value: 2, customLabel: Liam
},
- { label: 'Olivia', value: 3, customLabel: Olivia
},
- ], totalCount: 3
- }
- ));
+ const loadOptions = jest.fn(async () => ({
+ data: [
+ { label: 'John', value: 1, customLabel: John
},
+ { label: 'Liam', value: 2, customLabel: Liam
},
+ { label: 'Olivia', value: 3, customLabel: Olivia
},
+ ],
+ totalCount: 3,
+ }));
render();
await type('Liam');
const selectOptions = await findAllSelectOptions();
@@ -442,7 +441,10 @@ test('does not add a new option if allowNewOptions is false', async () => {
});
test('adds the null option when selected in single mode', async () => {
- const loadOptions = jest.fn(async () => ({ data: [OPTIONS[0], NULL_OPTION], totalCount: 2 }));
+ const loadOptions = jest.fn(async () => ({
+ data: [OPTIONS[0], NULL_OPTION],
+ totalCount: 2,
+ }));
render();
await open();
userEvent.click(await findSelectOption(NULL_OPTION.label));
@@ -451,13 +453,12 @@ test('adds the null option when selected in single mode', async () => {
});
test('adds the null option when selected in multiple mode', async () => {
- const loadOptions = jest.fn(async () => ({ data: [OPTIONS[0], NULL_OPTION], totalCount: 2 }));
+ const loadOptions = jest.fn(async () => ({
+ data: [OPTIONS[0], NULL_OPTION],
+ totalCount: 2,
+ }));
render(
- ,
+ ,
);
await open();
userEvent.click(await findSelectOption(OPTIONS[0].label));
diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx
index 0d64e634e768..802102fead2e 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.tsx
@@ -589,7 +589,9 @@ const AsyncSelect = (
// if no search input value, force sort options because it won't be sorted by
// `filterSort`.
if (isDropdownVisible && !inputValue && selectOptions.length > 1) {
- const sortedOptions = selectOptions.slice().sort(sortComparatorForNoSearch);
+ const sortedOptions = selectOptions
+ .slice()
+ .sort(sortComparatorForNoSearch);
if (!isEqual(sortedOptions, selectOptions)) {
setSelectOptions(sortedOptions);
}
@@ -659,13 +661,7 @@ const AsyncSelect = (
fetchPage('', 0);
}
}
- }, [
- loadingEnabled,
- fetchPage,
- allowFetch,
- inputValue,
- debouncedFetchPage,
- ]);
+ }, [loadingEnabled, fetchPage, allowFetch, inputValue, debouncedFetchPage]);
useEffect(() => {
if (loading !== undefined && loading !== isLoading) {
diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx
index b6c101efb1e5..18111f30ca19 100644
--- a/superset-frontend/src/components/Select/Select.test.tsx
+++ b/superset-frontend/src/components/Select/Select.test.tsx
@@ -115,9 +115,7 @@ test('displays a header', async () => {
});
test('adds a new option if the value is not in the options, when options are empty', async () => {
- render(
- ,
- );
+ render();
await open();
expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();
const options = await findAllSelectOptions();
diff --git a/superset-frontend/src/components/Select/Select.tsx b/superset-frontend/src/components/Select/Select.tsx
index 46e37cfdbb0d..d8e3d2b4ce22 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -21,7 +21,6 @@ import React, {
ReactElement,
ReactNode,
RefObject,
- UIEvent,
useEffect,
useMemo,
useState,
@@ -37,11 +36,9 @@ import AntdSelect, {
} from 'antd/lib/select';
import { DownOutlined, SearchOutlined } from '@ant-design/icons';
import { Spin } from 'antd';
-import debounce from 'lodash/debounce';
import { isEqual } from 'lodash';
import Icons from 'src/components/Icons';
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
-import { SLOW_DEBOUNCE } from 'src/constants';
import { rankedSearchCompare } from 'src/utils/rankedSearchCompare';
import { getValue, hasOption, isLabeledValue } from './utils';
@@ -97,13 +94,6 @@ export interface SelectProps extends PickedSelectProps {
* Can be any ReactNode.
*/
header?: ReactNode;
- /**
- * It fires a request against the server after
- * the first interaction and not on render.
- * Works in async mode only (See the options property).
- * True by default.
- */
- lazyLoading?: boolean;
/**
* It defines whether the Select should allow for the
* selection of multiple options or single.
@@ -128,12 +118,6 @@ export interface SelectProps extends PickedSelectProps {
* an array of options.
*/
options: OptionsType;
- /**
- * It defines how many results should be included
- * in the query response.
- * Works in async mode only (See the options property).
- */
- pageSize?: number;
/**
* It shows a stop-outlined icon at the far right of a selected
* option instead of the default checkmark.
@@ -142,13 +126,6 @@ export interface SelectProps extends PickedSelectProps {
* False by default.
*/
invertSelection?: boolean;
- /**
- * It fires a request against the server only after
- * searching.
- * Works in async mode only (See the options property).
- * Undefined by default.
- */
- fetchOnlyOnSearch?: boolean;
/**
* It provides a callback function when an error
* is generated after a request is fired.
@@ -222,7 +199,6 @@ const StyledLoadingText = styled.div`
const MAX_TAG_COUNT = 4;
const TOKEN_SEPARATORS = [',', '\n', '\t', ';'];
-const DEFAULT_PAGE_SIZE = 100;
const EMPTY_OPTIONS: OptionsType = [];
const Error = ({ error }: { error: string }) => (
@@ -267,9 +243,6 @@ export const propertyComparator =
return (a[property] as number) - (b[property] as number);
};
-const getQueryCacheKey = (value: string, page: number, pageSize: number) =>
- `${value};${page};${pageSize}`;
-
/**
* This component is a customized version of the Antdesign 4.X Select component
* https://ant.design/components/select/.
@@ -289,12 +262,10 @@ const Select = (
allowClear,
allowNewOptions = false,
ariaLabel,
- fetchOnlyOnSearch,
filterOption = true,
header = null,
invertSelection = false,
labelInValue = false,
- lazyLoading = true,
loading,
mode = 'single',
name,
@@ -305,7 +276,6 @@ const Select = (
onDropdownVisibleChange,
optionFilterProps = ['label', 'value'],
options,
- pageSize = DEFAULT_PAGE_SIZE,
placeholder = t('Select ...'),
showSearch = true,
sortComparator = DEFAULT_SORT_COMPARATOR,
@@ -345,7 +315,7 @@ const Select = (
);
const sortComparatorForNoSearch = useCallback(
(a: AntdLabeledValue, b: AntdLabeledValue) =>
- sortSelectedFirst(a, b) ||
+ sortSelectedFirst(a, b) ||
// we should preserve the options order as much as possible.
0,
[sortComparator, sortSelectedFirst],
@@ -426,7 +396,7 @@ const Select = (
}),
[onError],
);
-
+
const handleOnSearch = (search: string) => {
const searchValue = search.trim();
if (allowNewOptions && isSingleMode) {
From 0dc06dd753ae016c61d78f48c28c0c13228734f7 Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Thu, 14 Jul 2022 15:52:43 -0400
Subject: [PATCH 15/20] minor fixes to sync select component
---
.../src/components/Select/Select.tsx | 85 ++-----------------
1 file changed, 5 insertions(+), 80 deletions(-)
diff --git a/superset-frontend/src/components/Select/Select.tsx b/superset-frontend/src/components/Select/Select.tsx
index d8e3d2b4ce22..bec082772d83 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -24,9 +24,7 @@ import React, {
useEffect,
useMemo,
useState,
- useRef,
useCallback,
- useImperativeHandle,
} from 'react';
import { ensureIsArray, styled, t } from '@superset-ui/core';
import AntdSelect, {
@@ -38,7 +36,6 @@ import { DownOutlined, SearchOutlined } from '@ant-design/icons';
import { Spin } from 'antd';
import { isEqual } from 'lodash';
import Icons from 'src/components/Icons';
-import { getClientErrorObject } from 'src/utils/getClientErrorObject';
import { rankedSearchCompare } from 'src/utils/rankedSearchCompare';
import { getValue, hasOption, isLabeledValue } from './utils';
@@ -69,13 +66,6 @@ type PickedSelectProps = Pick<
export type OptionsType = Exclude;
-export type OptionsTypePage = {
- data: OptionsType;
- totalCount: number;
-};
-
-export type SelectRef = HTMLInputElement & { clearCache: () => void };
-
export interface SelectProps extends PickedSelectProps {
/**
* It enables the user to create new options.
@@ -126,12 +116,6 @@ export interface SelectProps extends PickedSelectProps {
* False by default.
*/
invertSelection?: boolean;
- /**
- * It provides a callback function when an error
- * is generated after a request is fired.
- * 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.
@@ -166,25 +150,6 @@ const StyledCheckOutlined = styled(Icons.CheckOutlined)`
vertical-align: 0;
`;
-const StyledError = styled.div`
- ${({ theme }) => `
- display: flex;
- justify-content: center;
- align-items: flex-start;
- width: 100%;
- padding: ${theme.gridUnit * 2}px;
- color: ${theme.colors.error.base};
- & svg {
- margin-right: ${theme.gridUnit * 2}px;
- }
- `}
-`;
-
-const StyledErrorMessage = styled.div`
- overflow: hidden;
- text-overflow: ellipsis;
-`;
-
const StyledSpin = styled(Spin)`
margin-top: ${({ theme }) => -theme.gridUnit}px;
`;
@@ -201,12 +166,6 @@ const MAX_TAG_COUNT = 4;
const TOKEN_SEPARATORS = [',', '\n', '\t', ';'];
const EMPTY_OPTIONS: OptionsType = [];
-const Error = ({ error }: { error: string }) => (
-
- {error}
-
-);
-
export const DEFAULT_SORT_COMPARATOR = (
a: AntdLabeledValue,
b: AntdLabeledValue,
@@ -270,7 +229,6 @@ const Select = (
mode = 'single',
name,
notFoundContent,
- onError,
onChange,
onClear,
onDropdownVisibleChange,
@@ -284,16 +242,14 @@ const Select = (
getPopupContainer,
...props
}: SelectProps,
- ref: RefObject,
+ ref: RefObject,
) => {
const isSingleMode = mode === 'single';
const shouldShowSearch = allowNewOptions ? true : showSearch;
const [selectValue, setSelectValue] = useState(value);
const [inputValue, setInputValue] = useState('');
const [isLoading, setIsLoading] = useState(loading);
- const [error, setError] = useState('');
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
- const fetchedQueries = useRef(new Map());
const mappedMode = isSingleMode
? undefined
: allowNewOptions
@@ -313,21 +269,14 @@ const Select = (
sortSelectedFirst(a, b) || sortComparator(a, b, inputValue),
[inputValue, sortComparator, sortSelectedFirst],
);
- const sortComparatorForNoSearch = useCallback(
- (a: AntdLabeledValue, b: AntdLabeledValue) =>
- sortSelectedFirst(a, b) ||
- // we should preserve the options order as much as possible.
- 0,
- [sortComparator, sortSelectedFirst],
- );
const initialOptions = useMemo(
() => (options && Array.isArray(options) ? options.slice() : EMPTY_OPTIONS),
[options],
);
const initialOptionsSorted = useMemo(
- () => initialOptions.slice().sort(sortComparatorForNoSearch),
- [initialOptions, sortComparatorForNoSearch],
+ () => initialOptions.slice().sort(sortSelectedFirst),
+ [initialOptions, sortSelectedFirst],
);
const [selectOptions, setSelectOptions] =
@@ -384,19 +333,6 @@ const Select = (
setInputValue('');
};
- const internalOnError = useCallback(
- (response: Response) =>
- getClientErrorObject(response).then(e => {
- const { error } = e;
- setError(error);
-
- if (onError) {
- onError(error);
- }
- }),
- [onError],
- );
-
const handleOnSearch = (search: string) => {
const searchValue = search.trim();
if (allowNewOptions && isSingleMode) {
@@ -461,7 +397,7 @@ const Select = (
if (isLoading && fullSelectOptions.length === 0) {
return {t('Loading...')};
}
- return error ? : originNode;
+ return originNode;
};
// use a function instead of component since every rerender of the
@@ -485,7 +421,6 @@ const Select = (
useEffect(() => {
// when `options` list is updated from component prop, reset states
- fetchedQueries.current.clear();
setSelectOptions(initialOptions);
}, [initialOptions]);
@@ -498,17 +433,7 @@ const Select = (
setIsLoading(loading);
}
}, [isLoading, loading]);
-
- const clearCache = () => fetchedQueries.current.clear();
-
- useImperativeHandle(
- ref,
- () => ({
- ...(ref.current as HTMLInputElement),
- clearCache,
- }),
- [ref],
- );
+
return (
From 92a45945411879e193df0b5824a194e1e01f4d8a Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Tue, 19 Jul 2022 14:33:03 -0400
Subject: [PATCH 16/20] removed unecessary variables and fixed linting
---
.../src/components/Select/AsyncSelect.tsx | 11 ++++-------
superset-frontend/src/components/Select/Select.tsx | 1 -
2 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx
index 802102fead2e..ff96fd9d60d9 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.tsx
@@ -55,7 +55,6 @@ type PickedSelectProps = Pick<
| 'autoFocus'
| 'disabled'
| 'filterOption'
- | 'labelInValue'
| 'loading'
| 'notFoundContent'
| 'onChange'
@@ -298,7 +297,6 @@ const AsyncSelect = (
filterOption = true,
header = null,
invertSelection = false,
- labelInValue = true,
lazyLoading = true,
loading,
mode = 'single',
@@ -322,7 +320,6 @@ const AsyncSelect = (
ref: RefObject,
) => {
const isSingleMode = mode === 'single';
- const shouldShowSearch = allowNewOptions ? true : showSearch;
const [selectValue, setSelectValue] = useState(value);
const [inputValue, setInputValue] = useState('');
const [isLoading, setIsLoading] = useState(loading);
@@ -620,7 +617,7 @@ const AsyncSelect = (
if (isLoading) {
return ;
}
- if (shouldShowSearch && isDropdownVisible) {
+ if (showSearch && isDropdownVisible) {
return ;
}
return ;
@@ -692,20 +689,20 @@ const AsyncSelect = (
getPopupContainer={
getPopupContainer || (triggerNode => triggerNode.parentNode)
}
- labelInValue={labelInValue}
+ labelInValue
maxTagCount={MAX_TAG_COUNT}
mode={mappedMode}
notFoundContent={isLoading ? t('Loading...') : notFoundContent}
onDeselect={handleOnDeselect}
onDropdownVisibleChange={handleOnDropdownVisibleChange}
onPopupScroll={handlePagination}
- onSearch={shouldShowSearch ? handleOnSearch : undefined}
+ onSearch={showSearch ? handleOnSearch : undefined}
onSelect={handleOnSelect}
onClear={handleClear}
onChange={onChange}
options={hasCustomLabels ? undefined : fullSelectOptions}
placeholder={placeholder}
- showSearch={shouldShowSearch}
+ showSearch
showArrow
tokenSeparators={tokenSeparators || TOKEN_SEPARATORS}
value={selectValue}
diff --git a/superset-frontend/src/components/Select/Select.tsx b/superset-frontend/src/components/Select/Select.tsx
index bec082772d83..04eccec83ad1 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -433,7 +433,6 @@ const Select = (
setIsLoading(loading);
}
}, [isLoading, loading]);
-
return (
From 0dcd36c4ae377896a0c1947d7937f075ddb943ef Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Wed, 20 Jul 2022 11:15:16 -0400
Subject: [PATCH 17/20] fixed npm test errors
---
.../src/components/DatabaseSelector/index.tsx | 1 -
.../components/ListView/Filters/Select.tsx | 45 +++++++++++++------
.../src/components/Select/Select.stories.tsx | 4 +-
.../src/components/TableSelector/index.tsx | 1 -
.../src/filters/components/GroupBy/types.ts | 3 +-
.../filters/components/TimeColumn/types.ts | 3 +-
.../src/filters/components/TimeGrain/types.ts | 3 +-
7 files changed, 37 insertions(+), 23 deletions(-)
diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx
index e972e95b002f..1df7f78a3bea 100644
--- a/superset-frontend/src/components/DatabaseSelector/index.tsx
+++ b/superset-frontend/src/components/DatabaseSelector/index.tsx
@@ -302,7 +302,6 @@ export default function DatabaseSelector({
disabled={!currentDb || readOnly}
header={{t('Schema')}}
labelInValue
- lazyLoading={false}
loading={loadingSchemas}
name="select-schema"
placeholder={t('Select schema or type schema name')}
diff --git a/superset-frontend/src/components/ListView/Filters/Select.tsx b/superset-frontend/src/components/ListView/Filters/Select.tsx
index 525061fd2741..c5b0ade77f62 100644
--- a/superset-frontend/src/components/ListView/Filters/Select.tsx
+++ b/superset-frontend/src/components/ListView/Filters/Select.tsx
@@ -27,6 +27,7 @@ import { Select } from 'src/components';
import { Filter, SelectOption } from 'src/components/ListView/types';
import { FormLabel } from 'src/components/Form';
import { FilterContainer, BaseFilter, FilterHandler } from './Base';
+import AsyncSelect from 'src/components/Select/AsyncSelect';
interface SelectFilterProps extends BaseFilter {
fetchSelects?: Filter['fetchSelects'];
@@ -86,19 +87,37 @@ function SelectFilter(
return (
- {Header}}
- onChange={onChange}
- onClear={onClear}
- options={fetchSelects ? fetchAndFormatSelects : selects}
- placeholder={t('Select or type a value')}
- showSearch
- value={selectedOption}
- />
+ {
+ fetchSelects ? (
+ {Header}}
+ onChange={onChange}
+ onClear={onClear}
+ options={fetchAndFormatSelects}
+ placeholder={t('Select or type a value')}
+ showSearch
+ value={selectedOption}
+ />
+ ) : (
+ {Header}}
+ labelInValue
+ onChange={onChange}
+ onClear={onClear}
+ options={selects}
+ placeholder={t('Select or type a value')}
+ showSearch
+ value={selectedOption}
+ />
+ )
+ }
+
);
}
diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx
index efcd91c0c38f..4281ebe30198 100644
--- a/superset-frontend/src/components/Select/Select.stories.tsx
+++ b/superset-frontend/src/components/Select/Select.stories.tsx
@@ -19,8 +19,8 @@
import React, { ReactNode, useState, useCallback, useRef } from 'react';
import Button from 'src/components/Button';
import ControlHeader from 'src/explore/components/ControlHeader';
-import AsyncSelect, { AsyncSelectProps, AsyncSelectRef } from './AsyncSelect';
-import Select, { SelectProps, OptionsTypePage, OptionsType } from './Select';
+import AsyncSelect, { AsyncSelectProps, AsyncSelectRef, OptionsTypePage } from './AsyncSelect';
+import Select, { SelectProps, OptionsType } from './Select';
export default {
title: 'Select',
diff --git a/superset-frontend/src/components/TableSelector/index.tsx b/superset-frontend/src/components/TableSelector/index.tsx
index d7b867347abf..4c07e256cc2e 100644
--- a/superset-frontend/src/components/TableSelector/index.tsx
+++ b/superset-frontend/src/components/TableSelector/index.tsx
@@ -336,7 +336,6 @@ const TableSelector: FunctionComponent = ({
filterOption={handleFilterOption}
header={header}
labelInValue
- lazyLoading={false}
loading={loadingTables}
name="select-table"
onChange={(options: TableOption | TableOption[]) =>
diff --git a/superset-frontend/src/filters/components/GroupBy/types.ts b/superset-frontend/src/filters/components/GroupBy/types.ts
index 1e6d75649778..5775edaa2f33 100644
--- a/superset-frontend/src/filters/components/GroupBy/types.ts
+++ b/superset-frontend/src/filters/components/GroupBy/types.ts
@@ -23,7 +23,6 @@ import {
QueryFormData,
} from '@superset-ui/core';
import { RefObject } from 'react';
-import { SelectRef } from 'src/components/Select/Select';
import { PluginFilterHooks, PluginFilterStylesProps } from '../types';
interface PluginFilterGroupByCustomizeProps {
@@ -41,7 +40,7 @@ export type PluginFilterGroupByProps = PluginFilterStylesProps & {
data: DataRecord[];
filterState: FilterState;
formData: PluginFilterGroupByQueryFormData;
- inputRef: RefObject;
+ inputRef: RefObject;
} & PluginFilterHooks;
export const DEFAULT_FORM_DATA: PluginFilterGroupByCustomizeProps = {
diff --git a/superset-frontend/src/filters/components/TimeColumn/types.ts b/superset-frontend/src/filters/components/TimeColumn/types.ts
index bc12cb87169c..95b1e5edfdc9 100644
--- a/superset-frontend/src/filters/components/TimeColumn/types.ts
+++ b/superset-frontend/src/filters/components/TimeColumn/types.ts
@@ -23,7 +23,6 @@ import {
QueryFormData,
} from '@superset-ui/core';
import { RefObject } from 'react';
-import { SelectRef } from 'src/components/Select/Select';
import { PluginFilterHooks, PluginFilterStylesProps } from '../types';
interface PluginFilterTimeColumnCustomizeProps {
@@ -40,7 +39,7 @@ export type PluginFilterTimeColumnProps = PluginFilterStylesProps & {
data: DataRecord[];
filterState: FilterState;
formData: PluginFilterTimeColumnQueryFormData;
- inputRef: RefObject;
+ inputRef: RefObject;
} & PluginFilterHooks;
export const DEFAULT_FORM_DATA: PluginFilterTimeColumnCustomizeProps = {
diff --git a/superset-frontend/src/filters/components/TimeGrain/types.ts b/superset-frontend/src/filters/components/TimeGrain/types.ts
index 64a716657357..3dfa9a99d8dc 100644
--- a/superset-frontend/src/filters/components/TimeGrain/types.ts
+++ b/superset-frontend/src/filters/components/TimeGrain/types.ts
@@ -18,7 +18,6 @@
*/
import { FilterState, QueryFormData, DataRecord } from '@superset-ui/core';
import { RefObject } from 'react';
-import { SelectRef } from 'src/components/Select/Select';
import { PluginFilterHooks, PluginFilterStylesProps } from '../types';
interface PluginFilterTimeGrainCustomizeProps {
@@ -34,7 +33,7 @@ export type PluginFilterTimeGrainProps = PluginFilterStylesProps & {
data: DataRecord[];
filterState: FilterState;
formData: PluginFilterTimeGrainQueryFormData;
- inputRef: RefObject;
+ inputRef: RefObject;
} & PluginFilterHooks;
export const DEFAULT_FORM_DATA: PluginFilterTimeGrainCustomizeProps = {
From bf5176a698cf15698670a2d4bce71a3a2364161d Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Wed, 20 Jul 2022 15:27:33 -0400
Subject: [PATCH 18/20] fixed linting issues
---
.../components/ListView/Filters/Select.tsx | 37 +++++++++----------
.../src/components/Select/Select.stories.tsx | 7 +++-
2 files changed, 23 insertions(+), 21 deletions(-)
diff --git a/superset-frontend/src/components/ListView/Filters/Select.tsx b/superset-frontend/src/components/ListView/Filters/Select.tsx
index c5b0ade77f62..ecda25a81ff6 100644
--- a/superset-frontend/src/components/ListView/Filters/Select.tsx
+++ b/superset-frontend/src/components/ListView/Filters/Select.tsx
@@ -26,8 +26,8 @@ import { t } from '@superset-ui/core';
import { Select } from 'src/components';
import { Filter, SelectOption } from 'src/components/ListView/types';
import { FormLabel } from 'src/components/Form';
-import { FilterContainer, BaseFilter, FilterHandler } from './Base';
import AsyncSelect from 'src/components/Select/AsyncSelect';
+import { FilterContainer, BaseFilter, FilterHandler } from './Base';
interface SelectFilterProps extends BaseFilter {
fetchSelects?: Filter['fetchSelects'];
@@ -87,8 +87,7 @@ function SelectFilter(
return (
- {
- fetchSelects ? (
+ {fetchSelects ? (
- ) : (
- {Header}}
- labelInValue
- onChange={onChange}
- onClear={onClear}
- options={selects}
- placeholder={t('Select or type a value')}
- showSearch
- value={selectedOption}
- />
- )
- }
-
+ ) : (
+ {Header}}
+ labelInValue
+ onChange={onChange}
+ onClear={onClear}
+ options={selects}
+ placeholder={t('Select or type a value')}
+ showSearch
+ value={selectedOption}
+ />
+ )}
);
}
diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx
index 4281ebe30198..5b127fb46210 100644
--- a/superset-frontend/src/components/Select/Select.stories.tsx
+++ b/superset-frontend/src/components/Select/Select.stories.tsx
@@ -19,7 +19,12 @@
import React, { ReactNode, useState, useCallback, useRef } from 'react';
import Button from 'src/components/Button';
import ControlHeader from 'src/explore/components/ControlHeader';
-import AsyncSelect, { AsyncSelectProps, AsyncSelectRef, OptionsTypePage } from './AsyncSelect';
+import AsyncSelect, {
+ AsyncSelectProps,
+ AsyncSelectRef,
+ OptionsTypePage,
+} from './AsyncSelect';
+
import Select, { SelectProps, OptionsType } from './Select';
export default {
From 78500f758107cd9df069e5d2d7d53fa9ed751fe3 Mon Sep 17 00:00:00 2001
From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Date: Wed, 27 Jul 2022 10:02:08 -0400
Subject: [PATCH 19/20] fixed showSearch and storybook
---
.../src/components/Select/AsyncSelect.tsx | 2 +-
.../src/components/Select/Select.stories.tsx | 12 ++++++------
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx
index ff96fd9d60d9..b95f2d8f0d1f 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.tsx
@@ -702,7 +702,7 @@ const AsyncSelect = (
onChange={onChange}
options={hasCustomLabels ? undefined : fullSelectOptions}
placeholder={placeholder}
- showSearch
+ showSearch={showSearch}
showArrow
tokenSeparators={tokenSeparators || TOKEN_SEPARATORS}
value={selectValue}
diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx
index 5b127fb46210..fedb7dc93950 100644
--- a/superset-frontend/src/components/Select/Select.stories.tsx
+++ b/superset-frontend/src/components/Select/Select.stories.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { ReactNode, useState, useCallback, useRef } from 'react';
+import React, { ReactNode, useState, useCallback, useRef, useMemo } from 'react';
import Button from 'src/components/Button';
import ControlHeader from 'src/explore/components/ControlHeader';
import AsyncSelect, {
@@ -457,6 +457,10 @@ export const AsynchronousSelect = ({
reject(new Error('Error while fetching the names from the server'));
});
+ const initialValue = useMemo(
+ () => ({ label: 'Valentina', value: 'Valentina' }),
+ [],
+ );
return (
<>
Date: Wed, 27 Jul 2022 10:03:38 -0400
Subject: [PATCH 20/20] fixed linting
---
.../src/components/Select/Select.stories.tsx | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx
index fedb7dc93950..b75e1ff28bd0 100644
--- a/superset-frontend/src/components/Select/Select.stories.tsx
+++ b/superset-frontend/src/components/Select/Select.stories.tsx
@@ -16,7 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { ReactNode, useState, useCallback, useRef, useMemo } from 'react';
+import React, {
+ ReactNode,
+ useState,
+ useCallback,
+ useRef,
+ useMemo,
+} from 'react';
import Button from 'src/components/Button';
import ControlHeader from 'src/explore/components/ControlHeader';
import AsyncSelect, {
@@ -460,7 +466,8 @@ export const AsynchronousSelect = ({
const initialValue = useMemo(
() => ({ label: 'Valentina', value: 'Valentina' }),
[],
- );
+ );
+
return (
<>