diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js b/superset-frontend/src/SqlLab/actions/sqlLab.js index 6a43821b5cbcc..1435e0d796e21 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.js @@ -112,7 +112,7 @@ const queryClientMapping = { id: 'remoteId', db_id: 'dbId', client_id: 'id', - label: 'title', + label: 'name', }; const queryServerMapping = invert(queryClientMapping); @@ -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, @@ -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, @@ -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.', ), ), ), @@ -873,7 +871,7 @@ export function saveQuery(query) { query, result: savedQuery, }); - dispatch(queryEditorSetTitle(query, query.title)); + dispatch(queryEditorSetTitle(query, query.name)); return savedQuery; }) .catch(() => @@ -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'))), @@ -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.', ), ), ), @@ -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, @@ -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)); @@ -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, diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.test.js b/superset-frontend/src/SqlLab/actions/sqlLab.test.js index f9e972d2a453e..1c4509b3a1ad4 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.test.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.test.js @@ -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; @@ -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); @@ -350,7 +350,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); @@ -358,7 +358,7 @@ describe('async actions', () => { { type: actions.ADD_QUERY_EDITOR, queryEditor: { - title: 'Copy of Dummy query editor', + name: 'Copy of Dummy query editor', dbId: 1, schema: null, autorun: true, @@ -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); diff --git a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx index 6e0e0d4317800..ebb8e4d3f030c 100644 --- a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx +++ b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx @@ -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}`, @@ -281,11 +281,8 @@ export default class ResultSet extends React.PureComponent< this.props.database?.allows_virtual_table_explore && ( 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 && ( + ) : ( + setShowSave(true)} + overlay={overlayMenu} + icon={ + + } + trigger={['click']} + > + {t('Save')} + + ); +} diff --git a/superset-frontend/src/SqlLab/components/SaveDatasetModal/SaveDatasetModal.test.tsx b/superset-frontend/src/SqlLab/components/SaveDatasetModal/SaveDatasetModal.test.tsx index b7f3ad8a8a42d..94017525f08d6 100644 --- a/superset-frontend/src/SqlLab/components/SaveDatasetModal/SaveDatasetModal.test.tsx +++ b/superset-frontend/src/SqlLab/components/SaveDatasetModal/SaveDatasetModal.test.tsx @@ -17,35 +17,12 @@ * under the License. */ import React from 'react'; -import { - ISaveableDatasource, - SaveDatasetModal, -} from 'src/SqlLab/components/SaveDatasetModal'; -import { render, screen } from 'spec/helpers/testing-library'; -import { DatasourceType } from '@superset-ui/core'; - -const testQuery: ISaveableDatasource = { - name: 'unimportant', - dbId: 1, - sql: 'SELECT *', - columns: [ - { - name: 'Column 1', - type: DatasourceType.Query, - is_dttm: false, - }, - { - name: 'Column 3', - type: DatasourceType.Query, - is_dttm: false, - }, - { - name: 'Column 2', - type: DatasourceType.Query, - is_dttm: true, - }, - ], -}; +import * as reactRedux from 'react-redux'; +import { render, screen, waitFor, within } from 'spec/helpers/testing-library'; +import userEvent from '@testing-library/user-event'; +import fetchMock from 'fetch-mock'; +import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal'; +import { user, testQuery, mockdatasets } from 'src/SqlLab/fixtures'; const mockedProps = { visible: true, @@ -55,8 +32,17 @@ const mockedProps = { datasource: testQuery, }; -describe('SaveDatasetModal RTL', () => { - it('renders a "Save as new" field', () => { +fetchMock.get('glob:*/api/v1/dataset?*', { + result: mockdatasets, + dataset_count: 3, +}); + +// Mock the user +const useSelectorMock = jest.spyOn(reactRedux, 'useSelector'); +beforeEach(() => useSelectorMock.mockClear()); + +describe('SaveDatasetModal', () => { + it('renders a "Save as new" field', async () => { render(, { useRedux: true }); const saveRadioBtn = screen.getByRole('radio', { @@ -73,7 +59,7 @@ describe('SaveDatasetModal RTL', () => { expect(inputFieldText).toBeVisible(); }); - it('renders an "Overwrite existing" field', () => { + it('renders an "Overwrite existing" field', async () => { render(, { useRedux: true }); const overwriteRadioBtn = screen.getByRole('radio', { @@ -89,15 +75,61 @@ describe('SaveDatasetModal RTL', () => { expect(placeholderText).toBeVisible(); }); - it('renders a save button', () => { + it('renders a close button', async () => { + render(, { useRedux: true }); + + expect(screen.getByRole('button', { name: /close/i })).toBeVisible(); + }); + + it('renders a save button when "Save as new" is selected', async () => { render(, { useRedux: true }); + // "Save as new" is selected when the modal opens by default expect(screen.getByRole('button', { name: /save/i })).toBeVisible(); }); - it('renders a close button', () => { + it('renders a back and overwrite button when "Overwrite existing" is selected', async () => { render(, { useRedux: true }); - expect(screen.getByRole('button', { name: /close/i })).toBeVisible(); + // Click the overwrite radio button to reveal the overwrite confirmation and back buttons + const overwriteRadioBtn = screen.getByRole('radio', { + name: /overwrite existing/i, + }); + userEvent.click(overwriteRadioBtn); + + expect(screen.getByRole('button', { name: /back/i })).toBeVisible(); + expect(screen.getByRole('button', { name: /overwrite/i })).toBeVisible(); + }); + + it('renders the overwrite button as disabled until an existing dataset is selected', async () => { + useSelectorMock.mockReturnValue({ ...user }); + render(, { useRedux: true }); + + // Click the overwrite radio button + const overwriteRadioBtn = screen.getByRole('radio', { + name: /overwrite existing/i, + }); + await waitFor(async () => { + userEvent.click(overwriteRadioBtn); + }); + + // Overwrite confirmation button should be disabled at this point + const overwriteConfirmationBtn = screen.getByRole('button', { + name: /overwrite/i, + }); + expect(overwriteConfirmationBtn).toBeDisabled(); + + // Click the select component + const select = screen.getByRole('combobox', { name: /existing dataset/i })!; + await waitFor(async () => userEvent.click(select)); + + // Select the first "existing dataset" from the listbox + const option = within( + document.querySelector('.rc-virtual-list')!, + ).getByText('coolest table 0')!; + userEvent.click(option); + + // Overwrite button should now be enabled + expect(overwriteConfirmationBtn).toBeEnabled(); }); }); diff --git a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx index da5968cc23688..12922ef79d26a 100644 --- a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx +++ b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx @@ -186,6 +186,11 @@ export const SaveDatasetModal: FunctionComponent = ({ ...(formData || {}), }; const handleOverwriteDataset = async () => { + // if user wants to overwrite a dataset we need to prompt them + if (!shouldOverwriteDataset) { + setShouldOverwriteDataset(true); + return; + } const [, key] = await Promise.all([ updateDataset( datasource?.dbId, @@ -258,12 +263,6 @@ export const SaveDatasetModal: FunctionComponent = ({ ); const handleSaveInDataset = () => { - // if user wants to overwrite a dataset we need to prompt them - if (newOrOverwrite === DatasetRadioState.OVERWRITE_DATASET) { - setShouldOverwriteDataset(true); - return; - } - const selectedColumns = datasource?.columns ?? []; // The filters param is only used to test jinja templates. @@ -347,7 +346,7 @@ export const SaveDatasetModal: FunctionComponent = ({ onHide={onHide} footer={ <> - {!shouldOverwriteDataset && ( + {newOrOverwrite === DatasetRadioState.SAVE_NEW && ( )} - {shouldOverwriteDataset && ( + {newOrOverwrite === DatasetRadioState.OVERWRITE_DATASET && ( <> + + setShowSaveDatasetModal(false)} + buttonTextOnSave={t('Save & Explore')} + buttonTextOnOverwrite={t('Overwrite & Explore')} + datasource={query} + /> ( const defaultProps = { queryEditor: { dbId: 0, - title: 'query title', + name: 'query title', schema: 'query_schema', autorun: false, sql: 'SELECT * FROM ...', diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx index 5f473c642fd31..c521cb5dc75d9 100644 --- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx +++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx @@ -49,8 +49,8 @@ function ShareSqlLabQuery({ const theme = useTheme(); const getCopyUrlForKvStore = (callback: Function) => { - const { dbId, title, schema, autorun, sql } = queryEditor; - const sharedQuery = { dbId, title, schema, autorun, sql }; + const { dbId, name, schema, autorun, sql } = queryEditor; + const sharedQuery = { dbId, name, schema, autorun, sql }; return storeQuery(sharedQuery) .then(shortUrl => { diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx index d40ca65f2f665..346e53fbdb24e 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx @@ -345,10 +345,10 @@ class SqlEditor extends React.PureComponent { key: userOS === 'Windows' ? 'ctrl+q' : 'ctrl+t', descr: t('New tab'), func: () => { - const title = newQueryTabName(this.props.queryEditors || []); + const name = newQueryTabName(this.props.queryEditors || []); this.props.addQueryEditor({ ...this.props.queryEditor, - title, + name, }); }, }, @@ -463,7 +463,7 @@ class SqlEditor extends React.PureComponent { dbId: qe.dbId, sql: qe.selectedText ? qe.selectedText : qe.sql, sqlEditorId: qe.id, - tab: qe.title, + tab: qe.name, schema: qe.schema, tempTable: ctas ? this.state.ctas : '', templateParams: qe.templateParams, @@ -584,7 +584,7 @@ class SqlEditor extends React.PureComponent { {scheduledQueriesConf && ( diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx index 9f8e5bcf1ae25..5de36bc99a455 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx @@ -55,7 +55,7 @@ describe('TabbedSqlEditors', () => { schema: null, selectedText: null, sql: 'SELECT ds...', - title: 'Untitled Query', + name: 'Untitled Query', }, ]; const queries = { @@ -177,7 +177,7 @@ describe('TabbedSqlEditors', () => { wrapper.instance().newQueryEditor(); expect( - wrapper.instance().props.actions.addQueryEditor.getCall(0).args[0].title, + wrapper.instance().props.actions.addQueryEditor.getCall(0).args[0].name, ).toContain('Untitled Query'); }); it('should properly increment query tab name', () => { @@ -186,7 +186,7 @@ describe('TabbedSqlEditors', () => { wrapper.instance().newQueryEditor(); expect( - wrapper.instance().props.actions.addQueryEditor.getCall(0).args[0].title, + wrapper.instance().props.actions.addQueryEditor.getCall(0).args[0].name, ).toContain('Untitled Query 2'); }); it('should duplicate query editor', () => { diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx index 494ef9cba0ef7..c97080c6e95a5 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx @@ -167,7 +167,7 @@ class TabbedSqlEditors extends React.PureComponent { } } const newQueryEditor = { - title: query.title, + name: query.name, dbId, schema: query.schema, autorun: query.autorun, @@ -266,7 +266,7 @@ class TabbedSqlEditors extends React.PureComponent { const newTitle = newQueryTabName(this.props.queryEditors || []); const qe = { - title: newTitle, + name: newTitle, dbId: activeQueryEditor && activeQueryEditor.dbId ? activeQueryEditor.dbId @@ -376,7 +376,7 @@ class TabbedSqlEditors extends React.PureComponent { const tabHeader = ( - {qe.title} {' '} + {qe.name} {' '} ); return ( diff --git a/superset-frontend/src/SqlLab/fixtures.ts b/superset-frontend/src/SqlLab/fixtures.ts index 58b21edd9cf73..ea0fbd1bb8d1e 100644 --- a/superset-frontend/src/SqlLab/fixtures.ts +++ b/superset-frontend/src/SqlLab/fixtures.ts @@ -20,6 +20,7 @@ import sinon from 'sinon'; import * as actions from 'src/SqlLab/actions/sqlLab'; import { ColumnKeyTypeType } from 'src/SqlLab/components/ColumnElement'; import { DatasourceType, QueryResponse, QueryState } from '@superset-ui/core'; +import { ISaveableDatasource } from 'src/SqlLab/components/SaveDatasetModal'; export const mockedActions = sinon.stub({ ...actions }); @@ -181,12 +182,12 @@ export const defaultQueryEditor = { latestQueryId: null, selectedText: null, sql: 'SELECT *\nFROM\nWHERE', - title: 'Untitled Query 1', + name: 'Untitled Query 1', schemaOptions: [ { value: 'main', label: 'main', - title: 'main', + name: 'main', }, ], }; @@ -670,3 +671,40 @@ export const query = { ctas: false, cached: false, }; + +export const testQuery: ISaveableDatasource = { + name: 'unimportant', + dbId: 1, + sql: 'SELECT *', + columns: [ + { + name: 'Column 1', + type: DatasourceType.Query, + is_dttm: false, + }, + { + name: 'Column 3', + type: DatasourceType.Query, + is_dttm: false, + }, + { + name: 'Column 2', + type: DatasourceType.Query, + is_dttm: true, + }, + ], +}; + +export const mockdatasets = [...new Array(3)].map((_, i) => ({ + changed_by_name: 'user', + kind: i === 0 ? 'virtual' : 'physical', // ensure there is 1 virtual + changed_by_url: 'changed_by_url', + changed_by: 'user', + changed_on: new Date().toISOString(), + database_name: `db ${i}`, + explore_url: `/explore/?dataset_type=table&dataset_id=${i}`, + id: i, + schema: `schema ${i}`, + table_name: `coolest table ${i}`, + owners: [{ username: 'admin', userId: 1 }], +})); diff --git a/superset-frontend/src/SqlLab/reducers/getInitialState.js b/superset-frontend/src/SqlLab/reducers/getInitialState.js index d5f02029d0ccf..21c367844d7fa 100644 --- a/superset-frontend/src/SqlLab/reducers/getInitialState.js +++ b/superset-frontend/src/SqlLab/reducers/getInitialState.js @@ -41,7 +41,7 @@ export default function getInitialState({ const defaultQueryEditor = { id: null, loaded: true, - title: t('Untitled query'), + name: t('Untitled query'), sql: 'SELECT *\nFROM\nWHERE', selectedText: null, latestQueryId: null, @@ -73,7 +73,7 @@ export default function getInitialState({ queryEditor = { id: id.toString(), loaded: true, - title: activeTab.label, + name: activeTab.label, sql: activeTab.sql || undefined, selectedText: undefined, latestQueryId: activeTab.latest_query @@ -99,7 +99,7 @@ export default function getInitialState({ ...defaultQueryEditor, id: id.toString(), loaded: false, - title: label, + name: label, }; } queryEditors.push(queryEditor); diff --git a/superset-frontend/src/SqlLab/reducers/sqlLab.js b/superset-frontend/src/SqlLab/reducers/sqlLab.js index a9cef50870540..f87c8ce9c1043 100644 --- a/superset-frontend/src/SqlLab/reducers/sqlLab.js +++ b/superset-frontend/src/SqlLab/reducers/sqlLab.js @@ -48,7 +48,7 @@ export default function sqlLabReducer(state = {}, action) { existing, { remoteId: result.remoteId, - title: query.title, + name: query.name, }, 'id', ); @@ -71,7 +71,7 @@ export default function sqlLabReducer(state = {}, action) { ); const qe = { remoteId: progenitor.remoteId, - title: t('Copy of %s', progenitor.title), + name: t('Copy of %s', progenitor.name), dbId: action.query.dbId ? action.query.dbId : null, schema: action.query.schema ? action.query.schema : null, autorun: true, @@ -467,7 +467,7 @@ export default function sqlLabReducer(state = {}, action) { }, [actions.QUERY_EDITOR_SET_TITLE]() { return alterInArr(state, 'queryEditors', action.queryEditor, { - title: action.title, + name: action.name, }); }, [actions.QUERY_EDITOR_SET_SQL]() { diff --git a/superset-frontend/src/SqlLab/reducers/sqlLab.test.js b/superset-frontend/src/SqlLab/reducers/sqlLab.test.js index c986fc5ac06f1..4c92e1324740f 100644 --- a/superset-frontend/src/SqlLab/reducers/sqlLab.test.js +++ b/superset-frontend/src/SqlLab/reducers/sqlLab.test.js @@ -96,14 +96,14 @@ describe('sqlLabReducer', () => { expect(newState.queryEditors[1].autorun).toBe(true); }); it('should not fail while setting title', () => { - const title = 'a new title'; + const title = 'Untitled Query 1'; const action = { type: actions.QUERY_EDITOR_SET_TITLE, queryEditor: qe, title, }; newState = sqlLabReducer(newState, action); - expect(newState.queryEditors[1].title).toBe(title); + expect(newState.queryEditors[0].name).toBe(title); }); it('should not fail while setting Sql', () => { const sql = 'SELECT nothing from dev_null'; diff --git a/superset-frontend/src/SqlLab/types.ts b/superset-frontend/src/SqlLab/types.ts index 0d54f97476676..8bb3e73d28870 100644 --- a/superset-frontend/src/SqlLab/types.ts +++ b/superset-frontend/src/SqlLab/types.ts @@ -21,6 +21,10 @@ import { SupersetError } from 'src/components/ErrorMessage/types'; import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; import { ToastType } from 'src/components/MessageToasts/types'; import { RootState } from 'src/dashboard/types'; +import { DropdownButtonProps } from 'src/components/DropdownButton'; +import { ButtonProps } from 'src/components/Button'; + +export type QueryButtonProps = DropdownButtonProps | ButtonProps; // Object as Dictionary (associative array) with Query id as the key and type Query as the value export type QueryDictionary = { @@ -29,7 +33,7 @@ export type QueryDictionary = { export interface QueryEditor { dbId?: number; - title: string; + name: string; schema: string; autorun: boolean; sql: string; diff --git a/superset-frontend/src/SqlLab/utils/newQueryTabName.test.ts b/superset-frontend/src/SqlLab/utils/newQueryTabName.test.ts index d0d98c3cd5e29..6f2af5ebb7376 100644 --- a/superset-frontend/src/SqlLab/utils/newQueryTabName.test.ts +++ b/superset-frontend/src/SqlLab/utils/newQueryTabName.test.ts @@ -36,8 +36,8 @@ describe('newQueryTabName', () => { it('should return next available number if there are unsaved editors', () => { const untitledQueryText = 'Untitled Query'; const unsavedEditors = [ - { ...emptyEditor, title: `${untitledQueryText} 1` }, - { ...emptyEditor, title: `${untitledQueryText} 2` }, + { ...emptyEditor, name: `${untitledQueryText} 1` }, + { ...emptyEditor, name: `${untitledQueryText} 2` }, ]; const nextTitle = newQueryTabName(unsavedEditors); diff --git a/superset-frontend/src/SqlLab/utils/newQueryTabName.ts b/superset-frontend/src/SqlLab/utils/newQueryTabName.ts index 3815226cd4ba5..c068fdd3d5cbb 100644 --- a/superset-frontend/src/SqlLab/utils/newQueryTabName.ts +++ b/superset-frontend/src/SqlLab/utils/newQueryTabName.ts @@ -31,10 +31,10 @@ export const newQueryTabName = ( if (queryEditors.length > 0) { const mappedUntitled = queryEditors.filter(qe => - qe.title.match(untitledQueryRegex), + qe.name.match(untitledQueryRegex), ); const untitledQueryNumbers = mappedUntitled.map( - qe => +qe.title.replace(untitledQuery, ''), + qe => +qe.name.replace(untitledQuery, ''), ); if (untitledQueryNumbers.length > 0) { // When there are query tabs open, and at least one is called "Untitled Query #" diff --git a/superset-frontend/src/components/Chart/Chart.jsx b/superset-frontend/src/components/Chart/Chart.jsx index 7d02bf3452e0d..893665ab15c7a 100644 --- a/superset-frontend/src/components/Chart/Chart.jsx +++ b/superset-frontend/src/components/Chart/Chart.jsx @@ -236,7 +236,6 @@ class Chart extends React.PureComponent { link={queryResponse ? queryResponse.link : null} source={dashboardId ? 'dashboard' : 'explore'} stackTrace={chartStackTrace} - errorMitigationFunction={this.toggleSaveDatasetModal} /> ); } diff --git a/superset-frontend/src/components/DropdownButton/index.tsx b/superset-frontend/src/components/DropdownButton/index.tsx index f2a223a49fd8d..c6293f66a3fbd 100644 --- a/superset-frontend/src/components/DropdownButton/index.tsx +++ b/superset-frontend/src/components/DropdownButton/index.tsx @@ -52,10 +52,9 @@ const StyledDropdownButton = styled.div` border-left: 1px solid ${({ theme }) => theme.colors.grayscale.light5}; content: ''; display: block; - height: 23px; + height: ${({ theme }) => theme.gridUnit * 8}px; margin: 0; position: absolute; - top: ${({ theme }) => theme.gridUnit * 0.75}px; width: ${({ theme }) => theme.gridUnit * 0.25}px; } diff --git a/superset-frontend/src/utils/datasourceUtils.js b/superset-frontend/src/utils/datasourceUtils.js index 4a469e51095a1..edfc02ec29c1a 100644 --- a/superset-frontend/src/utils/datasourceUtils.js +++ b/superset-frontend/src/utils/datasourceUtils.js @@ -18,7 +18,7 @@ */ export const getDatasourceAsSaveableDataset = source => ({ columns: source.columns, - name: source?.datasource_name || 'Untitled', + name: source?.datasource_name || source?.name || 'Untitled', dbId: source.database.id, sql: source?.sql || '', schema: source?.schema,