From 1d20d31dd174c767c33fb1da4611a1c141c2aa53 Mon Sep 17 00:00:00 2001 From: Grace Date: Sat, 21 Mar 2020 15:44:54 -0700 Subject: [PATCH 1/4] [explore view] fix long query issue from Run in SQL LAB Button --- superset-frontend/src/chart/chartAction.js | 15 +++++++------ superset-frontend/src/explore/exploreUtils.js | 21 ++++++++++++------- superset/views/core.py | 2 +- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/superset-frontend/src/chart/chartAction.js b/superset-frontend/src/chart/chartAction.js index c7a125f631630..0d6b03b1ea546 100644 --- a/superset-frontend/src/chart/chartAction.js +++ b/superset-frontend/src/chart/chartAction.js @@ -25,6 +25,7 @@ import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import { getExploreUrlAndPayload, getAnnotationJsonUrl, + postForm, } from '../explore/exploreUtils'; import { requiresQuery, @@ -358,14 +359,12 @@ export function redirectSQLLab(formData) { postPayload: { form_data: formData }, }) .then(({ json }) => { - const redirectUrl = new URL(window.location); - redirectUrl.pathname = '/superset/sqllab'; - for (const key of redirectUrl.searchParams.keys()) { - redirectUrl.searchParams.delete(key); - } - redirectUrl.searchParams.set('datasourceKey', formData.datasource); - redirectUrl.searchParams.set('sql', json.query); - window.open(redirectUrl.href, '_blank'); + const redirectUrl = '/superset/sqllab'; + const payload = { + datasourceKey: formData.datasource, + sql: json.query, + }; + postForm(redirectUrl, payload); }) .catch(() => dispatch(addDangerToast(t('An error occurred while loading the SQL'))), diff --git a/superset-frontend/src/explore/exploreUtils.js b/superset-frontend/src/explore/exploreUtils.js index 4b9abebf807e4..13713f6d43c3d 100644 --- a/superset-frontend/src/explore/exploreUtils.js +++ b/superset-frontend/src/explore/exploreUtils.js @@ -190,17 +190,15 @@ export function getExploreUrlAndPayload({ }; } -export function exportChart(formData, endpointType) { - const { url, payload } = getExploreUrlAndPayload({ - formData, - endpointType, - allowDomainSharding: false, - }); +export function postForm(url, payload, target = '_blank') { + if (!url) { + return; + } const exploreForm = document.createElement('form'); exploreForm.action = url; exploreForm.method = 'POST'; - exploreForm.target = '_blank'; + exploreForm.target = target; const token = document.createElement('input'); token.type = 'hidden'; token.name = 'csrf_token'; @@ -216,3 +214,12 @@ export function exportChart(formData, endpointType) { exploreForm.submit(); document.body.removeChild(exploreForm); } + +export function exportChart(formData, endpointType) { + const { url, payload } = getExploreUrlAndPayload({ + formData, + endpointType, + allowDomainSharding: false, + }); + postForm(url, payload); +} diff --git a/superset/views/core.py b/superset/views/core.py index 447461ef3f740..dff4d5ed04093 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -2752,7 +2752,7 @@ def _get_sqllab_payload(user_id: int) -> Dict[str, Any]: } @has_access - @expose("/sqllab") + @expose("/sqllab", methods=["GET", "POST"]) def sqllab(self): """SQL Editor""" payload = self._get_sqllab_payload(g.user.get_id()) From a64aaf96d429d19417f1e67b3ab3b1ea4e4d4597 Mon Sep 17 00:00:00 2001 From: Jesse Yang Date: Sun, 22 Mar 2020 01:05:25 -0700 Subject: [PATCH 2/4] SQL Lab page needs to take the post form data, too --- superset-frontend/src/SqlLab/App.jsx | 5 +-- .../src/SqlLab/components/App.jsx | 2 +- .../SqlLab/components/TabbedSqlEditors.jsx | 7 ++++- .../src/SqlLab/reducers/getInitialState.js | 31 ++++++++++++------- superset/views/core.py | 7 ++++- 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/superset-frontend/src/SqlLab/App.jsx b/superset-frontend/src/SqlLab/App.jsx index 585fede9aa9be..9707b8547b211 100644 --- a/superset-frontend/src/SqlLab/App.jsx +++ b/superset-frontend/src/SqlLab/App.jsx @@ -46,7 +46,9 @@ setupApp(); const appContainer = document.getElementById('app'); const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap')); + initFeatureFlags(bootstrapData.common.feature_flags); + const initialState = getInitialState(bootstrapData); const sqlLabPersistStateConfig = { paths: ['sqlLab'], @@ -59,7 +61,6 @@ const sqlLabPersistStateConfig = { // it caused configurations passed from server-side got override. // see PR 6257 for details delete state[path].common; // eslint-disable-line no-param-reassign - if (path === 'sqlLab') { subset[path] = { ...state[path], @@ -108,7 +109,7 @@ if (sqlLabMenu) { const Application = () => ( - + ); diff --git a/superset-frontend/src/SqlLab/components/App.jsx b/superset-frontend/src/SqlLab/components/App.jsx index f1c21be33615c..01d6ca3e99ffb 100644 --- a/superset-frontend/src/SqlLab/components/App.jsx +++ b/superset-frontend/src/SqlLab/components/App.jsx @@ -125,7 +125,7 @@ class App extends React.PureComponent { content = ( <> - + ); } diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx index b47d7921f05fc..d996417e777e2 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx @@ -99,7 +99,12 @@ class TabbedSqlEditors extends React.PureComponent { }); } - const query = URI(window.location).search(true); + // merge post form data with GET search params + const query = { + ...this.props.formData, + ...URI(window.location).search(true), + }; + // Popping a new tab based on the querystring if (query.id || query.sql || query.savedQueryId || query.datasourceKey) { if (query.id) { diff --git a/superset-frontend/src/SqlLab/reducers/getInitialState.js b/superset-frontend/src/SqlLab/reducers/getInitialState.js index e04f23b93cc09..470816b839338 100644 --- a/superset-frontend/src/SqlLab/reducers/getInitialState.js +++ b/superset-frontend/src/SqlLab/reducers/getInitialState.js @@ -19,8 +19,15 @@ import { t } from '@superset-ui/translation'; import getToastsFromPyFlashMessages from '../../messageToasts/utils/getToastsFromPyFlashMessages'; -export default function getInitialState({ defaultDbId, ...restBootstrapData }) { - /* +export default function getInitialState({ + defaultDbId, + common, + active_tab: activeTab, + tab_state_ids: tabStateIds = [], + databases, + queries: queries_, +}) { + /** * Before YYYY-MM-DD, the state for SQL Lab was stored exclusively in the * browser's localStorage. The feature flag `SQLLAB_BACKEND_PERSISTENCE` * moves the state to the backend instead, migrating it from local storage. @@ -39,7 +46,7 @@ export default function getInitialState({ defaultDbId, ...restBootstrapData }) { autorun: false, templateParams: null, dbId: defaultDbId, - queryLimit: restBootstrapData.common.conf.DEFAULT_SQLLAB_LIMIT, + queryLimit: common.conf.DEFAULT_SQLLAB_LIMIT, validationResult: { id: null, errors: [], @@ -52,11 +59,11 @@ export default function getInitialState({ defaultDbId, ...restBootstrapData }) { }, }; - /* Load state from the backend. This will be empty if the feature flag + /** + * Load state from the backend. This will be empty if the feature flag * `SQLLAB_BACKEND_PERSISTENCE` is off. */ - const activeTab = restBootstrapData.active_tab; - restBootstrapData.tab_state_ids.forEach(({ id, label }) => { + tabStateIds.forEach(({ id, label }) => { let queryEditor; if (activeTab && activeTab.id === id) { queryEditor = { @@ -92,7 +99,6 @@ export default function getInitialState({ defaultDbId, ...restBootstrapData }) { }); const tabHistory = activeTab ? [activeTab.id.toString()] : []; - const tables = []; if (activeTab) { activeTab.table_schemas @@ -126,9 +132,10 @@ export default function getInitialState({ defaultDbId, ...restBootstrapData }) { }); } - const { databases, queries } = restBootstrapData; + const queries = { ...queries_ }; - /* If the `SQLLAB_BACKEND_PERSISTENCE` feature flag is off, or if the user + /** + * If the `SQLLAB_BACKEND_PERSISTENCE` feature flag is off, or if the user * hasn't used SQL Lab after it has been turned on, the state will be stored * in the browser's local storage. */ @@ -174,12 +181,12 @@ export default function getInitialState({ defaultDbId, ...restBootstrapData }) { queriesLastUpdate: Date.now(), }, messageToasts: getToastsFromPyFlashMessages( - (restBootstrapData.common || {}).flash_messages || [], + (common || {}).flash_messages || [], ), localStorageUsageInKilobytes: 0, common: { - flash_messages: restBootstrapData.common.flash_messages, - conf: restBootstrapData.common.conf, + flash_messages: common.flash_messages, + conf: common.conf, }, }; } diff --git a/superset/views/core.py b/superset/views/core.py index dff4d5ed04093..13cc80e5ccdc8 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -2756,8 +2756,13 @@ def _get_sqllab_payload(user_id: int) -> Dict[str, Any]: def sqllab(self): """SQL Editor""" payload = self._get_sqllab_payload(g.user.get_id()) + form_data = request.form.get('form_data') + try: + payload['form_data'] = json.loads(form_data) + except: + pass bootstrap_data = json.dumps( - payload, default=utils.pessimistic_json_iso_dttm_ser + payload, default=utils.pessimistic_json_iso_dttm_ser, ) return self.render_template( From bd3b854e1bd097ea9e97936d6782bcd5a52b500c Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 24 Mar 2020 18:18:09 -0700 Subject: [PATCH 3/4] fix variable names --- superset-frontend/src/SqlLab/App.jsx | 2 +- .../src/SqlLab/components/App.jsx | 2 +- .../SqlLab/components/TabbedSqlEditors.jsx | 7 +++-- .../src/SqlLab/reducers/getInitialState.js | 2 ++ superset/views/core.py | 26 ++++++++++++------- tests/core_tests.py | 2 +- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/superset-frontend/src/SqlLab/App.jsx b/superset-frontend/src/SqlLab/App.jsx index 9707b8547b211..0b3cbbbd58f99 100644 --- a/superset-frontend/src/SqlLab/App.jsx +++ b/superset-frontend/src/SqlLab/App.jsx @@ -109,7 +109,7 @@ if (sqlLabMenu) { const Application = () => ( - + ); diff --git a/superset-frontend/src/SqlLab/components/App.jsx b/superset-frontend/src/SqlLab/components/App.jsx index 01d6ca3e99ffb..f1c21be33615c 100644 --- a/superset-frontend/src/SqlLab/components/App.jsx +++ b/superset-frontend/src/SqlLab/components/App.jsx @@ -125,7 +125,7 @@ class App extends React.PureComponent { content = ( <> - + ); } diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx index d996417e777e2..b4170b28b1d32 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx @@ -39,6 +39,7 @@ const propTypes = { databases: PropTypes.object.isRequired, queries: PropTypes.object.isRequired, queryEditors: PropTypes.array, + requestedQuery: PropTypes.object, tabHistory: PropTypes.array.isRequired, tables: PropTypes.array.isRequired, offline: PropTypes.bool, @@ -48,6 +49,7 @@ const propTypes = { const defaultProps = { queryEditors: [], offline: false, + requestedQuery: null, saveQueryWarning: null, scheduleQueryWarning: null, }; @@ -101,7 +103,7 @@ class TabbedSqlEditors extends React.PureComponent { // merge post form data with GET search params const query = { - ...this.props.formData, + ...this.props.requestedQuery, ...URI(window.location).search(true), }; @@ -379,7 +381,7 @@ class TabbedSqlEditors extends React.PureComponent { TabbedSqlEditors.propTypes = propTypes; TabbedSqlEditors.defaultProps = defaultProps; -function mapStateToProps({ sqlLab, common }) { +function mapStateToProps({ sqlLab, common, requestedQuery }) { return { databases: sqlLab.databases, queryEditors: sqlLab.queryEditors, @@ -393,6 +395,7 @@ function mapStateToProps({ sqlLab, common }) { maxRow: common.conf.SQL_MAX_ROW, saveQueryWarning: common.conf.SQLLAB_SAVE_WARNING_MESSAGE, scheduleQueryWarning: common.conf.SQLLAB_SCHEDULE_WARNING_MESSAGE, + requestedQuery, }; } function mapDispatchToProps(dispatch) { diff --git a/superset-frontend/src/SqlLab/reducers/getInitialState.js b/superset-frontend/src/SqlLab/reducers/getInitialState.js index 470816b839338..fd3c8ae747d0d 100644 --- a/superset-frontend/src/SqlLab/reducers/getInitialState.js +++ b/superset-frontend/src/SqlLab/reducers/getInitialState.js @@ -26,6 +26,7 @@ export default function getInitialState({ tab_state_ids: tabStateIds = [], databases, queries: queries_, + requested_query: requestedQuery, }) { /** * Before YYYY-MM-DD, the state for SQL Lab was stored exclusively in the @@ -180,6 +181,7 @@ export default function getInitialState({ tables, queriesLastUpdate: Date.now(), }, + requestedQuery, messageToasts: getToastsFromPyFlashMessages( (common || {}).flash_messages || [], ), diff --git a/superset/views/core.py b/superset/views/core.py index 13cc80e5ccdc8..b38a5585d09e5 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -2703,7 +2703,7 @@ def profile(self, username): ) @staticmethod - def _get_sqllab_payload(user_id: int) -> Dict[str, Any]: + def _get_sqllab_tabs(user_id: int) -> Dict[str, Any]: # send list of tab state ids tabs_state = ( db.session.query(TabState.id, TabState.label) @@ -2743,8 +2743,6 @@ def _get_sqllab_payload(user_id: int) -> Dict[str, Any]: } return { - "defaultDbId": config["SQLLAB_DEFAULT_DBID"], - "common": common_bootstrap_payload(), "tab_state_ids": tabs_state, "active_tab": active_tab.to_dict() if active_tab else None, "databases": databases, @@ -2755,14 +2753,22 @@ def _get_sqllab_payload(user_id: int) -> Dict[str, Any]: @expose("/sqllab", methods=["GET", "POST"]) def sqllab(self): """SQL Editor""" - payload = self._get_sqllab_payload(g.user.get_id()) - form_data = request.form.get('form_data') - try: - payload['form_data'] = json.loads(form_data) - except: - pass + payload = { + "defaultDbId": config["SQLLAB_DEFAULT_DBID"], + "common": common_bootstrap_payload(), + } + + tabs_data = self._get_sqllab_tabs(g.user.get_id()) + payload.update(tabs_data) + + form_data = request.form.get("form_data") + if form_data: + try: + payload["requested_query"] = json.loads(form_data) + except json.JSONDecodeError: + pass bootstrap_data = json.dumps( - payload, default=utils.pessimistic_json_iso_dttm_ser, + payload, default=utils.pessimistic_json_iso_dttm_ser ) return self.render_template( diff --git a/tests/core_tests.py b/tests/core_tests.py index 8b4ce6978edab..19d1b662a2bd6 100644 --- a/tests/core_tests.py +++ b/tests/core_tests.py @@ -1168,7 +1168,7 @@ def test_sqllab_backend_persistence_payload(self): # we should have only 1 query returned, since the second one is not # associated with any tabs - payload = views.Superset._get_sqllab_payload(user_id=user_id) + payload = views.Superset._get_sqllab_tabs(user_id=user_id) self.assertEqual(len(payload["queries"]), 1) From a2bbc459f8f73c60fdd69b104052e53964809ce9 Mon Sep 17 00:00:00 2001 From: Grace Date: Wed, 25 Mar 2020 11:23:10 -0700 Subject: [PATCH 4/4] updated payload dict, rename hidden form --- superset-frontend/src/explore/exploreUtils.js | 18 +++++++++--------- superset/views/core.py | 4 +--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/superset-frontend/src/explore/exploreUtils.js b/superset-frontend/src/explore/exploreUtils.js index 13713f6d43c3d..226989680f676 100644 --- a/superset-frontend/src/explore/exploreUtils.js +++ b/superset-frontend/src/explore/exploreUtils.js @@ -195,24 +195,24 @@ export function postForm(url, payload, target = '_blank') { return; } - const exploreForm = document.createElement('form'); - exploreForm.action = url; - exploreForm.method = 'POST'; - exploreForm.target = target; + const hiddenForm = document.createElement('form'); + hiddenForm.action = url; + hiddenForm.method = 'POST'; + hiddenForm.target = target; const token = document.createElement('input'); token.type = 'hidden'; token.name = 'csrf_token'; token.value = (document.getElementById('csrf_token') || {}).value; - exploreForm.appendChild(token); + hiddenForm.appendChild(token); const data = document.createElement('input'); data.type = 'hidden'; data.name = 'form_data'; data.value = safeStringify(payload); - exploreForm.appendChild(data); + hiddenForm.appendChild(data); - document.body.appendChild(exploreForm); - exploreForm.submit(); - document.body.removeChild(exploreForm); + document.body.appendChild(hiddenForm); + hiddenForm.submit(); + document.body.removeChild(hiddenForm); } export function exportChart(formData, endpointType) { diff --git a/superset/views/core.py b/superset/views/core.py index b38a5585d09e5..0d233478777ff 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -2756,11 +2756,9 @@ def sqllab(self): payload = { "defaultDbId": config["SQLLAB_DEFAULT_DBID"], "common": common_bootstrap_payload(), + **self._get_sqllab_tabs(g.user.get_id()), } - tabs_data = self._get_sqllab_tabs(g.user.get_id()) - payload.update(tabs_data) - form_data = request.form.get("form_data") if form_data: try: