From 8a57a71bed30a781a1d5e5b2ce42ccd08045b3e9 Mon Sep 17 00:00:00 2001 From: Antonio Rivero Martinez <38889534+Antonio-RiveroMartnez@users.noreply.github.com> Date: Fri, 1 Jul 2022 17:40:13 -0300 Subject: [PATCH] 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.test.tsx | 2 +- .../components/SaveDatasetModal/index.tsx | 121 +++++++++--------- .../src/components/Select/Select.test.tsx | 22 ++++ .../src/components/Select/Select.tsx | 6 +- .../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 c35b5eb2b..d0698e3ed 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(, { 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 4a87d52c1..4fbfe11ad 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 = ({ DatasetRadioState.SAVE_NEW, ); const [shouldOverwriteDataset, setShouldOverwriteDataset] = useState(false); - const [userDatasetOptions, setUserDatasetOptions] = useState< - DatasetOptionAutocomplete[] - >([]); const [datasetToOverwrite, setDatasetToOverwrite] = useState< Record >({}); - const [autocompleteValue, setAutocompleteValue] = useState(''); + const [selectedDatasetToOverwrite, setSelectedDatasetToOverwrite] = useState< + SelectValue | undefined + >(undefined); const user = useSelector(user => getInitialState(user), @@ -146,7 +149,7 @@ export const SaveDatasetModal: FunctionComponent = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ 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, - ) => setDatasetToOverwrite(option); - const handleDatasetNameChange = (e: React.FormEvent) => { // @ts-expect-error setDatasetName(e.target.value); @@ -298,12 +294,11 @@ export const SaveDatasetModal: FunctionComponent = ({ (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 = ({ disabled={newOrOverwrite !== 1} /> - - {t('Overwrite existing')} - { - setDatasetToOverwrite({}); - setAutocompleteValue(value); - }} - placeholder={t('Select or type dataset name')} - filterOption={filterAutocompleteOption} - disabled={newOrOverwrite !== 2} - value={autocompleteValue} - /> - +
+ + {t('Overwrite existing')} + +
+ +
, + ); + await open(); + expect(getPopupContainer).toHaveBeenCalled(); +}); + +test('static - triggers getPopupContainer if passed', async () => { + const getPopupContainer = jest.fn(); + render(