diff --git a/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.jsx b/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.tsx similarity index 93% rename from superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.jsx rename to superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.tsx index 276d8eea66..80a102ff21 100644 --- a/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.jsx +++ b/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.tsx @@ -20,11 +20,12 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { render, screen, waitFor } from 'spec/helpers/testing-library'; -import SouthPane from 'src/SqlLab/components/SouthPane'; +import SouthPane, { SouthPaneProps } from 'src/SqlLab/components/SouthPane'; import '@testing-library/jest-dom/extend-expect'; import { STATUS_OPTIONS } from 'src/SqlLab/constants'; import { initialState, table, defaultQueryEditor } from 'src/SqlLab/fixtures'; import { denormalizeTimestamp } from '@superset-ui/core'; +import { Store } from 'redux'; const mockedProps = { queryEditorId: defaultQueryEditor.id, @@ -42,6 +43,8 @@ const mockedEmptyProps = { defaultQueryLimit: 100, }; +jest.mock('src/SqlLab/components/SqlEditorLeftBar', () => jest.fn()); + const latestQueryProgressMsg = 'LATEST QUERY MESSAGE - LCly_kkIN'; const middlewares = [thunk]; @@ -100,14 +103,14 @@ const store = mockStore({ }, }, }); -const setup = (props, store) => +const setup = (props: SouthPaneProps, store: Store) => render(, { useRedux: true, ...(store && { store }), }); describe('SouthPane', () => { - const renderAndWait = (props, store) => + const renderAndWait = (props: SouthPaneProps, store: Store) => waitFor(async () => setup(props, store)); it('Renders an empty state for results', async () => { diff --git a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx index 42ee4830fd..d601018a6e 100644 --- a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx +++ b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import React, { createRef } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import React, { createRef, useMemo } from 'react'; +import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import shortid from 'shortid'; import Alert from 'src/components/Alert'; import Tabs from 'src/components/Tabs'; @@ -105,11 +105,26 @@ const SouthPane = ({ defaultQueryLimit, }: SouthPaneProps) => { const dispatch = useDispatch(); - - const { editorQueries, dataPreviewQueries, databases, offline, user } = - useSelector(({ user, sqlLab }: SqlLabRootState) => { - const { databases, offline, queries, tables } = sqlLab; - const dataPreviewQueries = tables + const user = useSelector(({ user }: SqlLabRootState) => user, shallowEqual); + const { databases, offline, queries, tables } = useSelector( + ({ sqlLab: { databases, offline, queries, tables } }: SqlLabRootState) => ({ + databases, + offline, + queries, + tables, + }), + shallowEqual, + ); + const editorQueries = useMemo( + () => + Object.values(queries).filter( + ({ sqlEditorId }) => sqlEditorId === queryEditorId, + ), + [queries, queryEditorId], + ); + const dataPreviewQueries = useMemo( + () => + tables .filter( ({ dataPreviewQueryId, queryEditorId: qeId }) => dataPreviewQueryId && @@ -119,18 +134,13 @@ const SouthPane = ({ .map(({ name, dataPreviewQueryId }) => ({ ...queries[dataPreviewQueryId || ''], tableName: name, - })); - const editorQueries = Object.values(queries).filter( - ({ sqlEditorId }) => sqlEditorId === queryEditorId, - ); - return { - editorQueries, - dataPreviewQueries, - databases, - offline: offline ?? false, - user, - }; - }); + })), + [queries, queryEditorId, tables], + ); + const latestQuery = useMemo( + () => editorQueries.find(({ id }) => id === latestQueryId), + [editorQueries, latestQueryId], + ); const activeSouthPaneTab = useSelector( @@ -148,11 +158,6 @@ const SouthPane = ({ ); const renderResults = () => { - let latestQuery; - if (editorQueries.length > 0) { - // get the latest query - latestQuery = editorQueries.find(({ id }) => id === latestQueryId); - } let results; if (latestQuery) { if (latestQuery?.extra?.errors) { diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx index ed3f3c9de2..23424ff264 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx @@ -30,6 +30,7 @@ import { defaultQueryEditor, } from 'src/SqlLab/fixtures'; import SqlEditorLeftBar from 'src/SqlLab/components/SqlEditorLeftBar'; +import ResultSet from 'src/SqlLab/components/ResultSet'; import { api } from 'src/hooks/apiResources/queryApi'; import { getExtensionsRegistry } from '@superset-ui/core'; import setupExtensions from 'src/setup/setupExtensions'; @@ -46,6 +47,7 @@ jest.mock('src/components/AsyncAceEditor', () => ({ ), })); jest.mock('src/SqlLab/components/SqlEditorLeftBar', () => jest.fn()); +jest.mock('src/SqlLab/components/ResultSet', () => jest.fn()); fetchMock.get('glob:*/api/v1/database/*/function_names/', { function_names: [], @@ -56,10 +58,17 @@ fetchMock.post('glob:*/sqllab/execute/*', { result: [] }); let store; let actions; +const latestQuery = { + ...queries[0], + sqlEditorId: defaultQueryEditor.id, +}; const mockInitialState = { ...initialState, sqlLab: { ...initialState.sqlLab, + queries: { + [latestQuery.id]: { ...latestQuery, startDttm: new Date().getTime() }, + }, databases: { 1991: { allow_ctas: false, @@ -77,6 +86,7 @@ const mockInitialState = { unsavedQueryEditor: { id: defaultQueryEditor.id, dbId: 1991, + latestQueryId: latestQuery.id, }, }, }; @@ -107,7 +117,6 @@ const createStore = initState => describe('SqlEditor', () => { const mockedProps = { queryEditor: initialState.sqlLab.queryEditors[0], - latestQuery: queries[0], tables: [table], getHeight: () => '100px', editorQueries: [], @@ -125,6 +134,8 @@ describe('SqlEditor', () => { SqlEditorLeftBar.mockImplementation(() => (
)); + ResultSet.mockClear(); + ResultSet.mockImplementation(() =>
); }); afterEach(() => { @@ -153,15 +164,18 @@ describe('SqlEditor', () => { expect(await findByTestId('react-ace')).toBeInTheDocument(); }); - it('avoids rerendering EditorLeftBar while typing', async () => { + it('avoids rerendering EditorLeftBar and ResultSet while typing', async () => { const { findByTestId } = setup(mockedProps, store); const editor = await findByTestId('react-ace'); const sql = 'select *'; const renderCount = SqlEditorLeftBar.mock.calls.length; + const renderCountForSouthPane = ResultSet.mock.calls.length; expect(SqlEditorLeftBar).toHaveBeenCalledTimes(renderCount); + expect(ResultSet).toHaveBeenCalledTimes(renderCountForSouthPane); fireEvent.change(editor, { target: { value: sql } }); // Verify the rendering regression expect(SqlEditorLeftBar).toHaveBeenCalledTimes(renderCount); + expect(ResultSet).toHaveBeenCalledTimes(renderCountForSouthPane); }); it('renders sql from unsaved change', async () => { @@ -198,10 +212,8 @@ describe('SqlEditor', () => { }); it('render a SouthPane', async () => { - const { findByText } = setup(mockedProps, store); - expect( - await findByText(/run a query to display results/i), - ).toBeInTheDocument(); + const { findByTestId } = setup(mockedProps, store); + expect(await findByTestId('mock-result-set')).toBeInTheDocument(); }); it('runs query action with ctas false', async () => {