From cc44a2c01b31e0f51eb8bdfe70d7989b245228e4 Mon Sep 17 00:00:00 2001 From: "Hugh A. Miles II" Date: Tue, 8 Dec 2020 17:29:41 -0800 Subject: [PATCH] feat: Sqllab to Explore UX improvements (#11755) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * create boiler modal component * hello world modal * setup modal flow * setup savemodal for components * flake8 * fix onclick reference * working create datasource boiler * saving spot for callback on text input * working dataset with input box * working redirect on completion * get data for owners dropdown * fix build with pull from master * fix the filteroption * 💯 * move state to upper component * add overwrite state * hacked overwrite process * linting * fix filter * cleaning up the coe * Delete preset.code-workspace * remove unused code * remove visualize * update default value * remove unneeded vars * checkout package-lock.json * linting * get user id * remove page filter * setup proper call for updating columns in dataset * add move to explore flow * linting * add param for overriding columns * linting * change title * fix some tings * cleanup * linting * add types in some places * save toast * use moment * add error toast * create enum for radio states * initial state for saving query * add tpying * addressing concerns * update propTypes * add functionality for CTAS explor btn * handle CTAS state * fix onclick to reference upper level component * formatting * reset state after closing * add error message when user doesn't pick an already selected dataset * remove unneeded todo * remove styling * move async calls to api directory * remove console.log * add user id param * typing * littty * move put to seperate file * save * dsf * fix typing errors * adding more types * fix typing erros * linting * add basic spec test * create dataset modal * add components reference * Rename SaveDatasetModal_spec.jsx to SaveDatasetModal_spec.tsx * remove sinon for now * fix typing errors on modal files * fix linting * address comments * attempt to fix linting * add props * fix test * fix the linting * yerp * fix this references * spaces * handleOverwriteCancel reference cleanup * rename bool value for shouldOverwriteDataset * fix typing for onChange * you still the best in the world * fix spec * align branches * push * fix key names * fix dataset reference * lowercase * fix save bug with tiem * fixed styling * fix date state after push to explore * add disabling states * plz refactor this * this is working fully now * do some renaming * renaming * remove console.logs * still refactoring * remove unneeded code * remove unneeded variables * still cleaning * added interface * fix typing issues * cleanup unused code * fix npm lnit * fix initial problems * add props to test * remove unneeded files * skip linting * saving * this works * remove old test * remove old test * fix linting * fix broken test * remove jsx file * refactoring * cleanup * remove comments * reset user object * fix functions * fix this * reverting CTAS btn flow * remove onclick * save frontend work * allow for database id to be passed as param in body * use enum * fix linting * style alignment * get rid of .then * add function to compute default value with tiem * lit * remove ts-error * fix typing --- .../sqllab/ExploreResultsButton_spec.jsx | 73 ----- .../sqllab/SaveDatasetModal_spec.tsx | 7 +- .../components/ExploreResultsButton.jsx | 56 +--- .../src/SqlLab/components/ResultSet.tsx | 251 +++++++++++++++++- .../SqlLab/components/SaveDatasetModal.tsx | 92 +++---- superset-frontend/src/api/dataset.ts | 2 + superset/datasets/schemas.py | 1 + 7 files changed, 304 insertions(+), 178 deletions(-) diff --git a/superset-frontend/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx b/superset-frontend/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx index 98affc0055..108835ea0c 100644 --- a/superset-frontend/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx +++ b/superset-frontend/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx @@ -24,7 +24,6 @@ import sinon from 'sinon'; import fetchMock from 'fetch-mock'; import shortid from 'shortid'; import sqlLabReducer from 'src/SqlLab/reducers/index'; -import * as actions from 'src/SqlLab/actions/sqlLab'; import ExploreResultsButton from 'src/SqlLab/components/ExploreResultsButton'; import * as exploreUtils from 'src/explore/exploreUtils'; import Button from 'src/components/Button'; @@ -184,77 +183,5 @@ describe('ExploreResultsButton', () => { wrapper.instance().buildVizOptions.restore(); fetchMock.reset(); }); - - it('should build request with correct args', () => { - return new Promise(done => { - wrapper.instance().visualize(); - - setTimeout(() => { - const calls = fetchMock.calls(visualizeEndpoint); - expect(calls).toHaveLength(1); - const formData = calls[0][1].body; - Object.keys(mockOptions).forEach(key => { - // eslint-disable-next-line no-unused-expressions - expect(formData.get(key)).toBeDefined(); - }); - - done(); - }); - }); - }); - - it('should export chart and add an info toast', () => { - return new Promise(done => { - const infoToastSpy = sinon.spy(); - const datasourceSpy = sinon.stub(); - - datasourceSpy.callsFake(() => Promise.resolve(visualizationPayload)); - - wrapper.setProps({ - actions: { - addInfoToast: infoToastSpy, - createDatasource: datasourceSpy, - }, - }); - - wrapper.instance().visualize(); - - setTimeout(() => { - expect(datasourceSpy.callCount).toBe(1); - expect(exploreUtils.exploreChart.callCount).toBe(1); - expect(exploreUtils.exploreChart.getCall(0).args[0].datasource).toBe( - '107__table', - ); - expect(infoToastSpy.callCount).toBe(1); - done(); - }); - }); - }); - - it('should add error toast', () => { - return new Promise(done => { - const dangerToastSpy = sinon.stub(actions, 'addDangerToast'); - const datasourceSpy = sinon.stub(); - - datasourceSpy.callsFake(() => Promise.reject({ error: 'error' })); - - wrapper.setProps({ - actions: { - createDatasource: datasourceSpy, - addDangerToast: dangerToastSpy, - }, - }); - - wrapper.instance().visualize(); - - setTimeout(() => { - expect(datasourceSpy.callCount).toBe(1); - expect(exploreUtils.exportChart.callCount).toBe(0); - expect(dangerToastSpy.callCount).toBe(1); - dangerToastSpy.restore(); - done(); - }); - }); - }); }); }); diff --git a/superset-frontend/spec/javascripts/sqllab/SaveDatasetModal_spec.tsx b/superset-frontend/spec/javascripts/sqllab/SaveDatasetModal_spec.tsx index 4b6ef601ef..0da9a9d07a 100644 --- a/superset-frontend/spec/javascripts/sqllab/SaveDatasetModal_spec.tsx +++ b/superset-frontend/spec/javascripts/sqllab/SaveDatasetModal_spec.tsx @@ -30,11 +30,16 @@ describe('SaveDatasetModal', () => { userDatasetsOwned: [], handleSaveDatasetRadioBtnState: () => {}, saveDatasetRadioBtnState: 1, - shouldOverwriteDataset: false, handleOverwriteCancel: () => {}, handleOverwriteDataset: () => {}, handleOverwriteDatasetOption: () => {}, defaultCreateDatasetValue: 'someDatasets', + shouldOverwriteDataset: false, + userDatasetOptions: [], + disableSaveAndExploreBtn: false, + handleSaveDatasetModalSearch: () => {}, + filterAutocompleteOption: () => false, + onChangeAutoComplete: () => {}, }; it('renders a radio group btn', () => { const wrapper = shallow(); diff --git a/superset-frontend/src/SqlLab/components/ExploreResultsButton.jsx b/superset-frontend/src/SqlLab/components/ExploreResultsButton.jsx index f447b43e43..fc8c8ac017 100644 --- a/superset-frontend/src/SqlLab/components/ExploreResultsButton.jsx +++ b/superset-frontend/src/SqlLab/components/ExploreResultsButton.jsx @@ -26,9 +26,7 @@ import { t } from '@superset-ui/core'; import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls'; import shortid from 'shortid'; -import Modal from 'src/common/components/Modal'; import Button from 'src/components/Button'; -import { exploreChart } from '../../explore/exploreUtils'; import * as actions from '../actions/sqlLab'; const propTypes = { @@ -37,6 +35,7 @@ const propTypes = { errorMessage: PropTypes.string, timeout: PropTypes.number, database: PropTypes.object.isRequired, + onClick: PropTypes.func.isRequired, }; const defaultProps = { query: {}, @@ -45,34 +44,12 @@ const defaultProps = { class ExploreResultsButton extends React.PureComponent { constructor(props) { super(props); - this.visualize = this.visualize.bind(this); - this.onClick = this.onClick.bind(this); this.getInvalidColumns = this.getInvalidColumns.bind(this); this.renderInvalidColumnMessage = this.renderInvalidColumnMessage.bind( this, ); } - onClick() { - const { timeout } = this.props; - const msg = this.renderInvalidColumnMessage(); - if (Math.round(this.getQueryDuration()) > timeout) { - Modal.confirm({ - title: t('Explore'), - content: this.renderTimeoutWarning(), - onOk: this.visualize, - icon: null, - }); - } else if (msg) { - Modal.warning({ - title: t('Explore'), - content: msg, - }); - } else { - this.visualize(); - } - } - getColumns() { const { props } = this; if ( @@ -123,35 +100,6 @@ class ExploreResultsButton extends React.PureComponent { }; } - visualize() { - this.props.actions - .createDatasource(this.buildVizOptions()) - .then(data => { - const columns = this.getColumns(); - const formData = { - datasource: `${data.table_id}__table`, - metrics: [], - groupby: [], - time_range: 'No filter', - viz_type: 'table', - all_columns: columns.map(c => c.name), - row_limit: 1000, - }; - - this.props.actions.addInfoToast( - t('Creating a data source and creating a new tab'), - ); - - // open new window for data visualization - exploreChart(formData); - }) - .catch(() => { - this.props.actions.addDangerToast( - this.props.errorMessage || t('An error occurred'), - ); - }); - } - renderTimeoutWarning() { return ( @@ -203,7 +151,7 @@ class ExploreResultsButton extends React.PureComponent { <> )} {shouldOverwriteDataset && ( <> - )} @@ -155,24 +144,29 @@ export const SaveDatasetModal: FunctionComponent = ({ className="smd-input" defaultValue={defaultCreateDatasetValue} onChange={handleDatasetNameChange} + disabled={saveDatasetRadioBtnState !== 1} /> Overwrite existing )} {shouldOverwriteDataset && ( -
Are you sure you want to overwrite this dataset?
+
+ Are you sure you want to overwrite this dataset? +
)} diff --git a/superset-frontend/src/api/dataset.ts b/superset-frontend/src/api/dataset.ts index 048487c50e..d426daffdd 100644 --- a/superset-frontend/src/api/dataset.ts +++ b/superset-frontend/src/api/dataset.ts @@ -41,6 +41,7 @@ export const getByUser = async (userId: number) => { export const put = async ( datasetId: number, + dbId: number, sql: string, columns: Array>, overrideColumns: boolean, @@ -50,6 +51,7 @@ export const put = async ( const body = JSON.stringify({ sql, columns, + database_id: dbId, }); const data: JsonResponse = await SupersetClient.put({ diff --git a/superset/datasets/schemas.py b/superset/datasets/schemas.py index b8406eb2cf..2703228739 100644 --- a/superset/datasets/schemas.py +++ b/superset/datasets/schemas.py @@ -75,6 +75,7 @@ class DatasetPostSchema(Schema): class DatasetPutSchema(Schema): table_name = fields.String(allow_none=True, validate=Length(1, 250)) + database_id = fields.Integer() sql = fields.String(allow_none=True) filter_select_enabled = fields.Boolean(allow_none=True) fetch_values_predicate = fields.String(allow_none=True, validate=Length(0, 1000))