[explore view] fix long query issue from Run in SQL LAB Button (#9345)

* [explore view] fix long query issue from Run in SQL LAB Button

* SQL Lab page needs to take the post form data, too

* fix variable names

* updated payload dict, rename hidden form

Co-authored-by: Jesse Yang <jesse.yang@airbnb.com>
This commit is contained in:
Grace Guo 2020-03-25 13:15:52 -07:00 committed by GitHub
parent 43f0221304
commit 6b0f62a36e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 81 additions and 48 deletions

View File

@ -46,7 +46,9 @@ setupApp();
const appContainer = document.getElementById('app'); const appContainer = document.getElementById('app');
const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap')); const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
initFeatureFlags(bootstrapData.common.feature_flags); initFeatureFlags(bootstrapData.common.feature_flags);
const initialState = getInitialState(bootstrapData); const initialState = getInitialState(bootstrapData);
const sqlLabPersistStateConfig = { const sqlLabPersistStateConfig = {
paths: ['sqlLab'], paths: ['sqlLab'],
@ -59,7 +61,6 @@ const sqlLabPersistStateConfig = {
// it caused configurations passed from server-side got override. // it caused configurations passed from server-side got override.
// see PR 6257 for details // see PR 6257 for details
delete state[path].common; // eslint-disable-line no-param-reassign delete state[path].common; // eslint-disable-line no-param-reassign
if (path === 'sqlLab') { if (path === 'sqlLab') {
subset[path] = { subset[path] = {
...state[path], ...state[path],

View File

@ -39,6 +39,7 @@ const propTypes = {
databases: PropTypes.object.isRequired, databases: PropTypes.object.isRequired,
queries: PropTypes.object.isRequired, queries: PropTypes.object.isRequired,
queryEditors: PropTypes.array, queryEditors: PropTypes.array,
requestedQuery: PropTypes.object,
tabHistory: PropTypes.array.isRequired, tabHistory: PropTypes.array.isRequired,
tables: PropTypes.array.isRequired, tables: PropTypes.array.isRequired,
offline: PropTypes.bool, offline: PropTypes.bool,
@ -48,6 +49,7 @@ const propTypes = {
const defaultProps = { const defaultProps = {
queryEditors: [], queryEditors: [],
offline: false, offline: false,
requestedQuery: null,
saveQueryWarning: null, saveQueryWarning: null,
scheduleQueryWarning: null, scheduleQueryWarning: null,
}; };
@ -99,7 +101,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.requestedQuery,
...URI(window.location).search(true),
};
// Popping a new tab based on the querystring // Popping a new tab based on the querystring
if (query.id || query.sql || query.savedQueryId || query.datasourceKey) { if (query.id || query.sql || query.savedQueryId || query.datasourceKey) {
if (query.id) { if (query.id) {
@ -374,7 +381,7 @@ class TabbedSqlEditors extends React.PureComponent {
TabbedSqlEditors.propTypes = propTypes; TabbedSqlEditors.propTypes = propTypes;
TabbedSqlEditors.defaultProps = defaultProps; TabbedSqlEditors.defaultProps = defaultProps;
function mapStateToProps({ sqlLab, common }) { function mapStateToProps({ sqlLab, common, requestedQuery }) {
return { return {
databases: sqlLab.databases, databases: sqlLab.databases,
queryEditors: sqlLab.queryEditors, queryEditors: sqlLab.queryEditors,
@ -388,6 +395,7 @@ function mapStateToProps({ sqlLab, common }) {
maxRow: common.conf.SQL_MAX_ROW, maxRow: common.conf.SQL_MAX_ROW,
saveQueryWarning: common.conf.SQLLAB_SAVE_WARNING_MESSAGE, saveQueryWarning: common.conf.SQLLAB_SAVE_WARNING_MESSAGE,
scheduleQueryWarning: common.conf.SQLLAB_SCHEDULE_WARNING_MESSAGE, scheduleQueryWarning: common.conf.SQLLAB_SCHEDULE_WARNING_MESSAGE,
requestedQuery,
}; };
} }
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {

View File

@ -19,8 +19,16 @@
import { t } from '@superset-ui/translation'; import { t } from '@superset-ui/translation';
import getToastsFromPyFlashMessages from '../../messageToasts/utils/getToastsFromPyFlashMessages'; 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_,
requested_query: requestedQuery,
}) {
/**
* Before YYYY-MM-DD, the state for SQL Lab was stored exclusively in the * Before YYYY-MM-DD, the state for SQL Lab was stored exclusively in the
* browser's localStorage. The feature flag `SQLLAB_BACKEND_PERSISTENCE` * browser's localStorage. The feature flag `SQLLAB_BACKEND_PERSISTENCE`
* moves the state to the backend instead, migrating it from local storage. * moves the state to the backend instead, migrating it from local storage.
@ -39,7 +47,7 @@ export default function getInitialState({ defaultDbId, ...restBootstrapData }) {
autorun: false, autorun: false,
templateParams: null, templateParams: null,
dbId: defaultDbId, dbId: defaultDbId,
queryLimit: restBootstrapData.common.conf.DEFAULT_SQLLAB_LIMIT, queryLimit: common.conf.DEFAULT_SQLLAB_LIMIT,
validationResult: { validationResult: {
id: null, id: null,
errors: [], errors: [],
@ -52,11 +60,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. * `SQLLAB_BACKEND_PERSISTENCE` is off.
*/ */
const activeTab = restBootstrapData.active_tab; tabStateIds.forEach(({ id, label }) => {
restBootstrapData.tab_state_ids.forEach(({ id, label }) => {
let queryEditor; let queryEditor;
if (activeTab && activeTab.id === id) { if (activeTab && activeTab.id === id) {
queryEditor = { queryEditor = {
@ -92,7 +100,6 @@ export default function getInitialState({ defaultDbId, ...restBootstrapData }) {
}); });
const tabHistory = activeTab ? [activeTab.id.toString()] : []; const tabHistory = activeTab ? [activeTab.id.toString()] : [];
const tables = []; const tables = [];
if (activeTab) { if (activeTab) {
activeTab.table_schemas activeTab.table_schemas
@ -126,9 +133,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 * hasn't used SQL Lab after it has been turned on, the state will be stored
* in the browser's local storage. * in the browser's local storage.
*/ */
@ -173,13 +181,14 @@ export default function getInitialState({ defaultDbId, ...restBootstrapData }) {
tables, tables,
queriesLastUpdate: Date.now(), queriesLastUpdate: Date.now(),
}, },
requestedQuery,
messageToasts: getToastsFromPyFlashMessages( messageToasts: getToastsFromPyFlashMessages(
(restBootstrapData.common || {}).flash_messages || [], (common || {}).flash_messages || [],
), ),
localStorageUsageInKilobytes: 0, localStorageUsageInKilobytes: 0,
common: { common: {
flash_messages: restBootstrapData.common.flash_messages, flash_messages: common.flash_messages,
conf: restBootstrapData.common.conf, conf: common.conf,
}, },
}; };
} }

View File

@ -25,6 +25,7 @@ import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import { import {
getExploreUrlAndPayload, getExploreUrlAndPayload,
getAnnotationJsonUrl, getAnnotationJsonUrl,
postForm,
} from '../explore/exploreUtils'; } from '../explore/exploreUtils';
import { import {
requiresQuery, requiresQuery,
@ -358,14 +359,12 @@ export function redirectSQLLab(formData) {
postPayload: { form_data: formData }, postPayload: { form_data: formData },
}) })
.then(({ json }) => { .then(({ json }) => {
const redirectUrl = new URL(window.location); const redirectUrl = '/superset/sqllab';
redirectUrl.pathname = '/superset/sqllab'; const payload = {
for (const key of redirectUrl.searchParams.keys()) { datasourceKey: formData.datasource,
redirectUrl.searchParams.delete(key); sql: json.query,
} };
redirectUrl.searchParams.set('datasourceKey', formData.datasource); postForm(redirectUrl, payload);
redirectUrl.searchParams.set('sql', json.query);
window.open(redirectUrl.href, '_blank');
}) })
.catch(() => .catch(() =>
dispatch(addDangerToast(t('An error occurred while loading the SQL'))), dispatch(addDangerToast(t('An error occurred while loading the SQL'))),

View File

@ -190,29 +190,36 @@ export function getExploreUrlAndPayload({
}; };
} }
export function postForm(url, payload, target = '_blank') {
if (!url) {
return;
}
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;
hiddenForm.appendChild(token);
const data = document.createElement('input');
data.type = 'hidden';
data.name = 'form_data';
data.value = safeStringify(payload);
hiddenForm.appendChild(data);
document.body.appendChild(hiddenForm);
hiddenForm.submit();
document.body.removeChild(hiddenForm);
}
export function exportChart(formData, endpointType) { export function exportChart(formData, endpointType) {
const { url, payload } = getExploreUrlAndPayload({ const { url, payload } = getExploreUrlAndPayload({
formData, formData,
endpointType, endpointType,
allowDomainSharding: false, allowDomainSharding: false,
}); });
postForm(url, payload);
const exploreForm = document.createElement('form');
exploreForm.action = url;
exploreForm.method = 'POST';
exploreForm.target = '_blank';
const token = document.createElement('input');
token.type = 'hidden';
token.name = 'csrf_token';
token.value = (document.getElementById('csrf_token') || {}).value;
exploreForm.appendChild(token);
const data = document.createElement('input');
data.type = 'hidden';
data.name = 'form_data';
data.value = safeStringify(payload);
exploreForm.appendChild(data);
document.body.appendChild(exploreForm);
exploreForm.submit();
document.body.removeChild(exploreForm);
} }

View File

@ -2713,7 +2713,7 @@ class Superset(BaseSupersetView):
) )
@staticmethod @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 # send list of tab state ids
tabs_state = ( tabs_state = (
db.session.query(TabState.id, TabState.label) db.session.query(TabState.id, TabState.label)
@ -2753,8 +2753,6 @@ class Superset(BaseSupersetView):
} }
return { return {
"defaultDbId": config["SQLLAB_DEFAULT_DBID"],
"common": common_bootstrap_payload(),
"tab_state_ids": tabs_state, "tab_state_ids": tabs_state,
"active_tab": active_tab.to_dict() if active_tab else None, "active_tab": active_tab.to_dict() if active_tab else None,
"databases": databases, "databases": databases,
@ -2762,10 +2760,21 @@ class Superset(BaseSupersetView):
} }
@has_access @has_access
@expose("/sqllab") @expose("/sqllab", methods=["GET", "POST"])
def sqllab(self): def sqllab(self):
"""SQL Editor""" """SQL Editor"""
payload = self._get_sqllab_payload(g.user.get_id()) payload = {
"defaultDbId": config["SQLLAB_DEFAULT_DBID"],
"common": common_bootstrap_payload(),
**self._get_sqllab_tabs(g.user.get_id()),
}
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( bootstrap_data = json.dumps(
payload, default=utils.pessimistic_json_iso_dttm_ser payload, default=utils.pessimistic_json_iso_dttm_ser
) )

View File

@ -1177,7 +1177,7 @@ class CoreTests(SupersetTestCase):
# we should have only 1 query returned, since the second one is not # we should have only 1 query returned, since the second one is not
# associated with any tabs # 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) self.assertEqual(len(payload["queries"]), 1)