This is an automated email from the ASF dual-hosted git repository.
elizabeth 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 463406ff09 fix: save dataset and repopulate state (#20965)
463406ff09 is described below
commit 463406ff095375613bf0270343a4af53142c84d6
Author: Elizabeth Thompson <[email protected]>
AuthorDate: Fri Aug 5 14:32:49 2022 -0700
fix: save dataset and repopulate state (#20965)
* save dataset and repopulate state
* disable dashboard button if dataset is missing
* fix error message
* fix tests
---
.../superset-ui-core/src/api/types/core.ts | 31 ++++++++++
.../src/explore/actions/datasourcesActions.test.ts | 54 ++++++++++++++++
.../src/explore/actions/datasourcesActions.ts | 46 +++++++++++++-
.../src/explore/actions/exploreActions.ts | 5 ++
.../src/explore/actions/hydrateExplore.test.ts | 72 +++++++++++++++++++++-
.../src/explore/actions/hydrateExplore.ts | 5 +-
.../src/explore/actions/saveModalActions.js | 16 ++++-
.../src/explore/actions/saveModalActions.test.js | 30 +++++----
.../src/explore/components/SaveModal.tsx | 68 +++++++++-----------
.../src/explore/reducers/exploreReducer.js | 8 +++
superset/views/core.py | 7 ++-
11 files changed, 283 insertions(+), 59 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-core/src/api/types/core.ts
b/superset-frontend/packages/superset-ui-core/src/api/types/core.ts
new file mode 100644
index 0000000000..9aeba85acc
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-core/src/api/types/core.ts
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// /superset/sqllab_viz
+interface SqlLabPostRequest {
+ data: {
+ schema: string;
+ sql: string;
+ dbId: number;
+ templateParams?: string | undefined;
+ datasourceName: string;
+ metrics?: string[];
+ columns?: string[];
+ };
+}
diff --git a/superset-frontend/src/explore/actions/datasourcesActions.test.ts
b/superset-frontend/src/explore/actions/datasourcesActions.test.ts
index a51d8576f8..6317f1f4a6 100644
--- a/superset-frontend/src/explore/actions/datasourcesActions.test.ts
+++ b/superset-frontend/src/explore/actions/datasourcesActions.test.ts
@@ -17,10 +17,14 @@
* under the License.
*/
import { DatasourceType } from '@superset-ui/core';
+import fetchMock from 'fetch-mock';
import {
setDatasource,
changeDatasource,
+ saveDataset,
} from 'src/explore/actions/datasourcesActions';
+import sinon from 'sinon';
+import * as ClientError from 'src/utils/getClientErrorObject';
import datasourcesReducer from '../reducers/datasourcesReducer';
import { updateFormDataByDatasource } from './exploreActions';
@@ -51,10 +55,21 @@ const NEW_DATASOURCE = {
description: null,
};
+const SAVE_DATASET_POST_ARGS = {
+ schema: 'foo',
+ sql: 'select * from bar',
+ database: { id: 1 },
+ templateParams: undefined,
+ datasourceName: 'new dataset',
+ columns: [],
+};
+
const defaultDatasourcesReducerState = {
[CURRENT_DATASOURCE.uid]: CURRENT_DATASOURCE,
};
+const saveDatasetEndpoint = `glob:*/superset/sqllab_viz/`;
+
test('sets new datasource', () => {
const newState = datasourcesReducer(
defaultDatasourcesReducerState,
@@ -83,3 +98,42 @@ test('change datasource action', () => {
updateFormDataByDatasource(CURRENT_DATASOURCE, NEW_DATASOURCE),
);
});
+
+test('saveDataset handles success', async () => {
+ const datasource = { id: 1 };
+ const saveDatasetResponse = {
+ data: datasource,
+ };
+ fetchMock.reset();
+ fetchMock.post(saveDatasetEndpoint, saveDatasetResponse);
+ const dispatch = sinon.spy();
+ const getState = sinon.spy(() => ({ explore: { datasource } }));
+ const dataset = await saveDataset(SAVE_DATASET_POST_ARGS)(dispatch);
+
+ expect(fetchMock.calls(saveDatasetEndpoint)).toHaveLength(1);
+ expect(dispatch.callCount).toBe(1);
+ const thunk = dispatch.getCall(0).args[0];
+ thunk(dispatch, getState);
+ expect(dispatch.getCall(1).args[0].type).toEqual('SET_DATASOURCE');
+
+ expect(dataset).toEqual(datasource);
+});
+
+test('updateSlice with add to existing dashboard handles failure', async () =>
{
+ fetchMock.reset();
+ const sampleError = new Error('sampleError');
+ fetchMock.post(saveDatasetEndpoint, { throws: sampleError });
+ const dispatch = sinon.spy();
+ const errorSpy = jest.spyOn(ClientError, 'getClientErrorObject');
+
+ let caughtError;
+ try {
+ await saveDataset(SAVE_DATASET_POST_ARGS)(dispatch);
+ } catch (error) {
+ caughtError = error;
+ }
+
+ expect(caughtError).toEqual(sampleError);
+ expect(fetchMock.calls(saveDatasetEndpoint)).toHaveLength(4);
+ expect(errorSpy).toHaveBeenCalledWith(sampleError);
+});
diff --git a/superset-frontend/src/explore/actions/datasourcesActions.ts
b/superset-frontend/src/explore/actions/datasourcesActions.ts
index 4fc3bce96a..9306c180e2 100644
--- a/superset-frontend/src/explore/actions/datasourcesActions.ts
+++ b/superset-frontend/src/explore/actions/datasourcesActions.ts
@@ -17,8 +17,12 @@
* under the License.
*/
-import { Dispatch } from 'redux';
+import { Dispatch, AnyAction } from 'redux';
+import { ThunkDispatch } from 'redux-thunk';
import { Dataset } from '@superset-ui/chart-controls';
+import { SupersetClient } from '@superset-ui/core';
+import { addDangerToast } from 'src/components/MessageToasts/actions';
+import { getClientErrorObject } from 'src/utils/getClientErrorObject';
import { updateFormDataByDatasource } from './exploreActions';
import { ExplorePageState } from '../types';
@@ -31,6 +35,45 @@ export function setDatasource(datasource: Dataset) {
return { type: SET_DATASOURCE, datasource };
}
+export function saveDataset({
+ schema,
+ sql,
+ database,
+ templateParams,
+ datasourceName,
+ columns,
+}: Omit<SqlLabPostRequest['data'], 'dbId'> & { database: { id: number } }) {
+ return async function (dispatch: ThunkDispatch<any, undefined, AnyAction>) {
+ // Create a dataset object
+ try {
+ const {
+ json: { data },
+ } = await SupersetClient.post({
+ endpoint: '/superset/sqllab_viz/',
+ postPayload: {
+ data: {
+ schema,
+ sql,
+ dbId: database?.id,
+ templateParams,
+ datasourceName,
+ metrics: [],
+ columns,
+ },
+ },
+ });
+ // Update form_data to point to new dataset
+ dispatch(changeDatasource(data));
+ return data;
+ } catch (error) {
+ getClientErrorObject(error).then(e => {
+ dispatch(addDangerToast(e.error));
+ });
+ throw error;
+ }
+ };
+}
+
export function changeDatasource(newDatasource: Dataset) {
return function (dispatch: Dispatch, getState: () => ExplorePageState) {
const {
@@ -44,6 +87,7 @@ export function changeDatasource(newDatasource: Dataset) {
export const datasourcesActions = {
setDatasource,
changeDatasource,
+ saveDataset,
};
export type AnyDatasourcesAction = SetDatasource;
diff --git a/superset-frontend/src/explore/actions/exploreActions.ts
b/superset-frontend/src/explore/actions/exploreActions.ts
index 2ec0d086cd..a2e6fde69b 100644
--- a/superset-frontend/src/explore/actions/exploreActions.ts
+++ b/superset-frontend/src/explore/actions/exploreActions.ts
@@ -104,6 +104,11 @@ export function setExploreControls(formData:
QueryFormData) {
return { type: SET_EXPLORE_CONTROLS, formData };
}
+export const SET_FORM_DATA = 'UPDATE_FORM_DATA';
+export function setFormData(formData: QueryFormData) {
+ return { type: SET_FORM_DATA, formData };
+}
+
export const UPDATE_CHART_TITLE = 'UPDATE_CHART_TITLE';
export function updateChartTitle(sliceName: string) {
return { type: UPDATE_CHART_TITLE, sliceName };
diff --git a/superset-frontend/src/explore/actions/hydrateExplore.test.ts
b/superset-frontend/src/explore/actions/hydrateExplore.test.ts
index 9cc7b883e9..c74dc1f284 100644
--- a/superset-frontend/src/explore/actions/hydrateExplore.test.ts
+++ b/superset-frontend/src/explore/actions/hydrateExplore.test.ts
@@ -82,7 +82,77 @@ test('creates hydrate action from initial data', () => {
controls: expect.any(Object),
form_data: exploreInitialData.form_data,
slice: exploreInitialData.slice,
- controlsTransferred: [],
+ standalone: null,
+ force: null,
+ },
+ },
+ }),
+ );
+});
+
+test('creates hydrate action with existing state', () => {
+ const dispatch = jest.fn();
+ const getState = jest.fn(() => ({
+ user: {},
+ charts: {},
+ datasources: {},
+ common: {},
+ explore: { controlsTransferred: ['all_columns'] },
+ }));
+ // ignore type check - we dont need exact explore state for this test
+ // @ts-ignore
+ hydrateExplore(exploreInitialData)(dispatch, getState);
+ expect(dispatch).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: HYDRATE_EXPLORE,
+ data: {
+ charts: {
+ 371: {
+ id: 371,
+ chartAlert: null,
+ chartStatus: null,
+ chartStackTrace: null,
+ chartUpdateEndTime: null,
+ chartUpdateStartTime: 0,
+ latestQueryFormData: {
+ cache_timeout: undefined,
+ datasource: '8__table',
+ slice_id: 371,
+ url_params: undefined,
+ viz_type: 'table',
+ },
+ sliceFormData: {
+ cache_timeout: undefined,
+ datasource: '8__table',
+ slice_id: 371,
+ url_params: undefined,
+ viz_type: 'table',
+ },
+ queryController: null,
+ queriesResponse: null,
+ triggerQuery: false,
+ lastRendered: 0,
+ },
+ },
+ datasources: {
+ '8__table': exploreInitialData.dataset,
+ },
+ saveModal: {
+ dashboards: [],
+ saveModalAlert: null,
+ },
+ explore: {
+ can_add: false,
+ can_download: false,
+ can_overwrite: false,
+ isDatasourceMetaLoading: false,
+ isStarred: false,
+ triggerRender: false,
+ datasource: exploreInitialData.dataset,
+ controls: expect.any(Object),
+ controlsTransferred: ['all_columns'],
+ form_data: exploreInitialData.form_data,
+ slice: exploreInitialData.slice,
standalone: null,
force: null,
},
diff --git a/superset-frontend/src/explore/actions/hydrateExplore.ts
b/superset-frontend/src/explore/actions/hydrateExplore.ts
index 0188337abe..24e7382087 100644
--- a/superset-frontend/src/explore/actions/hydrateExplore.ts
+++ b/superset-frontend/src/explore/actions/hydrateExplore.ts
@@ -49,7 +49,8 @@ export const HYDRATE_EXPLORE = 'HYDRATE_EXPLORE';
export const hydrateExplore =
({ form_data, slice, dataset }: ExplorePageInitialData) =>
(dispatch: Dispatch, getState: () => ExplorePageState) => {
- const { user, datasources, charts, sliceEntities, common } = getState();
+ const { user, datasources, charts, sliceEntities, common, explore } =
+ getState();
const sliceId = getUrlParam(URL_PARAMS.sliceId);
const dashboardId = getUrlParam(URL_PARAMS.dashboardId);
@@ -119,7 +120,7 @@ export const hydrateExplore =
controls: initialControls,
form_data: initialFormData,
slice: initialSlice,
- controlsTransferred: [],
+ controlsTransferred: explore.controlsTransferred,
standalone: getUrlParam(URL_PARAMS.standalone),
force: getUrlParam(URL_PARAMS.force),
sliceDashboards: initialFormData.dashboards,
diff --git a/superset-frontend/src/explore/actions/saveModalActions.js
b/superset-frontend/src/explore/actions/saveModalActions.js
index af7f4bc549..2ad6e55490 100644
--- a/superset-frontend/src/explore/actions/saveModalActions.js
+++ b/superset-frontend/src/explore/actions/saveModalActions.js
@@ -135,8 +135,13 @@ const addToasts = (isNewSlice, sliceName,
addedToDashboard) => {
// Update existing slice
export const updateSlice =
- ({ slice_id: sliceId, owners }, sliceName, formData, addedToDashboard) =>
- async dispatch => {
+ ({ slice_id: sliceId, owners }, sliceName, addedToDashboard) =>
+ async (dispatch, getState) => {
+ const {
+ explore: {
+ form_data: { url_params: _, ...formData },
+ },
+ } = getState();
try {
const response = await SupersetClient.put({
endpoint: `/api/v1/chart/${sliceId}`,
@@ -154,7 +159,12 @@ export const updateSlice =
// Create new slice
export const createSlice =
- (sliceName, formData, addedToDashboard) => async dispatch => {
+ (sliceName, addedToDashboard) => async (dispatch, getState) => {
+ const {
+ explore: {
+ form_data: { url_params: _, ...formData },
+ },
+ } = getState();
try {
const response = await SupersetClient.post({
endpoint: `/api/v1/chart/`,
diff --git a/superset-frontend/src/explore/actions/saveModalActions.test.js
b/superset-frontend/src/explore/actions/saveModalActions.test.js
index 1d97c3f45e..9dfb385b47 100644
--- a/superset-frontend/src/explore/actions/saveModalActions.test.js
+++ b/superset-frontend/src/explore/actions/saveModalActions.test.js
@@ -75,6 +75,7 @@ const formData = {
datasource: `${datasourceId}__${datasourceType}`,
dashboards,
};
+const mockExploreState = { explore: { form_data: formData } };
const sliceResponsePayload = {
id: 10,
@@ -95,11 +96,11 @@ test('updateSlice handles success', async () => {
fetchMock.reset();
fetchMock.put(updateSliceEndpoint, sliceResponsePayload);
const dispatch = sinon.spy();
- const slice = await updateSlice(
- { slice_id: sliceId },
- sliceName,
- formData,
- )(dispatch);
+ const getState = sinon.spy(() => mockExploreState);
+ const slice = await updateSlice({ slice_id: sliceId }, sliceName)(
+ dispatch,
+ getState,
+ );
expect(fetchMock.calls(updateSliceEndpoint)).toHaveLength(1);
expect(dispatch.callCount).toBe(2);
@@ -117,9 +118,10 @@ test('updateSlice handles failure', async () => {
fetchMock.reset();
fetchMock.put(updateSliceEndpoint, { throws: sampleError });
const dispatch = sinon.spy();
+ const getState = sinon.spy(() => mockExploreState);
let caughtError;
try {
- await updateSlice({ slice_id: sliceId }, sliceName, formData)(dispatch);
+ await updateSlice({ slice_id: sliceId }, sliceName)(dispatch, getState);
} catch (error) {
caughtError = error;
}
@@ -139,7 +141,8 @@ test('createSlice handles success', async () => {
fetchMock.reset();
fetchMock.post(createSliceEndpoint, sliceResponsePayload);
const dispatch = sinon.spy();
- const slice = await createSlice(sliceName, formData)(dispatch);
+ const getState = sinon.spy(() => mockExploreState);
+ const slice = await createSlice(sliceName)(dispatch, getState);
expect(fetchMock.calls(createSliceEndpoint)).toHaveLength(1);
expect(dispatch.callCount).toBe(2);
expect(dispatch.getCall(0).args[0].type).toBe(SAVE_SLICE_SUCCESS);
@@ -156,9 +159,10 @@ test('createSlice handles failure', async () => {
fetchMock.reset();
fetchMock.post(createSliceEndpoint, { throws: sampleError });
const dispatch = sinon.spy();
+ const getState = sinon.spy(() => mockExploreState);
let caughtError;
try {
- await createSlice(sliceName, formData)(dispatch);
+ await createSlice(sliceName)(dispatch, getState);
} catch (error) {
caughtError = error;
}
@@ -243,10 +247,11 @@ test('updateSlice with add to new dashboard handles
success', async () => {
fetchMock.reset();
fetchMock.put(updateSliceEndpoint, sliceResponsePayload);
const dispatch = sinon.spy();
- const slice = await updateSlice({ slice_id: sliceId }, sliceName, formData, {
+ const getState = sinon.spy(() => mockExploreState);
+ const slice = await updateSlice({ slice_id: sliceId }, sliceName, {
new: true,
title: dashboardName,
- })(dispatch);
+ })(dispatch, getState);
expect(fetchMock.calls(updateSliceEndpoint)).toHaveLength(1);
expect(dispatch.callCount).toBe(3);
@@ -269,10 +274,11 @@ test('updateSlice with add to existing dashboard handles
success', async () => {
fetchMock.reset();
fetchMock.put(updateSliceEndpoint, sliceResponsePayload);
const dispatch = sinon.spy();
- const slice = await updateSlice({ slice_id: sliceId }, sliceName, formData, {
+ const getState = sinon.spy(() => mockExploreState);
+ const slice = await updateSlice({ slice_id: sliceId }, sliceName, {
new: false,
title: dashboardName,
- })(dispatch);
+ })(dispatch, getState);
expect(fetchMock.calls(updateSliceEndpoint)).toHaveLength(1);
expect(dispatch.callCount).toBe(3);
diff --git a/superset-frontend/src/explore/components/SaveModal.tsx
b/superset-frontend/src/explore/components/SaveModal.tsx
index 0297d6c6f8..04c7fd6b8c 100644
--- a/superset-frontend/src/explore/components/SaveModal.tsx
+++ b/superset-frontend/src/explore/components/SaveModal.tsx
@@ -21,7 +21,7 @@ import React from 'react';
import { Input } from 'src/components/Input';
import { Form, FormItem } from 'src/components/Form';
import Alert from 'src/components/Alert';
-import { t, styled, SupersetClient, DatasourceType } from '@superset-ui/core';
+import { t, styled, DatasourceType } from '@superset-ui/core';
import ReactMarkdown from 'react-markdown';
import Modal from 'src/components/Modal';
import { Radio } from 'src/components/Radio';
@@ -31,7 +31,6 @@ import { SelectValue } from 'antd/lib/select';
import { connect } from 'react-redux';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
-import { getClientErrorObject } from 'src/utils/getClientErrorObject';
import Loading from 'src/components/Loading';
// Session storage key for recent dashboard
@@ -87,7 +86,6 @@ class SaveModal extends React.Component<SaveModalProps,
SaveModalState> {
alert: null,
action: this.canOverwriteSlice() ? 'overwrite' : 'saveas',
isLoading: false,
- saveStatus: null,
};
this.onDashboardSelectChange = this.onDashboardSelectChange.bind(this);
this.onSliceNameChange = this.onSliceNameChange.bind(this);
@@ -154,7 +152,6 @@ class SaveModal extends React.Component<SaveModalProps,
SaveModalState> {
this.setState({ alert: null, isLoading: true });
this.props.actions.removeSaveModalAlert();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { url_params, ...formData } = this.props.form_data || {};
let promise = Promise.resolve();
@@ -170,38 +167,22 @@ class SaveModal extends React.Component<SaveModalProps,
SaveModalState> {
const { templateParams } = this.props.datasource;
const columns = this.props.datasource?.columns || [];
- // Create a dataset object
- await SupersetClient.post({
- endpoint: '/superset/sqllab_viz/',
- postPayload: {
- data: {
- schema,
- sql,
- dbId: database?.id,
- templateParams,
- datasourceName: this.state.datasetName,
- metrics: [],
- columns,
- },
- },
- })
- .then(({ json }) => json)
- .then(async (data: { table_id: number }) => {
- // Update form_data to point to new dataset
- formData.datasource = `${data.table_id}__table`;
- this.setState({ saveStatus: 'succeed' });
- })
- .catch(response =>
- getClientErrorObject(response).then(e => {
- this.setState({ isLoading: false, saveStatus: 'failed' });
- this.props.addDangerToast(e.error);
- }),
- );
+ try {
+ await this.props.actions.saveDataset({
+ schema,
+ sql,
+ database,
+ templateParams,
+ datasourceName: this.state.datasetName,
+ columns,
+ });
+ } catch {
+ // Don't continue since server was unable to create dataset
+ this.setState({ isLoading: false });
+ return;
+ }
}
- // Don't continue since server was unable to create dataset
- if (this.state.saveStatus === 'failed') return;
-
let dashboard: DashboardGetResponse | null = null;
if (this.state.newDashboardName || this.state.saveToDashboardId) {
let saveToDashboardId = this.state.saveToDashboardId || null;
@@ -221,7 +202,11 @@ class SaveModal extends React.Component<SaveModalProps,
SaveModalState> {
dashboard = response.result;
const dashboards = new Set<number>(this.props.sliceDashboards);
dashboards.add(dashboard.id);
- formData.dashboards = Array.from(dashboards);
+ const { url_params, ...formData } = this.props.form_data || {};
+ this.props.actions.setFormData({
+ ...formData,
+ dashboards: Array.from(dashboards),
+ });
});
}
@@ -231,7 +216,6 @@ class SaveModal extends React.Component<SaveModalProps,
SaveModalState> {
this.props.actions.updateSlice(
this.props.slice,
this.state.newSliceName,
- formData,
dashboard
? {
title: dashboard.dashboard_title,
@@ -244,7 +228,6 @@ class SaveModal extends React.Component<SaveModalProps,
SaveModalState> {
promise = promise.then(() =>
this.props.actions.createSlice(
this.state.newSliceName,
- formData,
dashboard
? {
title: dashboard.dashboard_title,
@@ -386,7 +369,9 @@ class SaveModal extends React.Component<SaveModalProps,
SaveModalState> {
buttonSize="small"
disabled={
!this.state.newSliceName ||
- (!this.state.saveToDashboardId && !this.state.newDashboardName)
+ (!this.state.saveToDashboardId && !this.state.newDashboardName) ||
+ (this.props.datasource?.type !== DatasourceType.Table &&
+ !this.state.datasetName)
}
onClick={() => this.saveOrOverwrite(true)}
>
@@ -399,7 +384,12 @@ class SaveModal extends React.Component<SaveModalProps,
SaveModalState> {
buttonSize="small"
buttonStyle="primary"
onClick={() => this.saveOrOverwrite(false)}
- disabled={this.state.isLoading || !this.state.newSliceName}
+ disabled={
+ this.state.isLoading ||
+ !this.state.newSliceName ||
+ (this.props.datasource?.type !== DatasourceType.Table &&
+ !this.state.datasetName)
+ }
data-test="btn-modal-save"
>
{!this.canOverwriteSlice() && this.props.slice
diff --git a/superset-frontend/src/explore/reducers/exploreReducer.js
b/superset-frontend/src/explore/reducers/exploreReducer.js
index f99ab9437c..0bd3012d46 100644
--- a/superset-frontend/src/explore/reducers/exploreReducer.js
+++ b/superset-frontend/src/explore/reducers/exploreReducer.js
@@ -62,6 +62,8 @@ export default function exploreReducer(state = {}, action) {
// reset time range filter to default
newFormData.time_range = DEFAULT_TIME_RANGE;
+ newFormData.datasource = newDatasource.uid;
+
// reset control values for column/metric related controls
Object.entries(controls).forEach(([controlName, controlState]) => {
if (
@@ -215,6 +217,12 @@ export default function exploreReducer(state = {}, action)
{
controls: getControlsState(state, action.formData),
};
},
+ [actions.SET_FORM_DATA]() {
+ return {
+ ...state,
+ form_data: action.formData,
+ };
+ },
[actions.UPDATE_CHART_TITLE]() {
return {
...state,
diff --git a/superset/views/core.py b/superset/views/core.py
index 49035613f9..24c56e61d5 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -2135,7 +2135,12 @@ class Superset(BaseSupersetView): # pylint:
disable=too-many-public-methods
table.columns = cols
table.metrics = [SqlMetric(metric_name="count", expression="count(*)")]
db.session.commit()
- return json_success(json.dumps({"table_id": table.id}))
+
+ return json_success(
+ json.dumps(
+ {"table_id": table.id, "data":
sanitize_datasource_data(table.data)}
+ )
+ )
@has_access
@expose("/extra_table_metadata/<int:database_id>/<table_name>/<schema>/")