refactor(sql_lab): SQL Lab Persistent Saved State (#17771)
* a lot of console logs * testing * test * added saved_query to remoteId * created useEffect so that title properly changes in modal * Update superset-frontend/src/SqlLab/actions/sqlLab.js Co-authored-by: Lyndsi Kay Williams <55605634+lyndsiWilliams@users.noreply.github.com> Co-authored-by: Lyndsi Kay Williams <55605634+lyndsiWilliams@users.noreply.github.com>
This commit is contained in:
parent
5bfe2d47b0
commit
88db2cc0ab
|
|
@ -636,12 +636,13 @@ export function switchQueryEditor(queryEditor, displayLimit) {
|
||||||
title: json.label,
|
title: json.label,
|
||||||
sql: json.sql,
|
sql: json.sql,
|
||||||
selectedText: null,
|
selectedText: null,
|
||||||
latestQueryId: json.latest_query ? json.latest_query.id : null,
|
latestQueryId: json.latest_query?.id,
|
||||||
autorun: json.autorun,
|
autorun: json.autorun,
|
||||||
dbId: json.database_id,
|
dbId: json.database_id,
|
||||||
templateParams: json.template_params,
|
templateParams: json.template_params,
|
||||||
schema: json.schema,
|
schema: json.schema,
|
||||||
queryLimit: json.query_limit,
|
queryLimit: json.query_limit,
|
||||||
|
remoteId: json.saved_query?.id,
|
||||||
validationResult: {
|
validationResult: {
|
||||||
id: null,
|
id: null,
|
||||||
errors: [],
|
errors: [],
|
||||||
|
|
@ -864,19 +865,38 @@ export function saveQuery(query) {
|
||||||
stringify: false,
|
stringify: false,
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
|
const savedQuery = convertQueryToClient(result.json.item);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: QUERY_EDITOR_SAVED,
|
type: QUERY_EDITOR_SAVED,
|
||||||
query,
|
query,
|
||||||
result: convertQueryToClient(result.json.item),
|
result: savedQuery,
|
||||||
});
|
});
|
||||||
dispatch(addSuccessToast(t('Your query was saved')));
|
|
||||||
dispatch(queryEditorSetTitle(query, query.title));
|
dispatch(queryEditorSetTitle(query, query.title));
|
||||||
|
return savedQuery;
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
dispatch(addDangerToast(t('Your query could not be saved'))),
|
dispatch(addDangerToast(t('Your query could not be saved'))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const addSavedQueryToTabState =
|
||||||
|
(queryEditor, savedQuery) => dispatch => {
|
||||||
|
const sync = isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE)
|
||||||
|
? SupersetClient.put({
|
||||||
|
endpoint: `/tabstateview/${queryEditor.id}`,
|
||||||
|
postPayload: { saved_query_id: savedQuery.remoteId },
|
||||||
|
})
|
||||||
|
: Promise.resolve();
|
||||||
|
|
||||||
|
return sync
|
||||||
|
.catch(() => {
|
||||||
|
dispatch(addDangerToast(t('Your query was not properly saved')));
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
dispatch(addSuccessToast(t('Your query was saved')));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export function updateSavedQuery(query) {
|
export function updateSavedQuery(query) {
|
||||||
return dispatch =>
|
return dispatch =>
|
||||||
SupersetClient.put({
|
SupersetClient.put({
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import configureMockStore from 'redux-mock-store';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import shortid from 'shortid';
|
import shortid from 'shortid';
|
||||||
import * as featureFlags from 'src/featureFlags';
|
import * as featureFlags from 'src/featureFlags';
|
||||||
import { ADD_TOAST } from 'src/components/MessageToasts/actions';
|
|
||||||
import * as actions from 'src/SqlLab/actions/sqlLab';
|
import * as actions from 'src/SqlLab/actions/sqlLab';
|
||||||
import { defaultQueryEditor, query } from '../fixtures';
|
import { defaultQueryEditor, query } from '../fixtures';
|
||||||
|
|
||||||
|
|
@ -93,7 +92,7 @@ describe('async actions', () => {
|
||||||
expect.assertions(1);
|
expect.assertions(1);
|
||||||
|
|
||||||
return makeRequest().then(() => {
|
return makeRequest().then(() => {
|
||||||
expect(dispatch.callCount).toBe(3);
|
expect(dispatch.callCount).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -111,7 +110,6 @@ describe('async actions', () => {
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
const expectedActionTypes = [
|
const expectedActionTypes = [
|
||||||
actions.QUERY_EDITOR_SAVED,
|
actions.QUERY_EDITOR_SAVED,
|
||||||
ADD_TOAST,
|
|
||||||
actions.QUERY_EDITOR_SET_TITLE,
|
actions.QUERY_EDITOR_SET_TITLE,
|
||||||
];
|
];
|
||||||
return store.dispatch(actions.saveQuery(query)).then(() => {
|
return store.dispatch(actions.saveQuery(query)).then(() => {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Row, Col, Input, TextArea } from 'src/common/components';
|
import { Row, Col, Input, TextArea } from 'src/common/components';
|
||||||
import { t, styled } from '@superset-ui/core';
|
import { t, styled } from '@superset-ui/core';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
|
|
@ -90,6 +90,11 @@ export default function SaveQuery({
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSaved) {
|
||||||
|
setLabel(defaultLabel);
|
||||||
|
}
|
||||||
|
}, [defaultLabel]);
|
||||||
const close = () => {
|
const close = () => {
|
||||||
setShowSave(false);
|
setShowSave(false);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ import {
|
||||||
queryEditorSetTemplateParams,
|
queryEditorSetTemplateParams,
|
||||||
runQuery,
|
runQuery,
|
||||||
saveQuery,
|
saveQuery,
|
||||||
|
addSavedQueryToTabState,
|
||||||
scheduleQuery,
|
scheduleQuery,
|
||||||
setActiveSouthPaneTab,
|
setActiveSouthPaneTab,
|
||||||
updateSavedQuery,
|
updateSavedQuery,
|
||||||
|
|
@ -192,6 +193,7 @@ class SqlEditor extends React.PureComponent {
|
||||||
this.canValidateQuery = this.canValidateQuery.bind(this);
|
this.canValidateQuery = this.canValidateQuery.bind(this);
|
||||||
this.runQuery = this.runQuery.bind(this);
|
this.runQuery = this.runQuery.bind(this);
|
||||||
this.stopQuery = this.stopQuery.bind(this);
|
this.stopQuery = this.stopQuery.bind(this);
|
||||||
|
this.saveQuery = this.saveQuery.bind(this);
|
||||||
this.onSqlChanged = this.onSqlChanged.bind(this);
|
this.onSqlChanged = this.onSqlChanged.bind(this);
|
||||||
this.setQueryEditorSql = this.setQueryEditorSql.bind(this);
|
this.setQueryEditorSql = this.setQueryEditorSql.bind(this);
|
||||||
this.setQueryEditorSqlWithDebounce = debounce(
|
this.setQueryEditorSqlWithDebounce = debounce(
|
||||||
|
|
@ -592,6 +594,12 @@ class SqlEditor extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async saveQuery(query) {
|
||||||
|
const { queryEditor: qe, actions } = this.props;
|
||||||
|
const savedQuery = await actions.saveQuery(query);
|
||||||
|
actions.addSavedQueryToTabState(qe, savedQuery);
|
||||||
|
}
|
||||||
|
|
||||||
renderEditorBottomBar() {
|
renderEditorBottomBar() {
|
||||||
const { queryEditor: qe } = this.props;
|
const { queryEditor: qe } = this.props;
|
||||||
|
|
||||||
|
|
@ -630,6 +638,7 @@ class SqlEditor extends React.PureComponent {
|
||||||
)}
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledToolbar className="sql-toolbar" id="js-sql-toolbar">
|
<StyledToolbar className="sql-toolbar" id="js-sql-toolbar">
|
||||||
<div className="leftItems">
|
<div className="leftItems">
|
||||||
|
|
@ -693,7 +702,7 @@ class SqlEditor extends React.PureComponent {
|
||||||
<SaveQuery
|
<SaveQuery
|
||||||
query={qe}
|
query={qe}
|
||||||
defaultLabel={qe.title || qe.description}
|
defaultLabel={qe.title || qe.description}
|
||||||
onSave={this.props.actions.saveQuery}
|
onSave={this.saveQuery}
|
||||||
onUpdate={this.props.actions.updateSavedQuery}
|
onUpdate={this.props.actions.updateSavedQuery}
|
||||||
saveQueryWarning={this.props.saveQueryWarning}
|
saveQueryWarning={this.props.saveQueryWarning}
|
||||||
/>
|
/>
|
||||||
|
|
@ -809,6 +818,7 @@ function mapDispatchToProps(dispatch) {
|
||||||
queryEditorSetTemplateParams,
|
queryEditorSetTemplateParams,
|
||||||
runQuery,
|
runQuery,
|
||||||
saveQuery,
|
saveQuery,
|
||||||
|
addSavedQueryToTabState,
|
||||||
scheduleQuery,
|
scheduleQuery,
|
||||||
setActiveSouthPaneTab,
|
setActiveSouthPaneTab,
|
||||||
updateSavedQuery,
|
updateSavedQuery,
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ export default function getInitialState({
|
||||||
latestQueryId: activeTab.latest_query
|
latestQueryId: activeTab.latest_query
|
||||||
? activeTab.latest_query.id
|
? activeTab.latest_query.id
|
||||||
: null,
|
: null,
|
||||||
|
remoteId: activeTab.saved_query?.id,
|
||||||
autorun: activeTab.autorun,
|
autorun: activeTab.autorun,
|
||||||
templateParams: activeTab.template_params || undefined,
|
templateParams: activeTab.template_params || undefined,
|
||||||
dbId: activeTab.database_id,
|
dbId: activeTab.database_id,
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,11 @@ class SavedQuery(Model, AuditMixinNullable, ExtraJSONMixin, ImportExportMixin):
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return str(self.label)
|
return str(self.label)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pop_tab_link(self) -> Markup:
|
def pop_tab_link(self) -> Markup:
|
||||||
return Markup(
|
return Markup(
|
||||||
|
|
@ -285,6 +290,10 @@ class TabState(Model, AuditMixinNullable, ExtraJSONMixin):
|
||||||
template_params = Column(Text)
|
template_params = Column(Text)
|
||||||
hide_left_bar = Column(Boolean, default=False)
|
hide_left_bar = Column(Boolean, default=False)
|
||||||
|
|
||||||
|
# any saved queries that are associated with the Tab State
|
||||||
|
saved_query_id = Column(Integer, ForeignKey("saved_query.id"), nullable=True)
|
||||||
|
saved_query = relationship("SavedQuery", foreign_keys=[saved_query_id])
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
|
|
@ -300,6 +309,7 @@ class TabState(Model, AuditMixinNullable, ExtraJSONMixin):
|
||||||
"autorun": self.autorun,
|
"autorun": self.autorun,
|
||||||
"template_params": self.template_params,
|
"template_params": self.template_params,
|
||||||
"hide_left_bar": self.hide_left_bar,
|
"hide_left_bar": self.hide_left_bar,
|
||||||
|
"saved_query": self.saved_query.to_dict() if self.saved_query else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue