Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(sqllab): Allow opening of SQL Lab in new browser tab #25582

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions superset-frontend/src/components/Chart/chartAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { getClientErrorObject } from 'src/utils/getClientErrorObject';
import { allowCrossDomain as domainShardingEnabled } from 'src/utils/hostNamesConfig';
import { updateDataMask } from 'src/dataMask/actions';
import { waitForAsyncData } from 'src/middleware/asyncEvent';
import { safeStringify } from 'src/utils/safeStringify';

export const CHART_UPDATE_STARTED = 'CHART_UPDATE_STARTED';
export function chartUpdateStarted(queryController, latestQueryFormData, key) {
Expand Down Expand Up @@ -579,12 +580,18 @@ export function redirectSQLLab(formData, history) {
datasourceKey: formData.datasource,
sql: json.result[0].query,
};
history.push({
pathname: redirectUrl,
state: {
requestedQuery: payload,
},
});
if (history) {
history.push({
pathname: redirectUrl,
state: {
requestedQuery: payload,
},
});
} else {
SupersetClient.postForm(redirectUrl, {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

form_data: safeStringify(payload),
});
}
})
.catch(() =>
dispatch(addDangerToast(t('An error occurred while loading the SQL'))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ export const ExploreChartHeader = ({
const { redirectSQLLab } = actions;

const redirectToSQLLab = useCallback(
formData => {
redirectSQLLab(formData, history);
(formData, openNewWindow = false) => {
redirectSQLLab(formData, !openNewWindow && history);
},
[redirectSQLLab, history],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@

import React from 'react';
import PropTypes from 'prop-types';
import { DatasourceType, styled, t, withTheme } from '@superset-ui/core';
import {
DatasourceType,
SupersetClient,
styled,
t,
withTheme,
} from '@superset-ui/core';
import { getTemporalColumns } from '@superset-ui/chart-controls';
import { getUrlParam } from 'src/utils/urlUtils';
import { AntdDropdown } from 'src/components';
Expand All @@ -44,6 +50,7 @@ import ModalTrigger from 'src/components/ModalTrigger';
import ViewQueryModalFooter from 'src/explore/components/controls/ViewQueryModalFooter';
import ViewQuery from 'src/explore/components/controls/ViewQuery';
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
import { safeStringify } from 'src/utils/safeStringify';
import { isString } from 'lodash';
import { Link } from 'react-router-dom';

Expand Down Expand Up @@ -120,6 +127,7 @@ const Styles = styled.div`
`;

const CHANGE_DATASET = 'change_dataset';
const VIEW_IN_SQL_LAB = 'view_in_sql_lab';
const EDIT_DATASET = 'edit_dataset';
const QUERY_PREVIEW = 'query_preview';
const SAVE_AS_DATASET = 'save_as_dataset';
Expand Down Expand Up @@ -155,6 +163,14 @@ export const getDatasourceTitle = datasource => {
return datasource?.name || '';
};

const preventRouterLinkWhileMetaClicked = evt => {
if (evt.metaKey) {
evt.preventDefault();
} else {
evt.stopPropagation();
}
};

class DatasourceControl extends React.PureComponent {
constructor(props) {
super(props);
Expand Down Expand Up @@ -231,6 +247,19 @@ class DatasourceControl extends React.PureComponent {
this.toggleEditDatasourceModal();
break;

case VIEW_IN_SQL_LAB:
{
const { datasource } = this.props;
const payload = {
datasourceKey: `${datasource.id}__${datasource.type}`,
sql: datasource.sql,
};
SupersetClient.postForm('/sqllab/', {
form_data: safeStringify(payload),
});
}
break;

case SAVE_AS_DATASET:
this.toggleSaveDatasetModal();
break;
Expand Down Expand Up @@ -294,12 +323,13 @@ class DatasourceControl extends React.PureComponent {
)}
<Menu.Item key={CHANGE_DATASET}>{t('Swap dataset')}</Menu.Item>
{!isMissingDatasource && canAccessSqlLab && (
<Menu.Item>
<Menu.Item key={VIEW_IN_SQL_LAB}>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<Link
to={{
pathname: '/sqllab',
state: { requestedQuery },
}}
onClick={preventRouterLinkWhileMetaClicked}
>
{t('View in SQL Lab')}
</Link>
Expand Down Expand Up @@ -333,12 +363,13 @@ class DatasourceControl extends React.PureComponent {
/>
</Menu.Item>
{canAccessSqlLab && (
<Menu.Item>
<Menu.Item key={VIEW_IN_SQL_LAB}>
<Link
to={{
pathname: '/sqllab',
state: { requestedQuery },
}}
onClick={preventRouterLinkWhileMetaClicked}
>
{t('View in SQL Lab')}
</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
import React from 'react';
import { isObject } from 'lodash';
import { t } from '@superset-ui/core';
import { t, SupersetClient } from '@superset-ui/core';
import Button from 'src/components/Button';
import { useHistory } from 'react-router-dom';

Expand All @@ -44,24 +44,33 @@ const ViewQueryModalFooter: React.FC<ViewQueryModalFooterProps> = (props: {
datasource: SimpleDataSource;
}) => {
const history = useHistory();
const viewInSQLLab = (id: string, type: string, sql: string) => {
const viewInSQLLab = (
openInNewWindow: boolean,
id: string,
type: string,
sql: string,
) => {
const payload = {
datasourceKey: `${id}__${type}`,
sql,
};
history.push({
pathname: '/sqllab',
state: {
requestedQuery: payload,
},
});
if (openInNewWindow) {
SupersetClient.postForm('/sqllab/', payload);
} else {
history.push({
pathname: '/sqllab',
state: {
requestedQuery: payload,
},
});
}
};

const openSQL = () => {
const openSQL = (openInNewWindow: boolean) => {
const { datasource } = props;
if (isObject(datasource)) {
const { id, type, sql } = datasource;
viewInSQLLab(id, type, sql);
viewInSQLLab(openInNewWindow, id, type, sql);
}
};
return (
Expand All @@ -74,7 +83,9 @@ const ViewQueryModalFooter: React.FC<ViewQueryModalFooterProps> = (props: {
>
{SAVE_AS_DATASET}
</Button>
<Button onClick={() => openSQL()}>{OPEN_IN_SQL_LAB}</Button>
<Button onClick={({ metaKey }) => openSQL(Boolean(metaKey))}>
{OPEN_IN_SQL_LAB}
</Button>
<Button
buttonStyle="primary"
onClick={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ export const useExploreAdditionalActionsMenu = (
setIsDropdownVisible(false);
break;
case MENU_KEYS.RUN_IN_SQL_LAB:
onOpenInEditor(latestQueryFormData);
onOpenInEditor(latestQueryFormData, domEvent.metaKey);
setIsDropdownVisible(false);
break;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ describe('SavedQueryPreviewModal', () => {

it('handle open in sql lab', async () => {
act(() => {
wrapper.find('[data-test="open-in-sql-lab"]').first().props().onClick();
wrapper.find('[data-test="open-in-sql-lab"]').first().props().onClick({});
});
expect(mockedProps.openInSqlLab).toHaveBeenCalled();
expect(mockedProps.openInSqlLab.mock.calls[0][0]).toEqual(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ type SavedQueryObject = {
interface SavedQueryPreviewModalProps extends ToastProps {
fetchData: (id: number) => {};
onHide: () => void;
openInSqlLab: (id: number) => {};
openInSqlLab: (id: number, openInNewWindow: boolean) => {};
queries: Array<SavedQueryObject>;
savedQuery: SavedQueryObject;
show: boolean;
Expand Down Expand Up @@ -117,7 +117,9 @@ const SavedQueryPreviewModal: FunctionComponent<SavedQueryPreviewModalProps> =
data-test="open-in-sql-lab"
key="open-in-sql-lab"
buttonStyle="primary"
onClick={() => openInSqlLab(savedQuery.id)}
onClick={({ metaKey }) =>
openInSqlLab(savedQuery.id, Boolean(metaKey))
}
>
{t('Open in SQL Lab')}
</Button>
Expand Down
11 changes: 8 additions & 3 deletions superset-frontend/src/pages/SavedQueryList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,12 @@ function SavedQueryList({
menuData.buttons = subMenuButtons;

// Action methods
const openInSqlLab = (id: number) => {
history.push(`/sqllab?savedQueryId=${id}`);
const openInSqlLab = (id: number, openInNewWindow: boolean) => {
if (openInNewWindow) {
window.open(`/sqllab?savedQueryId=${id}`);
} else {
history.push(`/sqllab?savedQueryId=${id}`);
}
};

const copyQueryLink = useCallback(
Expand Down Expand Up @@ -389,7 +393,8 @@ function SavedQueryList({
const handlePreview = () => {
handleSavedQueryPreview(original.id);
};
const handleEdit = () => openInSqlLab(original.id);
const handleEdit = ({ metaKey }: React.MouseEvent) =>
openInSqlLab(original.id, Boolean(metaKey));
const handleCopy = () => copyQueryLink(original.id);
const handleExport = () => handleBulkSavedQueryExport([original]);
const handleDelete = () => setQueryCurrentlyDeleting(original);
Expand Down
5 changes: 4 additions & 1 deletion superset/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,10 +293,13 @@ def json_response(obj: Any, status: int = 200) -> FlaskResponse:
mimetype="application/json",
)

def render_app_template(self) -> FlaskResponse:
def render_app_template(
self, extra_bootstrap_data: Optional[dict[str, Any]] = None
) -> FlaskResponse:
payload = {
"user": bootstrap_user_data(g.user, include_perms=True),
"common": common_bootstrap_payload(g.user),
**(extra_bootstrap_data or {}),
}
return self.render_template(
"superset/spa.html",
Expand Down
12 changes: 10 additions & 2 deletions superset/views/sqllab.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import contextlib

import simplejson as json
from flask import request
from flask_appbuilder import permission_name
from flask_appbuilder.api import expose
from flask_appbuilder.security.decorators import has_access
Expand All @@ -31,12 +35,16 @@ class SqllabView(BaseSupersetView):

method_permission_name = MODEL_API_RW_METHOD_PERMISSION_MAP

@expose("/")
@expose("/", methods=["GET", "POST"])
@has_access
@permission_name("read")
@event_logger.log_this
def root(self) -> FlaskResponse:
return self.render_app_template()
payload = {}
if form_data := request.form.get("form_data"):
with contextlib.suppress(json.JSONDecodeError):
payload["requested_query"] = json.loads(form_data)
return self.render_app_template(payload)
Comment on lines +44 to +47
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


@expose("/history/", methods=("GET",))
@has_access
Expand Down