This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 8a57a71bed fix(sql lab): Save Dataset Modal Autocomplete should 
display list when overwritting (#20512)
8a57a71bed is described below

commit 8a57a71bed30a781a1d5e5b2ce42ccd08045b3e9
Author: Antonio Rivero Martinez 
<38889534+antonio-riveromart...@users.noreply.github.com>
AuthorDate: Fri Jul 1 17:40:13 2022 -0300

    fix(sql lab): Save Dataset Modal Autocomplete should display list when 
overwritting (#20512)
    
    * Save Dataset Modal:
    
    - Use our Select component as substitute of the Autocomplete one so options 
are loaded initially without the user having to trigger a search and we are 
mosre consistent with the rest of the app
    - Changing datasetId to lowercase so when custom props get into the DOM we 
don't get warning related to invalid formatting
    - We extracted the dropdown out of the radio because it causes invalid 
click handling when an option is selected
    - Updated tests
    
    * Save Dataset Modal:
    
    - Update missing test for DatasourceControl
    
    * Save Dataset Modal:
    
    - Remove conditional from load options function since only guest users dont 
have userId, and if that is the case they wont reach this part of the 
application
    
    * Save Dataset Modal:
    
    - Remove unused comment
    
    * Save Dataset Modal:
    
    - Add getPopupContainer as prop for Select component
    
    * Save Dataset Modal:
    
    - Add tests for our new getPopupContainer prop in Select component. So if 
passed it gets called.
    
    * Save Dataset Modal:
    
    - use lowercased property when calling post form data
    
    * Save Dataset Modal:
    
    - Update tests so there is no need to define a null returning func
    
    * Save Dataset Modal:
    
    - Including getPopupContainer from PickedSelectProps instead
    - Updating definition in SelectFilterPlugin
---
 .../SaveDatasetModal/SaveDatasetModal.test.tsx     |   2 +-
 .../SqlLab/components/SaveDatasetModal/index.tsx   | 121 ++++++++++-----------
 .../src/components/Select/Select.test.tsx          |  22 ++++
 superset-frontend/src/components/Select/Select.tsx |   6 +-
 .../DatasourceControl/DatasourceControl.test.tsx   |   4 +-
 .../components/Select/SelectFilterPlugin.tsx       |   5 +-
 6 files changed, 93 insertions(+), 67 deletions(-)

diff --git 
a/superset-frontend/src/SqlLab/components/SaveDatasetModal/SaveDatasetModal.test.tsx
 
b/superset-frontend/src/SqlLab/components/SaveDatasetModal/SaveDatasetModal.test.tsx
index c35b5eb2b6..d0698e3edf 100644
--- 
a/superset-frontend/src/SqlLab/components/SaveDatasetModal/SaveDatasetModal.test.tsx
+++ 
b/superset-frontend/src/SqlLab/components/SaveDatasetModal/SaveDatasetModal.test.tsx
@@ -50,7 +50,7 @@ describe('SaveDatasetModal RTL', () => {
     render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
 
     const overwriteRadioBtn = screen.getByRole('radio', {
-      name: /overwrite existing select or type dataset name/i,
+      name: /overwrite existing/i,
     });
     const fieldLabel = screen.getByText(/overwrite existing/i);
     const inputField = screen.getByRole('combobox');
diff --git a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx 
b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx
index 4a87d52c18..4fbfe11adb 100644
--- a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx
@@ -17,9 +17,9 @@
  * under the License.
  */
 
-import React, { FunctionComponent, useState } from 'react';
+import React, { FunctionComponent, useCallback, useState } from 'react';
 import { Radio } from 'src/components/Radio';
-import { AutoComplete, RadioChangeEvent } from 'src/components';
+import { RadioChangeEvent, Select } from 'src/components';
 import { Input } from 'src/components/Input';
 import StyledModal from 'src/components/Modal';
 import Button from 'src/components/Button';
@@ -27,7 +27,6 @@ import {
   styled,
   t,
   SupersetClient,
-  makeApi,
   JsonResponse,
   JsonObject,
   QueryResponse,
@@ -42,7 +41,6 @@ import {
   DatasetRadioState,
   EXPLORE_CHART_DEFAULT,
   DatasetOwner,
-  DatasetOptionAutocomplete,
   SqlLabExploreRootState,
   getInitialState,
   ExploreDatasource,
@@ -51,6 +49,8 @@ import {
 import { mountExploreUrl } from 'src/explore/exploreUtils';
 import { postFormData } from 'src/explore/exploreUtils/formData';
 import { URL_PARAMS } from 'src/constants';
+import { SelectValue } from 'antd/lib/select';
+import { isEmpty } from 'lodash';
 
 interface SaveDatasetModalProps {
   visible: boolean;
@@ -70,8 +70,8 @@ const Styles = styled.div`
     width: 401px;
   }
   .sdm-autocomplete {
-    margin-left: 8px;
     width: 401px;
+    align-self: center;
   }
   .sdm-radio {
     display: block;
@@ -82,6 +82,10 @@ const Styles = styled.div`
   .sdm-overwrite-msg {
     margin: 7px;
   }
+  .sdm-overwrite-container {
+    flex: 1 1 auto;
+    display: flex;
+  }
 `;
 
 const updateDataset = async (
@@ -129,13 +133,12 @@ export const SaveDatasetModal: 
FunctionComponent<SaveDatasetModalProps> = ({
     DatasetRadioState.SAVE_NEW,
   );
   const [shouldOverwriteDataset, setShouldOverwriteDataset] = useState(false);
-  const [userDatasetOptions, setUserDatasetOptions] = useState<
-    DatasetOptionAutocomplete[]
-  >([]);
   const [datasetToOverwrite, setDatasetToOverwrite] = useState<
     Record<string, any>
   >({});
-  const [autocompleteValue, setAutocompleteValue] = useState('');
+  const [selectedDatasetToOverwrite, setSelectedDatasetToOverwrite] = useState<
+    SelectValue | undefined
+  >(undefined);
 
   const user = useSelector<SqlLabExploreRootState, User>(user =>
     getInitialState(user),
@@ -146,7 +149,7 @@ export const SaveDatasetModal: 
FunctionComponent<SaveDatasetModalProps> = ({
     const [, key] = await Promise.all([
       updateDataset(
         query.dbId,
-        datasetToOverwrite.datasetId,
+        datasetToOverwrite.datasetid,
         query.sql,
         query.results.selected_columns.map(
           (d: { name: string; type: string; is_dttm: boolean }) => ({
@@ -158,9 +161,9 @@ export const SaveDatasetModal: 
FunctionComponent<SaveDatasetModalProps> = ({
         datasetToOverwrite.owners.map((o: DatasetOwner) => o.id),
         true,
       ),
-      postFormData(datasetToOverwrite.datasetId, 'table', {
+      postFormData(datasetToOverwrite.datasetid, 'table', {
         ...EXPLORE_CHART_DEFAULT,
-        datasource: `${datasetToOverwrite.datasetId}__table`,
+        datasource: `${datasetToOverwrite.datasetid}__table`,
         ...(defaultVizType === 'table' && {
           all_columns: query.results.selected_columns.map(
             column => column.name,
@@ -179,17 +182,15 @@ export const SaveDatasetModal: 
FunctionComponent<SaveDatasetModalProps> = ({
     setDatasetName(getDefaultDatasetName());
   };
 
-  const getUserDatasets = async (searchText = '') => {
-    // Making sure that autocomplete input has a value before rendering the 
dropdown
-    // Transforming the userDatasetsOwned data for SaveModalComponent)
-    const { userId } = user;
-    if (userId) {
+  const loadDatasetOverwriteOptions = useCallback(
+    async (input = '') => {
+      const { userId } = user;
       const queryParams = rison.encode({
         filters: [
           {
             col: 'table_name',
             opr: 'ct',
-            value: searchText,
+            value: input,
           },
           {
             col: 'owners',
@@ -201,22 +202,22 @@ export const SaveDatasetModal: 
FunctionComponent<SaveDatasetModalProps> = ({
         order_direction: 'desc',
       });
 
-      const response = await makeApi({
-        method: 'GET',
-        endpoint: '/api/v1/dataset',
-      })(`q=${queryParams}`);
-
-      return response.result.map(
-        (r: { table_name: string; id: number; owners: [DatasetOwner] }) => ({
-          value: r.table_name,
-          datasetId: r.id,
-          owners: r.owners,
-        }),
-      );
-    }
-
-    return null;
-  };
+      return SupersetClient.get({
+        endpoint: `/api/v1/dataset?q=${queryParams}`,
+      }).then(response => ({
+        data: response.json.result.map(
+          (r: { table_name: string; id: number; owners: [DatasetOwner] }) => ({
+            value: r.table_name,
+            label: r.table_name,
+            datasetid: r.id,
+            owners: r.owners,
+          }),
+        ),
+        totalCount: response.json.count,
+      }));
+    },
+    [user],
+  );
 
   const handleSaveInDataset = () => {
     // if user wants to overwrite a dataset we need to prompt them
@@ -274,16 +275,11 @@ export const SaveDatasetModal: 
FunctionComponent<SaveDatasetModalProps> = ({
     onHide();
   };
 
-  const handleSaveDatasetModalSearch = async (searchText: string) => {
-    const userDatasetsOwned = await getUserDatasets(searchText);
-    setUserDatasetOptions(userDatasetsOwned);
+  const handleOverwriteDatasetOption = (value: SelectValue, option: any) => {
+    setDatasetToOverwrite(option);
+    setSelectedDatasetToOverwrite(value);
   };
 
-  const handleOverwriteDatasetOption = (
-    _data: string,
-    option: Record<string, any>,
-  ) => setDatasetToOverwrite(option);
-
   const handleDatasetNameChange = (e: React.FormEvent<HTMLInputElement>) => {
     // @ts-expect-error
     setDatasetName(e.target.value);
@@ -298,12 +294,11 @@ export const SaveDatasetModal: 
FunctionComponent<SaveDatasetModalProps> = ({
     (newOrOverwrite === DatasetRadioState.SAVE_NEW &&
       datasetName.length === 0) ||
     (newOrOverwrite === DatasetRadioState.OVERWRITE_DATASET &&
-      Object.keys(datasetToOverwrite).length === 0 &&
-      autocompleteValue.length === 0);
+      isEmpty(selectedDatasetToOverwrite));
 
   const filterAutocompleteOption = (
     inputValue: string,
-    option: { value: string; datasetId: number },
+    option: { value: string; datasetid: number },
   ) => option.value.toLowerCase().includes(inputValue.toLowerCase());
 
   return (
@@ -359,23 +354,25 @@ export const SaveDatasetModal: 
FunctionComponent<SaveDatasetModalProps> = ({
                   disabled={newOrOverwrite !== 1}
                 />
               </Radio>
-              <Radio className="sdm-radio" value={2}>
-                {t('Overwrite existing')}
-                <AutoComplete
-                  className="sdm-autocomplete"
-                  options={userDatasetOptions}
-                  onSelect={handleOverwriteDatasetOption}
-                  onSearch={handleSaveDatasetModalSearch}
-                  onChange={value => {
-                    setDatasetToOverwrite({});
-                    setAutocompleteValue(value);
-                  }}
-                  placeholder={t('Select or type dataset name')}
-                  filterOption={filterAutocompleteOption}
-                  disabled={newOrOverwrite !== 2}
-                  value={autocompleteValue}
-                />
-              </Radio>
+              <div className="sdm-overwrite-container">
+                <Radio className="sdm-radio" value={2}>
+                  {t('Overwrite existing')}
+                </Radio>
+                <div className="sdm-autocomplete">
+                  <Select
+                    allowClear
+                    showSearch
+                    placeholder={t('Select or type dataset name')}
+                    ariaLabel={t('Existing dataset')}
+                    onChange={handleOverwriteDatasetOption}
+                    options={input => loadDatasetOverwriteOptions(input)}
+                    value={selectedDatasetToOverwrite}
+                    filterOption={filterAutocompleteOption}
+                    disabled={newOrOverwrite !== 2}
+                    getPopupContainer={() => document.body}
+                  />
+                </div>
+              </div>
             </Radio.Group>
           </div>
         )}
diff --git a/superset-frontend/src/components/Select/Select.test.tsx 
b/superset-frontend/src/components/Select/Select.test.tsx
index 97aec1c081..1515a7c548 100644
--- a/superset-frontend/src/components/Select/Select.test.tsx
+++ b/superset-frontend/src/components/Select/Select.test.tsx
@@ -829,6 +829,28 @@ test('async - requests the options again after clearing 
the cache', async () =>
   expect(mock).toHaveBeenCalledTimes(2);
 });
 
+test('async - triggers getPopupContainer if passed', async () => {
+  const getPopupContainer = jest.fn();
+  render(
+    <div>
+      <Select
+        {...defaultProps}
+        options={loadOptions}
+        getPopupContainer={getPopupContainer}
+      />
+    </div>,
+  );
+  await open();
+  expect(getPopupContainer).toHaveBeenCalled();
+});
+
+test('static - triggers getPopupContainer if passed', async () => {
+  const getPopupContainer = jest.fn();
+  render(<Select {...defaultProps} getPopupContainer={getPopupContainer} />);
+  await open();
+  expect(getPopupContainer).toHaveBeenCalled();
+});
+
 /*
  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/Select/Select.tsx 
b/superset-frontend/src/components/Select/Select.tsx
index 591fe350ce..9cea5a726d 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -67,6 +67,7 @@ type PickedSelectProps = Pick<
   | 'showSearch'
   | 'tokenSeparators'
   | 'value'
+  | 'getPopupContainer'
 >;
 
 export type OptionsType = Exclude<AntdSelectAllProps['options'], undefined>;
@@ -316,6 +317,7 @@ const Select = (
     sortComparator = DEFAULT_SORT_COMPARATOR,
     tokenSeparators,
     value,
+    getPopupContainer,
     ...props
   }: SelectProps,
   ref: RefObject<SelectRef>,
@@ -701,7 +703,9 @@ const Select = (
         dropdownRender={dropdownRender}
         filterOption={handleFilterOption}
         filterSort={sortComparatorWithSearch}
-        getPopupContainer={triggerNode => triggerNode.parentNode}
+        getPopupContainer={
+          getPopupContainer || (triggerNode => triggerNode.parentNode)
+        }
         labelInValue={isAsync || labelInValue}
         maxTagCount={MAX_TAG_COUNT}
         mode={mappedMode}
diff --git 
a/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx
 
b/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx
index 8a65a1139d..46ecbeae58 100644
--- 
a/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx
+++ 
b/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.tsx
@@ -199,10 +199,12 @@ test('Click on Save as dataset', () => {
     name: /save as new undefined/i,
   });
   const overwriteRadioBtn = screen.getByRole('radio', {
-    name: /overwrite existing select or type dataset name/i,
+    name: /overwrite existing/i,
   });
+  const dropdownField = screen.getByText(/select or type dataset name/i);
   expect(saveRadioBtn).toBeVisible();
   expect(overwriteRadioBtn).toBeVisible();
   expect(screen.getByRole('button', { name: /save/i })).toBeVisible();
   expect(screen.getByRole('button', { name: /close/i })).toBeVisible();
+  expect(dropdownField).toBeVisible();
 });
diff --git 
a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx 
b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
index 9803e4a7e6..936c6d548a 100644
--- a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
+++ b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
@@ -307,8 +307,9 @@ export default function PluginFilterSelect(props: 
PluginFilterSelectProps) {
           disabled={isDisabled}
           getPopupContainer={
             showOverflow
-              ? () => parentRef?.current
-              : (trigger: HTMLElement) => trigger?.parentNode
+              ? () => (parentRef?.current as HTMLElement) || document.body
+              : (trigger: HTMLElement) =>
+                  (trigger?.parentNode as HTMLElement) || document.body
           }
           showSearch={showSearch}
           mode={multiSelect ? 'multiple' : 'single'}

Reply via email to