diff --git a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx index 59a6a5a118c1..5b2cae174166 100644 --- a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx +++ b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx @@ -21,7 +21,11 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { render } from 'spec/helpers/testing-library'; import { Store } from 'redux'; -import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures'; +import { + initialState, + defaultQueryEditor, + extraQueryEditor1, +} from 'src/SqlLab/fixtures'; import EstimateQueryCostButton, { EstimateQueryCostButtonProps, @@ -43,7 +47,7 @@ jest.mock('src/components/Select/AsyncSelect', () => () => ( const setup = (props: Partial, store?: Store) => render( , @@ -61,12 +65,8 @@ describe('EstimateQueryCostButton', () => { }); it('renders label for selected query', async () => { - const queryEditorWithSelectedText = { - ...defaultQueryEditor, - selectedText: 'SELECT', - }; const { queryByText } = setup( - { queryEditor: queryEditorWithSelectedText }, + { queryEditorId: extraQueryEditor1.id }, mockStore(initialState), ); diff --git a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx index dc2c23c40626..c0c92e3a5549 100644 --- a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/index.tsx @@ -17,43 +17,37 @@ * under the License. */ import React, { useMemo } from 'react'; -import Alert from 'src/components/Alert'; +import { useSelector } from 'react-redux'; import { t } from '@superset-ui/core'; + +import Alert from 'src/components/Alert'; import TableView from 'src/components/TableView'; import Button from 'src/components/Button'; import Loading from 'src/components/Loading'; import ModalTrigger from 'src/components/ModalTrigger'; import { EmptyWrapperType } from 'src/components/TableView/TableView'; -import { - SqlLabRootState, - QueryCostEstimate, - QueryEditor, -} from 'src/SqlLab/types'; -import { getUpToDateQuery } from 'src/SqlLab/actions/sqlLab'; -import { useSelector } from 'react-redux'; +import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; +import { SqlLabRootState, QueryCostEstimate } from 'src/SqlLab/types'; export interface EstimateQueryCostButtonProps { getEstimate: Function; - queryEditor: QueryEditor; + queryEditorId: string; tooltip?: string; disabled?: boolean; } const EstimateQueryCostButton = ({ getEstimate, - queryEditor, + queryEditorId, tooltip = '', disabled = false, }: EstimateQueryCostButtonProps) => { const queryCostEstimate = useSelector< SqlLabRootState, QueryCostEstimate | undefined - >(state => state.sqlLab.queryCostEstimates?.[queryEditor.id]); - const selectedText = useSelector( - rootState => - (getUpToDateQuery(rootState, queryEditor) as unknown as QueryEditor) - .selectedText, - ); + >(state => state.sqlLab.queryCostEstimates?.[queryEditorId]); + + const { selectedText } = useQueryEditor(queryEditorId, ['selectedText']); const { cost } = queryCostEstimate || {}; const tableData = useMemo(() => (Array.isArray(cost) ? cost : []), [cost]); const columns = useMemo( diff --git a/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx b/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx index 886e139a98e5..9b45e4b39718 100644 --- a/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx +++ b/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx @@ -19,6 +19,7 @@ import React from 'react'; import { useDispatch } from 'react-redux'; import { styled, useTheme } from '@superset-ui/core'; + import { AntdDropdown } from 'src/components'; import { Menu } from 'src/components/Menu'; import Icons from 'src/components/Icons'; @@ -83,12 +84,13 @@ const QueryLimitSelect = ({ maxRow, defaultQueryLimit, }: QueryLimitSelectProps) => { + const theme = useTheme(); + const dispatch = useDispatch(); + const queryEditor = useQueryEditor(queryEditorId, ['id', 'queryLimit']); const queryLimit = queryEditor.queryLimit || defaultQueryLimit; - const dispatch = useDispatch(); const setQueryLimit = (updatedQueryLimit: number) => dispatch(queryEditorSetQueryLimit(queryEditor, updatedQueryLimit)); - const theme = useTheme(); return ( diff --git a/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx b/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx index 7062ad694a2e..276f9b7d1967 100644 --- a/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx +++ b/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx @@ -24,7 +24,7 @@ import { Store } from 'redux'; import { render, fireEvent, waitFor } from 'spec/helpers/testing-library'; import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures'; import RunQueryActionButton, { - Props, + RunQueryActionButtonProps, } from 'src/SqlLab/components/RunQueryActionButton'; const middlewares = [thunk]; @@ -51,7 +51,7 @@ const defaultProps = { overlayCreateAsMenu: null, }; -const setup = (props?: Partial, store?: Store) => +const setup = (props?: Partial, store?: Store) => render(, { useRedux: true, ...(store && { store }), diff --git a/superset-frontend/src/SqlLab/components/RunQueryActionButton/index.tsx b/superset-frontend/src/SqlLab/components/RunQueryActionButton/index.tsx index 94a1bcde50f2..9a94fb81c3c9 100644 --- a/superset-frontend/src/SqlLab/components/RunQueryActionButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/RunQueryActionButton/index.tsx @@ -27,7 +27,7 @@ import { detectOS } from 'src/utils/common'; import { QueryButtonProps } from 'src/SqlLab/types'; import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; -export interface Props { +export interface RunQueryActionButtonProps { queryEditorId: string; allowAsync: boolean; queryState?: string; @@ -81,14 +81,14 @@ const StyledButton = styled.span` } `; -const RunQueryActionButton: React.FC = ({ +const RunQueryActionButton = ({ allowAsync = false, queryEditorId, queryState, overlayCreateAsMenu, runQuery, stopQuery, -}) => { +}: RunQueryActionButtonProps) => { const theme = useTheme(); const userOS = detectOS(); diff --git a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx index 769b1b4606d4..f05c00818957 100644 --- a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx +++ b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx @@ -61,14 +61,14 @@ const Styles = styled.span` } `; -export default function SaveQuery({ +const SaveQuery = ({ queryEditorId, onSave = () => {}, onUpdate, saveQueryWarning = null, database, columns, -}: SaveQueryProps) { +}: SaveQueryProps) => { const queryEditor = useQueryEditor(queryEditorId, [ 'autorun', 'name', @@ -230,4 +230,6 @@ export default function SaveQuery({ ); -} +}; + +export default SaveQuery; diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx index a7d88483cacb..73a774964678 100644 --- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx +++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx @@ -27,7 +27,7 @@ import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; -interface ShareSqlLabQueryPropTypes { +interface ShareSqlLabQueryProps { queryEditorId: string; addDangerToast: (msg: string) => void; } @@ -42,10 +42,10 @@ const StyledIcon = styled(Icons.Link)` } `; -function ShareSqlLabQuery({ +const ShareSqlLabQuery = ({ queryEditorId, addDangerToast, -}: ShareSqlLabQueryPropTypes) { +}: ShareSqlLabQueryProps) => { const theme = useTheme(); const { dbId, name, schema, autorun, sql, remoteId, templateParams } = @@ -126,6 +126,6 @@ function ShareSqlLabQuery({ )} ); -} +}; export default withToasts(ShareSqlLabQuery); diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx index ad1dcc815834..494d25b3d01c 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx @@ -467,7 +467,7 @@ const SqlEditor = ({ onChange={params => { dispatch(queryEditorSetTemplateParams(qe, params)); }} - queryEditor={qe} + queryEditorId={qe.id} /> )} @@ -543,7 +543,7 @@ const SqlEditor = ({ @@ -657,9 +657,8 @@ const SqlEditor = ({ > setShowEmptyState(bool)} /> diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx index 5f9b140c12fa..c2cf1b5a785f 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx @@ -19,23 +19,17 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import fetchMock from 'fetch-mock'; -import { render, screen, act } from 'spec/helpers/testing-library'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event'; import { Provider } from 'react-redux'; import '@testing-library/jest-dom/extend-expect'; import thunk from 'redux-thunk'; import SqlEditorLeftBar from 'src/SqlLab/components/SqlEditorLeftBar'; -import { - table, - initialState, - defaultQueryEditor, - mockedActions, -} from 'src/SqlLab/fixtures'; +import { table, initialState, defaultQueryEditor } from 'src/SqlLab/fixtures'; const mockedProps = { - actions: mockedActions, tables: [table], - queryEditor: defaultQueryEditor, + queryEditorId: defaultQueryEditor.id, database: { id: 1, database_name: 'main', @@ -58,103 +52,91 @@ fetchMock.get('glob:*/superset/tables/**', { tableLength: 1, }); -describe('Left Panel Expansion', () => { - test('is valid', () => { - expect( - React.isValidElement( - - - , - ), - ).toBe(true); - }); - - test('renders a TableElement', async () => { - render(, { +const renderAndWait = (props, store) => + waitFor(() => + render(, { useRedux: true, - initialState, - }); + ...(store && { store }), + }), + ); + +test('is valid', () => { + expect( + React.isValidElement( + + + , + ), + ).toBe(true); +}); - expect(await screen.findByText(/Database/i)).toBeInTheDocument(); +test('renders a TableElement', async () => { + await renderAndWait(mockedProps, store); + expect(await screen.findByText(/Database/i)).toBeInTheDocument(); + const tableElement = screen.getAllByTestId('table-element'); + expect(tableElement.length).toBeGreaterThanOrEqual(1); +}); + +test('table should be visible when expanded is true', async () => { + const { container } = await renderAndWait(mockedProps, store); + + const dbSelect = screen.getByRole('combobox', { + name: 'Select database or type database name', + }); + const schemaSelect = screen.getByRole('combobox', { + name: 'Select schema or type schema name', + }); + const dropdown = screen.getByText(/Table/i); + const abUser = screen.queryAllByText(/ab_user/i); + + await waitFor(() => { + expect(screen.getByText(/Database/i)).toBeInTheDocument(); + expect(dbSelect).toBeInTheDocument(); + expect(schemaSelect).toBeInTheDocument(); + expect(dropdown).toBeInTheDocument(); + expect(abUser).toHaveLength(2); expect( - screen.queryAllByTestId('table-element').length, - ).toBeGreaterThanOrEqual(1); + container.querySelector('.ant-collapse-content-active'), + ).toBeInTheDocument(); }); +}); - test('table should be visible when expanded is true', async () => { - const { container } = render(, { - useRedux: true, - initialState, - }); +test('should toggle the table when the header is clicked', async () => { + const store = mockStore(initialState); + await renderAndWait(mockedProps, store); - const dbSelect = screen.getByRole('combobox', { - name: 'Select database or type database name', - }); - const schemaSelect = screen.getByRole('combobox', { - name: 'Select schema or type schema name', - }); - const dropdown = screen.getByText(/Table/i); - const abUser = screen.queryAllByText(/ab_user/i); + const header = (await screen.findAllByText(/ab_user/))[1]; + expect(header).toBeInTheDocument(); + userEvent.click(header); - act(async () => { - expect(await screen.findByText(/Database/i)).toBeInTheDocument(); - expect(dbSelect).toBeInTheDocument(); - expect(schemaSelect).toBeInTheDocument(); - expect(dropdown).toBeInTheDocument(); - expect(abUser).toHaveLength(2); - expect( - container.querySelector('.ant-collapse-content-active'), - ).toBeInTheDocument(); - }); + await waitFor(() => { + expect(store.getActions()).toHaveLength(2); + expect(store.getActions()[1].type).toEqual('COLLAPSE_TABLE'); }); +}); - test('should toggle the table when the header is clicked', async () => { - const collapseMock = jest.fn(); - render( - , - { - useRedux: true, - initialState, - }, - ); +test('When changing database the table list must be updated', async () => { + const { rerender } = await renderAndWait(mockedProps, store); - expect(await screen.findByText(/ab_user/)).toBeInTheDocument(); - const header = screen.getByText(/ab_user/); - userEvent.click(header); - expect(collapseMock).toHaveBeenCalled(); - }); + expect(screen.getAllByText(/main/i)[0]).toBeInTheDocument(); + expect(screen.getAllByText(/ab_user/i)[0]).toBeInTheDocument(); - test('When changing database the table list must be updated', async () => { - const { rerender } = render(, { + rerender( + , + { useRedux: true, initialState, - }); - - await act(async () => { - expect(await screen.findByText(/main/i)).toBeInTheDocument(); - expect(await screen.findByText(/ab_user/i)).toBeInTheDocument(); - }); - rerender( - , - { - useRedux: true, - initialState, - }, - ); - expect(await screen.findByText(/new_db/i)).toBeInTheDocument(); - expect(await screen.findByText(/new_table/i)).toBeInTheDocument(); - }); + }, + ); + expect(await screen.findByText(/new_db/i)).toBeInTheDocument(); + expect(await screen.findByText(/new_table/i)).toBeInTheDocument(); }); diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx index 06a31711db4a..4b73cffff986 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx @@ -18,21 +18,35 @@ */ import React, { useEffect, - useRef, useCallback, useMemo, useState, Dispatch, SetStateAction, } from 'react'; +import { useDispatch } from 'react-redux'; import querystring from 'query-string'; + +import { + queryEditorSetDb, + queryEditorSetFunctionNames, + addTable, + removeTables, + collapseTable, + expandTable, + queryEditorSetSchema, + queryEditorSetTableOptions, + queryEditorSetSchemaOptions, + setDatabases, + addDangerToast, + resetState, +} from 'src/SqlLab/actions/sqlLab'; import Button from 'src/components/Button'; import { t, styled, css, SupersetTheme } from '@superset-ui/core'; import Collapse from 'src/components/Collapse'; import Icons from 'src/components/Icons'; import { TableSelectorMultiple } from 'src/components/TableSelector'; import { IconTooltip } from 'src/components/IconTooltip'; -import { QueryEditor, SchemaOption } from 'src/SqlLab/types'; import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; import { DatabaseObject } from 'src/components/DatabaseSelector'; import { EmptyStateSmall } from 'src/components/EmptyState'; @@ -41,37 +55,16 @@ import { LocalStorageKeys, setItem, } from 'src/utils/localStorageHelpers'; -import TableElement, { Table, TableElementProps } from '../TableElement'; +import TableElement, { Table } from '../TableElement'; interface ExtendedTable extends Table { expanded: boolean; } -interface actionsTypes { - queryEditorSetDb: (queryEditor: QueryEditor, dbId: number) => void; - queryEditorSetFunctionNames: (queryEditor: QueryEditor, dbId: number) => void; - collapseTable: (table: Table) => void; - expandTable: (table: Table) => void; - addTable: (queryEditor: any, database: any, value: any, schema: any) => void; - setDatabases: (arg0: any) => {}; - addDangerToast: (msg: string) => void; - queryEditorSetSchema: (queryEditor: QueryEditor, schema?: string) => void; - queryEditorSetSchemaOptions: ( - queryEditor: QueryEditor, - options: SchemaOption[], - ) => void; - queryEditorSetTableOptions: ( - queryEditor: QueryEditor, - options: Array, - ) => void; - resetState: () => void; -} - interface SqlEditorLeftBarProps { - queryEditor: QueryEditor; + queryEditorId: string; height?: number; tables?: ExtendedTable[]; - actions: actionsTypes & TableElementProps['actions']; database: DatabaseObject; setEmptyState: Dispatch>; } @@ -102,22 +95,21 @@ const collapseStyles = (theme: SupersetTheme) => css` } `; -export default function SqlEditorLeftBar({ - actions, +const SqlEditorLeftBar = ({ database, - queryEditor, + queryEditorId, tables = [], height = 500, setEmptyState, -}: SqlEditorLeftBarProps) { - // Ref needed to avoid infinite rerenders on handlers - // that require and modify the queryEditor - const queryEditorRef = useRef(queryEditor); +}: SqlEditorLeftBarProps) => { + const dispatch = useDispatch(); + const queryEditor = useQueryEditor(queryEditorId, ['dbId', 'schema']); + const [emptyResultsWithSearch, setEmptyResultsWithSearch] = useState(false); const [userSelectedDb, setUserSelected] = useState( null, ); - const { schema } = useQueryEditor(queryEditor.id, ['schema']); + const { schema } = queryEditor; useEffect(() => { const bool = querystring.parse(window.location.search).db; @@ -132,18 +124,14 @@ export default function SqlEditorLeftBar({ } else setUserSelected(database); }, [database]); - useEffect(() => { - queryEditorRef.current = queryEditor; - }, [queryEditor, database]); - const onEmptyResults = (searchText?: string) => { setEmptyResultsWithSearch(!!searchText); }; const onDbChange = ({ id: dbId }: { id: number }) => { setEmptyState(false); - actions.queryEditorSetDb(queryEditor, dbId); - actions.queryEditorSetFunctionNames(queryEditor, dbId); + dispatch(queryEditorSetDb(queryEditor, dbId)); + dispatch(queryEditorSetFunctionNames(queryEditor, dbId)); }; const selectedTableNames = useMemo( @@ -168,21 +156,21 @@ export default function SqlEditorLeftBar({ }); tablesToAdd.forEach(tableName => - actions.addTable(queryEditor, database, tableName, schemaName), + dispatch(addTable(queryEditor, database, tableName, schemaName)), ); - actions.removeTables(currentTables); + dispatch(removeTables(currentTables)); }; const onToggleTable = (updatedTables: string[]) => { tables.forEach((table: ExtendedTable) => { if (!updatedTables.includes(table.id.toString()) && table.expanded) { - actions.collapseTable(table); + dispatch(collapseTable(table)); } else if ( updatedTables.includes(table.id.toString()) && !table.expanded ) { - actions.expandTable(table); + dispatch(expandTable(table)); } }); }; @@ -225,41 +213,45 @@ export default function SqlEditorLeftBar({ } /> ); - const handleSchemaChange = useCallback( - (schema: string) => { - if (queryEditorRef.current) { - actions.queryEditorSetSchema(queryEditorRef.current, schema); - } - }, - [actions], - ); - const handleTablesLoad = React.useCallback( - (options: Array) => { - if (queryEditorRef.current) { - actions.queryEditorSetTableOptions(queryEditorRef.current, options); - } - }, - [actions], - ); + const handleSchemaChange = useCallback((schema: string) => { + if (queryEditor) { + dispatch(queryEditorSetSchema(queryEditor, schema)); + } + }, []); - const handleSchemasLoad = React.useCallback( - (options: Array) => { - if (queryEditorRef.current) { - actions.queryEditorSetSchemaOptions(queryEditorRef.current, options); - } - }, - [actions], - ); + const handleTablesLoad = useCallback((options: Array) => { + if (queryEditor) { + dispatch(queryEditorSetTableOptions(queryEditor, options)); + } + }, []); + + const handleSchemasLoad = useCallback((options: Array) => { + if (queryEditor) { + dispatch(queryEditorSetSchemaOptions(queryEditor, options)); + } + }, []); + + const handleDbList = useCallback((result: DatabaseObject) => { + dispatch(setDatabases(result)); + }, []); + + const handleError = useCallback((message: string) => { + dispatch(addDangerToast(message)); + }, []); + + const handleResetState = useCallback(() => { + dispatch(resetState()); + }, []); return ( -
+
{tables.map(table => ( - + ))}
@@ -296,11 +288,13 @@ export default function SqlEditorLeftBar({ )}
); -} +}; + +export default SqlEditorLeftBar; diff --git a/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.jsx b/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.jsx index 91bddc57fb2b..60efa3a59677 100644 --- a/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.jsx +++ b/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.jsx @@ -17,22 +17,24 @@ * under the License. */ import React from 'react'; -import { mount, shallow } from 'enzyme'; +import { mount } from 'enzyme'; import { Provider } from 'react-redux'; import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; import { supersetTheme, ThemeProvider } from '@superset-ui/core'; import Collapse from 'src/components/Collapse'; import { IconTooltip } from 'src/components/IconTooltip'; import TableElement from 'src/SqlLab/components/TableElement'; import ColumnElement from 'src/SqlLab/components/ColumnElement'; import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; -import { mockedActions, table } from 'src/SqlLab/fixtures'; +import { initialState, table } from 'src/SqlLab/fixtures'; + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); describe('TableElement', () => { - const mockStore = configureStore([]); - const store = mockStore({}); + const store = mockStore(initialState); const mockedProps = { - actions: mockedActions, table, timeout: 0, }; @@ -57,7 +59,17 @@ describe('TableElement', () => { expect(wrapper.find(IconTooltip)).toHaveLength(4); }); it('has 14 columns', () => { - const wrapper = shallow(); + const wrapper = mount( + + + , + { + wrappingComponent: ThemeProvider, + wrappingComponentProps: { + theme: supersetTheme, + }, + }, + ); expect(wrapper.find(ColumnElement)).toHaveLength(14); }); it('mounts', () => { @@ -143,6 +155,7 @@ describe('TableElement', () => { }, ); wrapper.find('.table-remove').hostNodes().simulate('click'); - expect(mockedActions.removeDataPreview.called).toBe(true); + expect(store.getActions()).toHaveLength(1); + expect(store.getActions()[0].type).toEqual('REMOVE_DATA_PREVIEW'); }); }); diff --git a/superset-frontend/src/SqlLab/components/TableElement/index.tsx b/superset-frontend/src/SqlLab/components/TableElement/index.tsx index 1ecc7822203f..44fbe6e1cc0c 100644 --- a/superset-frontend/src/SqlLab/components/TableElement/index.tsx +++ b/superset-frontend/src/SqlLab/components/TableElement/index.tsx @@ -17,12 +17,14 @@ * under the License. */ import React, { useState, useRef } from 'react'; +import { useDispatch } from 'react-redux'; import Collapse from 'src/components/Collapse'; import Card from 'src/components/Card'; import ButtonGroup from 'src/components/ButtonGroup'; import { t, styled } from '@superset-ui/core'; import { debounce } from 'lodash'; +import { removeDataPreview, removeTables } from 'src/SqlLab/actions/sqlLab'; import { Tooltip } from 'src/components/Tooltip'; import CopyToClipboard from 'src/components/CopyToClipboard'; import { IconTooltip } from 'src/components/IconTooltip'; @@ -55,10 +57,6 @@ export interface Table { export interface TableElementProps { table: Table; - actions: { - removeDataPreview: (table: Table) => void; - removeTables: (tables: Table[]) => void; - }; } const StyledSpan = styled.span` @@ -74,7 +72,9 @@ const Fade = styled.div` opacity: ${(props: { hovered: boolean }) => (props.hovered ? 1 : 0)}; `; -const TableElement = ({ table, actions, ...props }: TableElementProps) => { +const TableElement = ({ table, ...props }: TableElementProps) => { + const dispatch = useDispatch(); + const [sortColumns, setSortColumns] = useState(false); const [hovered, setHovered] = useState(false); const tableNameRef = useRef(null); @@ -84,8 +84,8 @@ const TableElement = ({ table, actions, ...props }: TableElementProps) => { }; const removeTable = () => { - actions.removeDataPreview(table); - actions.removeTables([table]); + dispatch(removeDataPreview(table)); + dispatch(removeTables([table])); }; const toggleSortColumns = () => { diff --git a/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx b/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx index 886edb7afc12..fdf8fd3b53f5 100644 --- a/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx +++ b/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx @@ -30,7 +30,7 @@ import { import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures'; import TemplateParamsEditor, { - Props, + TemplateParamsEditorProps, } from 'src/SqlLab/components/TemplateParamsEditor'; jest.mock('src/components/DeprecatedSelect', () => () => ( @@ -50,12 +50,15 @@ jest.mock('src/components/AsyncAceEditor', () => ({ const middlewares = [thunk]; const mockStore = configureStore(middlewares); -const setup = (otherProps: Partial = {}, store?: Store) => +const setup = ( + otherProps: Partial = {}, + store?: Store, +) => render( {}} - queryEditor={defaultQueryEditor} + queryEditorId={defaultQueryEditor.id} {...otherProps} />, { diff --git a/superset-frontend/src/SqlLab/components/TemplateParamsEditor/index.tsx b/superset-frontend/src/SqlLab/components/TemplateParamsEditor/index.tsx index 4eea10da05fe..8943c2b8d5d4 100644 --- a/superset-frontend/src/SqlLab/components/TemplateParamsEditor/index.tsx +++ b/superset-frontend/src/SqlLab/components/TemplateParamsEditor/index.tsx @@ -17,18 +17,16 @@ * under the License. */ import React, { useState, useEffect } from 'react'; -import Badge from 'src/components/Badge'; import { t, styled } from '@superset-ui/core'; import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls'; import { debounce } from 'lodash'; +import Badge from 'src/components/Badge'; import ModalTrigger from 'src/components/ModalTrigger'; import { ConfigEditor } from 'src/components/AsyncAceEditor'; import { FAST_DEBOUNCE } from 'src/constants'; import { Tooltip } from 'src/components/Tooltip'; -import { useSelector } from 'react-redux'; -import { QueryEditor, SqlLabRootState } from 'src/SqlLab/types'; -import { getUpToDateQuery } from 'src/SqlLab/actions/sqlLab'; +import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; const StyledConfigEditor = styled(ConfigEditor)` &.ace_editor { @@ -36,24 +34,22 @@ const StyledConfigEditor = styled(ConfigEditor)` } `; -export type Props = { - queryEditor: QueryEditor; +export type TemplateParamsEditorProps = { + queryEditorId: string; language: 'yaml' | 'json'; onChange: () => void; }; -function TemplateParamsEditor({ - queryEditor, +const TemplateParamsEditor = ({ + queryEditorId, language, onChange = () => {}, -}: Props) { +}: TemplateParamsEditorProps) => { const [parsedJSON, setParsedJSON] = useState({}); const [isValid, setIsValid] = useState(true); - const code = useSelector( - rootState => - (getUpToDateQuery(rootState, queryEditor) as unknown as QueryEditor) - .templateParams || '{}', - ); + + const { templateParams } = useQueryEditor(queryEditorId, ['templateParams']); + const code = templateParams ?? '{}'; useEffect(() => { try { @@ -125,6 +121,6 @@ function TemplateParamsEditor({ modalBody={modalBody} /> ); -} +}; export default TemplateParamsEditor; diff --git a/superset-frontend/src/SqlLab/fixtures.ts b/superset-frontend/src/SqlLab/fixtures.ts index bb38fe6873a8..878c49faae43 100644 --- a/superset-frontend/src/SqlLab/fixtures.ts +++ b/superset-frontend/src/SqlLab/fixtures.ts @@ -203,6 +203,7 @@ export const extraQueryEditor1 = { id: 'diekd23', sql: 'SELECT *\nFROM\nWHERE\nLIMIT', name: 'Untitled Query 2', + selectedText: 'SELECT', }; export const extraQueryEditor2 = { diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx index 26b118bcac83..6197352e481e 100644 --- a/superset-frontend/src/components/DatabaseSelector/index.tsx +++ b/superset-frontend/src/components/DatabaseSelector/index.tsx @@ -92,7 +92,7 @@ export interface DatabaseSelectorProps { db?: DatabaseObject | null; emptyState?: ReactNode; formMode?: boolean; - getDbList?: (arg0: any) => {}; + getDbList?: (arg0: any) => void; handleError: (msg: string) => void; isDatabaseSelectEnabled?: boolean; onDbChange?: (db: DatabaseObject) => void; diff --git a/superset-frontend/src/components/TableSelector/index.tsx b/superset-frontend/src/components/TableSelector/index.tsx index 4a9fb62d944c..2adf70c74c86 100644 --- a/superset-frontend/src/components/TableSelector/index.tsx +++ b/superset-frontend/src/components/TableSelector/index.tsx @@ -93,7 +93,7 @@ interface TableSelectorProps { database?: DatabaseObject | null; emptyState?: ReactNode; formMode?: boolean; - getDbList?: (arg0: any) => {}; + getDbList?: (arg0: any) => void; handleError: (msg: string) => void; isDatabaseSelectEnabled?: boolean; onDbChange?: (db: DatabaseObject) => void;