Skip to content

Commit

Permalink
feat(SqlLab): Change Save Dataset Button to Split Save Query Button IV (
Browse files Browse the repository at this point in the history
#20852)

* Moving entire split save btn PR

* Addressed review comments

* Remove arbitrary div from ErrorBoundary in Chart

* Added accidentally removed comment

* Fix act errors in SaveQuery tests

* Fix SaveDatasetActionButton test

* SaveDatasetModal test almost working

* SaveDatasetModal tests all passing

* Clean SaveDatasetModal test

* Fix create chart button and SaveDatasetModal text in SQL Lab

* Fix untitled dataset name on SaveDatasetModal in explore

* Fix styling on split save button
  • Loading branch information
lyndsiWilliams authored Aug 1, 2022
1 parent 3a11856 commit 8a04536
Show file tree
Hide file tree
Showing 25 changed files with 511 additions and 192 deletions.
28 changes: 13 additions & 15 deletions superset-frontend/src/SqlLab/actions/sqlLab.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const queryClientMapping = {
id: 'remoteId',
db_id: 'dbId',
client_id: 'id',
label: 'title',
label: 'name',
};
const queryServerMapping = invert(queryClientMapping);

Expand Down Expand Up @@ -541,7 +541,7 @@ export function cloneQueryToNewTab(query, autorun) {
qe => qe.id === tabHistory[tabHistory.length - 1],
);
const queryEditor = {
title: t('Copy of %s', sourceQueryEditor.title),
name: t('Copy of %s', sourceQueryEditor.name),
dbId: query.dbId ? query.dbId : null,
schema: query.schema ? query.schema : null,
autorun,
Expand Down Expand Up @@ -629,7 +629,7 @@ export function switchQueryEditor(queryEditor, displayLimit) {
const loadedQueryEditor = {
id: json.id.toString(),
loaded: true,
title: json.label,
name: json.label,
sql: json.sql,
selectedText: null,
latestQueryId: json.latest_query?.id,
Expand Down Expand Up @@ -834,24 +834,22 @@ export function queryEditorSetAutorun(queryEditor, autorun) {
};
}

export function queryEditorSetTitle(queryEditor, title) {
export function queryEditorSetTitle(queryEditor, name) {
return function (dispatch) {
const sync = isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE)
? SupersetClient.put({
endpoint: encodeURI(`/tabstateview/${queryEditor.id}`),
postPayload: { label: title },
postPayload: { label: name },
})
: Promise.resolve();

return sync
.then(() =>
dispatch({ type: QUERY_EDITOR_SET_TITLE, queryEditor, title }),
)
.then(() => dispatch({ type: QUERY_EDITOR_SET_TITLE, queryEditor, name }))
.catch(() =>
dispatch(
addDangerToast(
t(
'An error occurred while setting the tab title. Please contact your administrator.',
'An error occurred while setting the tab name. Please contact your administrator.',
),
),
),
Expand All @@ -873,7 +871,7 @@ export function saveQuery(query) {
query,
result: savedQuery,
});
dispatch(queryEditorSetTitle(query, query.title));
dispatch(queryEditorSetTitle(query, query.name));
return savedQuery;
})
.catch(() =>
Expand Down Expand Up @@ -908,7 +906,7 @@ export function updateSavedQuery(query) {
})
.then(() => {
dispatch(addSuccessToast(t('Your query was updated')));
dispatch(queryEditorSetTitle(query, query.title));
dispatch(queryEditorSetTitle(query, query.name));
})
.catch(() =>
dispatch(addDangerToast(t('Your query could not be updated'))),
Expand Down Expand Up @@ -965,7 +963,7 @@ export function queryEditorSetQueryLimit(queryEditor, queryLimit) {
dispatch(
addDangerToast(
t(
'An error occurred while setting the tab title. Please contact your administrator.',
'An error occurred while setting the tab name. Please contact your administrator.',
),
),
),
Expand Down Expand Up @@ -1264,7 +1262,7 @@ export function popStoredQuery(urlId) {
.then(({ json }) =>
dispatch(
addQueryEditor({
title: json.title ? json.title : t('Shared query'),
name: json.name ? json.name : t('Shared query'),
dbId: json.dbId ? parseInt(json.dbId, 10) : null,
schema: json.schema ? json.schema : null,
autorun: json.autorun ? json.autorun : false,
Expand Down Expand Up @@ -1302,7 +1300,7 @@ export function popQuery(queryId) {
dbId: queryData.database.id,
schema: queryData.schema,
sql: queryData.sql,
title: `Copy of ${queryData.tab_name}`,
name: `Copy of ${queryData.tab_name}`,
autorun: false,
};
return dispatch(addQueryEditor(queryEditorProps));
Expand All @@ -1318,7 +1316,7 @@ export function popDatasourceQuery(datasourceKey, sql) {
.then(({ json }) =>
dispatch(
addQueryEditor({
title: `Query ${json.name}`,
name: `Query ${json.name}`,
dbId: json.database.id,
schema: json.schema,
autorun: sql !== undefined,
Expand Down
16 changes: 8 additions & 8 deletions superset-frontend/src/SqlLab/actions/sqlLab.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ describe('async actions', () => {
latestQueryId: null,
selectedText: null,
sql: 'SELECT *\nFROM\nWHERE',
title: 'Untitled Query 1',
schemaOptions: [{ value: 'main', label: 'main', title: 'main' }],
name: 'Untitled Query 1',
schemaOptions: [{ value: 'main', label: 'main', name: 'main' }],
};

let dispatch;
Expand Down Expand Up @@ -290,7 +290,7 @@ describe('async actions', () => {
const state = {
sqlLab: {
tabHistory: [id],
queryEditors: [{ id, title: 'Dummy query editor' }],
queryEditors: [{ id, name: 'Dummy query editor' }],
},
};
const store = mockStore(state);
Expand Down Expand Up @@ -350,15 +350,15 @@ describe('async actions', () => {
const state = {
sqlLab: {
tabHistory: [id],
queryEditors: [{ id, title: 'Dummy query editor' }],
queryEditors: [{ id, name: 'Dummy query editor' }],
},
};
const store = mockStore(state);
const expectedActions = [
{
type: actions.ADD_QUERY_EDITOR,
queryEditor: {
title: 'Copy of Dummy query editor',
name: 'Copy of Dummy query editor',
dbId: 1,
schema: null,
autorun: true,
Expand Down Expand Up @@ -617,17 +617,17 @@ describe('async actions', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);

const title = 'title';
const name = 'name';
const store = mockStore({});
const expectedActions = [
{
type: actions.QUERY_EDITOR_SET_TITLE,
queryEditor,
title,
name,
},
];
return store
.dispatch(actions.queryEditorSetTitle(queryEditor, title))
.dispatch(actions.queryEditorSetTitle(queryEditor, name))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
Expand Down
7 changes: 2 additions & 5 deletions superset-frontend/src/SqlLab/components/ResultSet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export default class ResultSet extends React.PureComponent<
popSelectStar(tempSchema: string | null, tempTable: string) {
const qe = {
id: shortid.generate(),
title: tempTable,
name: tempTable,
autorun: false,
dbId: this.props.query.dbId,
sql: `SELECT * FROM ${tempSchema ? `${tempSchema}.` : ''}${tempTable}`,
Expand Down Expand Up @@ -281,11 +281,8 @@ export default class ResultSet extends React.PureComponent<
this.props.database?.allows_virtual_table_explore && (
<ExploreResultsButton
database={this.props.database}
onClick={() => this.setState({ showSaveDatasetModal: true })}
onClick={this.createExploreResultsOnClick}
/>
// In order to use the new workflow for a query powered chart, replace the
// above function with:
// onClick={this.createExploreResultsOnClick}
)}
{this.props.csv && (
<Button buttonSize="small" href={`/superset/csv/${query.id}`}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ import React, { useMemo } from 'react';
import { t, styled, useTheme } from '@superset-ui/core';

import { Menu } from 'src/components/Menu';
import Button, { ButtonProps } from 'src/components/Button';
import Button from 'src/components/Button';
import Icons from 'src/components/Icons';
import {
DropdownButton,
DropdownButtonProps,
} from 'src/components/DropdownButton';
import { DropdownButton } from 'src/components/DropdownButton';
import { detectOS } from 'src/utils/common';
import { QueryButtonProps } from 'src/SqlLab/types';

interface Props {
allowAsync: boolean;
Expand All @@ -38,8 +36,6 @@ interface Props {
overlayCreateAsMenu: typeof Menu | null;
}

type QueryButtonProps = DropdownButtonProps | ButtonProps;

const buildText = (
shouldShowStopButton: boolean,
selectedText: string | undefined,
Expand Down Expand Up @@ -80,7 +76,7 @@ const StyledButton = styled.span`
}
span[name='caret-down'] {
display: flex;
margin-right: ${({ theme }) => theme.gridUnit * -2}px;
margin-left: ${({ theme }) => theme.gridUnit * 1}px;
}
}
`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* 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.
*/
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { Menu } from 'src/components/Menu';
import SaveDatasetActionButton from 'src/SqlLab/components/SaveDatasetActionButton';

const overlayMenu = (
<Menu>
<Menu.Item>Save dataset</Menu.Item>
</Menu>
);

describe('SaveDatasetActionButton', () => {
it('renders a split save button', () => {
render(
<SaveDatasetActionButton
setShowSave={() => true}
overlayMenu={overlayMenu}
/>,
);

const saveBtn = screen.getByRole('button', { name: /save/i });
const caretBtn = screen.getByRole('button', { name: /caret-down/i });

expect(saveBtn).toBeVisible();
expect(caretBtn).toBeVisible();
});

it('renders a "save dataset" dropdown menu item when user clicks caret button', () => {
render(
<SaveDatasetActionButton
setShowSave={() => true}
overlayMenu={overlayMenu}
/>,
);

const caretBtn = screen.getByRole('button', { name: /caret-down/i });
userEvent.click(caretBtn);

const saveDatasetMenuItem = screen.getByText(/save dataset/i);

expect(saveDatasetMenuItem).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* 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.
*/
import React from 'react';
import { t, useTheme, styled } from '@superset-ui/core';
import Icons from 'src/components/Icons';
import { DropdownButton } from 'src/components/DropdownButton';
import Button from 'src/components/Button';
import { DropdownButtonProps } from 'antd/lib/dropdown';

interface Props {
setShowSave: (arg0: boolean) => void;
overlayMenu: JSX.Element | null;
}

export default function SaveDatasetActionButton({
setShowSave,
overlayMenu,
}: Props) {
const theme = useTheme();

const StyledDropdownButton = styled(
DropdownButton as React.FC<DropdownButtonProps>,
)`
&.ant-dropdown-button button.ant-btn.ant-btn-default {
&:first-of-type {
width: ${theme.gridUnit * 16}px;
}
font-weight: ${theme.gridUnit * 150};
background-color: ${theme.colors.primary.light4};
color: ${theme.colors.primary.dark1};
&:nth-child(2) {
&:before,
&:hover:before {
border-left: 2px solid ${theme.colors.primary.dark2};
}
}
}
span[name='caret-down'] {
margin-left: ${theme.gridUnit * 1}px;
color: ${theme.colors.primary.dark2};
}
`;

return !overlayMenu ? (
<Button
onClick={() => setShowSave(true)}
buttonStyle="primary"
css={{ width: theme.gridUnit * 25 }}
>
{t('Save')}
</Button>
) : (
<StyledDropdownButton
onClick={() => setShowSave(true)}
overlay={overlayMenu}
icon={
<Icons.CaretDown
iconColor={theme.colors.grayscale.light5}
name="caret-down"
/>
}
trigger={['click']}
>
{t('Save')}
</StyledDropdownButton>
);
}
Loading

0 comments on commit 8a04536

Please sign in to comment.