perf(sqllab): Rendering perf improvement using immutable state (#20877)

* perf(sqllab): Rendering perf improvement using immutable state

- keep queryEditors immutable during active state
- add unsavedQueryEditor to store all active changes
- refactor each component to subscribe the related unsaved editor state only

* revert ISaveableDatasource type cast

* missing trigger prop

* a default of an empty object and optional operator
This commit is contained in:
JUST.in DO IT
2022-08-23 08:17:19 -07:00
committed by GitHub
parent 4ca4a5c7cb
commit f77b910e2c
32 changed files with 1929 additions and 606 deletions

View File

@@ -17,18 +17,19 @@
* under the License.
*/
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 userEvent from '@testing-library/user-event';
import SaveQuery from 'src/SqlLab/components/SaveQuery';
import { databases } from 'src/SqlLab/fixtures';
import { initialState, databases } from 'src/SqlLab/fixtures';
const mockedProps = {
query: {
queryEditor: {
dbId: 1,
schema: 'main',
sql: 'SELECT * FROM t',
},
defaultLabel: 'untitled',
animation: false,
database: databases.result[0],
onUpdate: () => {},
@@ -43,9 +44,15 @@ const splitSaveBtnProps = {
},
};
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
describe('SavedQuery', () => {
it('renders a non-split save button when allows_virtual_table_explore is not enabled', () => {
render(<SaveQuery {...mockedProps} />, { useRedux: true });
render(<SaveQuery {...mockedProps} />, {
useRedux: true,
store: mockStore(initialState),
});
const saveBtn = screen.getByRole('button', { name: /save/i });
@@ -53,7 +60,10 @@ describe('SavedQuery', () => {
});
it('renders a save query modal when user clicks save button', () => {
render(<SaveQuery {...mockedProps} />, { useRedux: true });
render(<SaveQuery {...mockedProps} />, {
useRedux: true,
store: mockStore(initialState),
});
const saveBtn = screen.getByRole('button', { name: /save/i });
userEvent.click(saveBtn);
@@ -66,7 +76,10 @@ describe('SavedQuery', () => {
});
it('renders the save query modal UI', () => {
render(<SaveQuery {...mockedProps} />, { useRedux: true });
render(<SaveQuery {...mockedProps} />, {
useRedux: true,
store: mockStore(initialState),
});
const saveBtn = screen.getByRole('button', { name: /save/i });
userEvent.click(saveBtn);
@@ -100,12 +113,15 @@ describe('SavedQuery', () => {
it('renders a "save as new" and "update" button if query already exists', () => {
const props = {
...mockedProps,
query: {
queryEditor: {
...mockedProps.query,
remoteId: '42',
},
};
render(<SaveQuery {...props} />, { useRedux: true });
render(<SaveQuery {...props} />, {
useRedux: true,
store: mockStore(initialState),
});
const saveBtn = screen.getByRole('button', { name: /save/i });
userEvent.click(saveBtn);
@@ -118,7 +134,10 @@ describe('SavedQuery', () => {
});
it('renders a split save button when allows_virtual_table_explore is enabled', async () => {
render(<SaveQuery {...splitSaveBtnProps} />, { useRedux: true });
render(<SaveQuery {...splitSaveBtnProps} />, {
useRedux: true,
store: mockStore(initialState),
});
await waitFor(() => {
const saveBtn = screen.getByRole('button', { name: /save/i });
@@ -130,7 +149,10 @@ describe('SavedQuery', () => {
});
it('renders a save dataset modal when user clicks "save dataset" menu item', async () => {
render(<SaveQuery {...splitSaveBtnProps} />, { useRedux: true });
render(<SaveQuery {...splitSaveBtnProps} />, {
useRedux: true,
store: mockStore(initialState),
});
await waitFor(() => {
const caretBtn = screen.getByRole('button', { name: /caret-down/i });
@@ -146,7 +168,10 @@ describe('SavedQuery', () => {
});
it('renders the save dataset modal UI', async () => {
render(<SaveQuery {...splitSaveBtnProps} />, { useRedux: true });
render(<SaveQuery {...splitSaveBtnProps} />, {
useRedux: true,
store: mockStore(initialState),
});
await waitFor(() => {
const caretBtn = screen.getByRole('button', { name: /caret-down/i });

View File

@@ -17,6 +17,7 @@
* under the License.
*/
import React, { useState, useEffect } from 'react';
import { useSelector, shallowEqual } from 'react-redux';
import { Row, Col } from 'src/components';
import { Input, TextArea } from 'src/components/Input';
import { t, styled } from '@superset-ui/core';
@@ -25,12 +26,16 @@ import { Menu } from 'src/components/Menu';
import { Form, FormItem } from 'src/components/Form';
import Modal from 'src/components/Modal';
import SaveDatasetActionButton from 'src/SqlLab/components/SaveDatasetActionButton';
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
import {
SaveDatasetModal,
ISaveableDatasource,
} from 'src/SqlLab/components/SaveDatasetModal';
import { getDatasourceAsSaveableDataset } from 'src/utils/datasourceUtils';
import { QueryEditor, SqlLabRootState } from 'src/SqlLab/types';
interface SaveQueryProps {
query: QueryPayload;
defaultLabel: string;
queryEditor: QueryEditor;
columns: ISaveableDatasource['columns'];
onSave: (arg0: QueryPayload) => void;
onUpdate: (arg0: QueryPayload) => void;
saveQueryWarning: string | null;
@@ -76,13 +81,22 @@ const Styles = styled.span`
`;
export default function SaveQuery({
query,
defaultLabel = t('Undefined'),
queryEditor,
onSave = () => {},
onUpdate,
saveQueryWarning = null,
database,
columns,
}: SaveQueryProps) {
const query = useSelector<SqlLabRootState, QueryEditor>(
({ sqlLab: { unsavedQueryEditor } }) => ({
...queryEditor,
...(queryEditor.id === unsavedQueryEditor.id && unsavedQueryEditor),
columns,
}),
shallowEqual,
);
const defaultLabel = query.name || query.description || t('Undefined');
const [description, setDescription] = useState<string>(
query.description || '',
);
@@ -100,11 +114,12 @@ export default function SaveQuery({
</Menu>
);
const queryPayload = () => ({
...query,
name: label,
description,
});
const queryPayload = () =>
({
...query,
name: label,
description,
} as any as QueryPayload);
useEffect(() => {
if (!isSaved) setLabel(defaultLabel);