mirror of
https://github.com/apache/superset.git
synced 2026-04-22 17:45:21 +00:00
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:
@@ -17,38 +17,100 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { Store } from 'redux';
|
||||
import {
|
||||
render,
|
||||
fireEvent,
|
||||
getByText,
|
||||
waitFor,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
||||
import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures';
|
||||
|
||||
import TemplateParamsEditor from 'src/SqlLab/components/TemplateParamsEditor';
|
||||
import TemplateParamsEditor, {
|
||||
Props,
|
||||
} from 'src/SqlLab/components/TemplateParamsEditor';
|
||||
|
||||
const ThemeWrapper = ({ children }: { children: ReactNode }) => (
|
||||
<ThemeProvider theme={supersetTheme}>{children}</ThemeProvider>
|
||||
);
|
||||
jest.mock('src/components/Select', () => () => (
|
||||
<div data-test="mock-deprecated-select" />
|
||||
));
|
||||
jest.mock('src/components/Select/Select', () => () => (
|
||||
<div data-test="mock-deprecated-select-select" />
|
||||
));
|
||||
jest.mock('src/components/Select/AsyncSelect', () => () => (
|
||||
<div data-test="mock-async-select" />
|
||||
));
|
||||
jest.mock('src/components/AsyncAceEditor', () => ({
|
||||
ConfigEditor: ({ value }: { value: string }) => (
|
||||
<div data-test="mock-async-ace-editor">{value}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
const setup = (otherProps: Partial<Props> = {}, store?: Store) =>
|
||||
render(
|
||||
<TemplateParamsEditor
|
||||
language="json"
|
||||
onChange={() => {}}
|
||||
queryEditor={defaultQueryEditor}
|
||||
{...otherProps}
|
||||
/>,
|
||||
{
|
||||
useRedux: true,
|
||||
store: mockStore(initialState),
|
||||
...(store && { store }),
|
||||
},
|
||||
);
|
||||
|
||||
describe('TemplateParamsEditor', () => {
|
||||
it('should render with a title', () => {
|
||||
const { container } = render(
|
||||
<TemplateParamsEditor code="FOO" language="json" onChange={() => {}} />,
|
||||
{ wrapper: ThemeWrapper },
|
||||
);
|
||||
const { container } = setup();
|
||||
expect(container.querySelector('div[role="button"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should open a modal with the ace editor', async () => {
|
||||
const { container, baseElement } = render(
|
||||
<TemplateParamsEditor code="FOO" language="json" onChange={() => {}} />,
|
||||
{ wrapper: ThemeWrapper },
|
||||
const { container, getByTestId } = setup();
|
||||
fireEvent.click(getByText(container, 'Parameters'));
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('mock-async-ace-editor')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders templateParams', async () => {
|
||||
const { container, getByTestId } = setup();
|
||||
fireEvent.click(getByText(container, 'Parameters'));
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('mock-async-ace-editor')).toBeInTheDocument();
|
||||
});
|
||||
expect(getByTestId('mock-async-ace-editor')).toHaveTextContent(
|
||||
defaultQueryEditor.templateParams,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders code from unsaved changes', async () => {
|
||||
const expectedCode = 'custom code value';
|
||||
const { container, getByTestId } = setup(
|
||||
{},
|
||||
mockStore({
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
...initialState.sqlLab,
|
||||
unsavedQueryEditor: {
|
||||
id: defaultQueryEditor.id,
|
||||
templateParams: expectedCode,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
fireEvent.click(getByText(container, 'Parameters'));
|
||||
await waitFor(() => {
|
||||
expect(baseElement.querySelector('#ace-editor')).toBeInTheDocument();
|
||||
expect(getByTestId('mock-async-ace-editor')).toBeInTheDocument();
|
||||
});
|
||||
expect(getByTestId('mock-async-ace-editor')).toHaveTextContent(
|
||||
expectedCode,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,9 @@ 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';
|
||||
|
||||
const StyledConfigEditor = styled(ConfigEditor)`
|
||||
&.ace_editor {
|
||||
@@ -33,17 +36,24 @@ const StyledConfigEditor = styled(ConfigEditor)`
|
||||
}
|
||||
`;
|
||||
|
||||
function TemplateParamsEditor({
|
||||
code = '{}',
|
||||
language,
|
||||
onChange = () => {},
|
||||
}: {
|
||||
code: string;
|
||||
export type Props = {
|
||||
queryEditor: QueryEditor;
|
||||
language: 'yaml' | 'json';
|
||||
onChange: () => void;
|
||||
}) {
|
||||
};
|
||||
|
||||
function TemplateParamsEditor({
|
||||
queryEditor,
|
||||
language,
|
||||
onChange = () => {},
|
||||
}: Props) {
|
||||
const [parsedJSON, setParsedJSON] = useState({});
|
||||
const [isValid, setIsValid] = useState(true);
|
||||
const code = useSelector<SqlLabRootState, string>(
|
||||
rootState =>
|
||||
(getUpToDateQuery(rootState, queryEditor) as unknown as QueryEditor)
|
||||
.templateParams || '{}',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user