mirror of
https://github.com/apache/superset.git
synced 2026-04-21 17:14:57 +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:
@@ -40,7 +40,12 @@ import {
|
||||
} from 'src/SqlLab/actions/sqlLab';
|
||||
import { EmptyStateBig } from 'src/components/EmptyState';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
import { initialState, queries, table } from 'src/SqlLab/fixtures';
|
||||
import {
|
||||
initialState,
|
||||
queries,
|
||||
table,
|
||||
defaultQueryEditor,
|
||||
} from 'src/SqlLab/fixtures';
|
||||
|
||||
const MOCKED_SQL_EDITOR_HEIGHT = 500;
|
||||
|
||||
@@ -48,7 +53,31 @@ fetchMock.get('glob:*/api/v1/database/*', { result: [] });
|
||||
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
const store = mockStore(initialState);
|
||||
const store = mockStore({
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
...initialState.sqlLab,
|
||||
databases: {
|
||||
dbid1: {
|
||||
allow_ctas: false,
|
||||
allow_cvas: false,
|
||||
allow_dml: false,
|
||||
allow_file_upload: false,
|
||||
allow_multi_schema_metadata_fetch: false,
|
||||
allow_run_async: false,
|
||||
backend: 'postgresql',
|
||||
database_name: 'examples',
|
||||
expose_in_sqllab: true,
|
||||
force_ctas_schema: null,
|
||||
id: 1,
|
||||
},
|
||||
},
|
||||
unsavedQueryEditor: {
|
||||
id: defaultQueryEditor.id,
|
||||
dbId: 'dbid1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('SqlEditor', () => {
|
||||
const mockedProps = {
|
||||
@@ -57,21 +86,9 @@ describe('SqlEditor', () => {
|
||||
queryEditorSetSelectedText,
|
||||
queryEditorSetSchemaOptions,
|
||||
addDangerToast: jest.fn(),
|
||||
removeDataPreview: jest.fn(),
|
||||
},
|
||||
database: {
|
||||
allow_ctas: false,
|
||||
allow_cvas: false,
|
||||
allow_dml: false,
|
||||
allow_file_upload: false,
|
||||
allow_multi_schema_metadata_fetch: false,
|
||||
allow_run_async: false,
|
||||
backend: 'postgresql',
|
||||
database_name: 'examples',
|
||||
expose_in_sqllab: true,
|
||||
force_ctas_schema: null,
|
||||
id: 1,
|
||||
},
|
||||
queryEditorId: initialState.sqlLab.queryEditors[0].id,
|
||||
queryEditor: initialState.sqlLab.queryEditors[0],
|
||||
latestQuery: queries[0],
|
||||
tables: [table],
|
||||
getHeight: () => '100px',
|
||||
@@ -94,8 +111,8 @@ describe('SqlEditor', () => {
|
||||
);
|
||||
|
||||
it('does not render SqlEditor if no db selected', () => {
|
||||
const database = {};
|
||||
const updatedProps = { ...mockedProps, database };
|
||||
const queryEditor = initialState.sqlLab.queryEditors[1];
|
||||
const updatedProps = { ...mockedProps, queryEditor };
|
||||
const wrapper = buildWrapper(updatedProps);
|
||||
expect(wrapper.find(EmptyStateBig)).toExist();
|
||||
});
|
||||
|
||||
@@ -43,10 +43,10 @@ import {
|
||||
persistEditorHeight,
|
||||
postStopQuery,
|
||||
queryEditorSetAutorun,
|
||||
queryEditorSetQueryLimit,
|
||||
queryEditorSetSql,
|
||||
queryEditorSetAndSaveSql,
|
||||
queryEditorSetTemplateParams,
|
||||
runQueryFromSqlEditor,
|
||||
runQuery,
|
||||
saveQuery,
|
||||
addSavedQueryToTabState,
|
||||
@@ -79,8 +79,8 @@ import SqlEditorLeftBar from '../SqlEditorLeftBar';
|
||||
import AceEditorWrapper from '../AceEditorWrapper';
|
||||
import RunQueryActionButton from '../RunQueryActionButton';
|
||||
import { newQueryTabName } from '../../utils/newQueryTabName';
|
||||
import QueryLimitSelect from '../QueryLimitSelect';
|
||||
|
||||
const LIMIT_DROPDOWN = [10, 100, 1000, 10000, 100000];
|
||||
const SQL_EDITOR_PADDING = 10;
|
||||
const INITIAL_NORTH_PERCENT = 30;
|
||||
const INITIAL_SOUTH_PERCENT = 70;
|
||||
@@ -96,26 +96,6 @@ const validatorMap =
|
||||
bootstrapData?.common?.conf?.SQL_VALIDATORS_BY_ENGINE || {};
|
||||
const scheduledQueriesConf = bootstrapData?.common?.conf?.SCHEDULED_QUERIES;
|
||||
|
||||
const LimitSelectStyled = styled.span`
|
||||
${({ theme }) => `
|
||||
.ant-dropdown-trigger {
|
||||
align-items: center;
|
||||
color: ${theme.colors.grayscale.dark2};
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
margin-right: ${theme.gridUnit * 2}px;
|
||||
text-decoration: none;
|
||||
span {
|
||||
display: inline-block;
|
||||
margin-right: ${theme.gridUnit * 2}px;
|
||||
&:last-of-type: {
|
||||
margin-right: ${theme.gridUnit * 4}px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledToolbar = styled.div`
|
||||
padding: ${({ theme }) => theme.gridUnit * 2}px;
|
||||
background: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
@@ -154,7 +134,7 @@ const propTypes = {
|
||||
tables: PropTypes.array.isRequired,
|
||||
editorQueries: PropTypes.array.isRequired,
|
||||
dataPreviewQueries: PropTypes.array.isRequired,
|
||||
queryEditorId: PropTypes.string.isRequired,
|
||||
queryEditor: PropTypes.object.isRequired,
|
||||
hideLeftBar: PropTypes.bool,
|
||||
defaultQueryLimit: PropTypes.number.isRequired,
|
||||
maxRow: PropTypes.number.isRequired,
|
||||
@@ -205,7 +185,6 @@ class SqlEditor extends React.PureComponent {
|
||||
);
|
||||
this.queryPane = this.queryPane.bind(this);
|
||||
this.getHotkeyConfig = this.getHotkeyConfig.bind(this);
|
||||
this.renderQueryLimit = this.renderQueryLimit.bind(this);
|
||||
this.getAceEditorAndSouthPaneHeights =
|
||||
this.getAceEditorAndSouthPaneHeights.bind(this);
|
||||
this.getSqlEditorHeight = this.getSqlEditorHeight.bind(this);
|
||||
@@ -382,21 +361,10 @@ class SqlEditor extends React.PureComponent {
|
||||
this.props.queryEditorSetAndSaveSql(this.props.queryEditor, sql);
|
||||
}
|
||||
|
||||
setQueryLimit(queryLimit) {
|
||||
this.props.queryEditorSetQueryLimit(this.props.queryEditor, queryLimit);
|
||||
}
|
||||
|
||||
getQueryCostEstimate() {
|
||||
if (this.props.database) {
|
||||
const qe = this.props.queryEditor;
|
||||
const query = {
|
||||
dbId: qe.dbId,
|
||||
sql: qe.selectedText ? qe.selectedText : this.props.queryEditor.sql,
|
||||
sqlEditorId: qe.id,
|
||||
schema: qe.schema,
|
||||
templateParams: qe.templateParams,
|
||||
};
|
||||
this.props.estimateQueryCost(query);
|
||||
this.props.estimateQueryCost(qe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,16 +393,9 @@ class SqlEditor extends React.PureComponent {
|
||||
}
|
||||
|
||||
requestValidation(sql) {
|
||||
if (this.props.database) {
|
||||
const qe = this.props.queryEditor;
|
||||
const query = {
|
||||
dbId: qe.dbId,
|
||||
sql,
|
||||
sqlEditorId: qe.id,
|
||||
schema: qe.schema,
|
||||
templateParams: qe.templateParams,
|
||||
};
|
||||
this.props.validateQuery(query);
|
||||
const { database, queryEditor, validateQuery } = this.props;
|
||||
if (database) {
|
||||
validateQuery(queryEditor, sql);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,25 +419,22 @@ class SqlEditor extends React.PureComponent {
|
||||
}
|
||||
|
||||
startQuery(ctas = false, ctas_method = CtasEnum.TABLE) {
|
||||
const qe = this.props.queryEditor;
|
||||
const query = {
|
||||
dbId: qe.dbId,
|
||||
sql: qe.selectedText ? qe.selectedText : qe.sql,
|
||||
sqlEditorId: qe.id,
|
||||
tab: qe.name,
|
||||
schema: qe.schema,
|
||||
tempTable: ctas ? this.state.ctas : '',
|
||||
templateParams: qe.templateParams,
|
||||
queryLimit: qe.queryLimit || this.props.defaultQueryLimit,
|
||||
runAsync: this.props.database
|
||||
? this.props.database.allow_run_async
|
||||
: false,
|
||||
const {
|
||||
database,
|
||||
runQueryFromSqlEditor,
|
||||
setActiveSouthPaneTab,
|
||||
queryEditor,
|
||||
defaultQueryLimit,
|
||||
} = this.props;
|
||||
runQueryFromSqlEditor(
|
||||
database,
|
||||
queryEditor,
|
||||
defaultQueryLimit,
|
||||
ctas ? this.state.ctas : '',
|
||||
ctas,
|
||||
ctas_method,
|
||||
updateTabState: !qe.selectedText,
|
||||
};
|
||||
this.props.runQuery(query);
|
||||
this.props.setActiveSouthPaneTab('Results');
|
||||
);
|
||||
setActiveSouthPaneTab('Results');
|
||||
}
|
||||
|
||||
stopQuery() {
|
||||
@@ -529,11 +487,7 @@ class SqlEditor extends React.PureComponent {
|
||||
onBlur={this.setQueryEditorSql}
|
||||
onChange={this.onSqlChanged}
|
||||
queryEditor={this.props.queryEditor}
|
||||
sql={this.props.queryEditor.sql}
|
||||
database={this.props.database}
|
||||
schemas={this.props.queryEditor.schemaOptions}
|
||||
tables={this.props.queryEditor.tableOptions}
|
||||
functionNames={this.props.queryEditor.functionNames}
|
||||
extendedTables={this.props.tables}
|
||||
height={`${aceEditorHeight}px`}
|
||||
hotkeys={hotkeys}
|
||||
@@ -577,7 +531,7 @@ class SqlEditor extends React.PureComponent {
|
||||
onChange={params => {
|
||||
this.props.actions.queryEditorSetTemplateParams(qe, params);
|
||||
}}
|
||||
code={qe.templateParams}
|
||||
queryEditor={qe}
|
||||
/>
|
||||
</Menu.Item>
|
||||
)}
|
||||
@@ -599,25 +553,6 @@ class SqlEditor extends React.PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
renderQueryLimit() {
|
||||
// Adding SQL_MAX_ROW value to dropdown
|
||||
const { maxRow } = this.props;
|
||||
LIMIT_DROPDOWN.push(maxRow);
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
{[...new Set(LIMIT_DROPDOWN)].map(limit => (
|
||||
<Menu.Item key={`${limit}`} onClick={() => this.setQueryLimit(limit)}>
|
||||
{/* // eslint-disable-line no-use-before-define */}
|
||||
<a role="button" styling="link">
|
||||
{this.convertToNumWithSpaces(limit)}
|
||||
</a>{' '}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
async saveQuery(query) {
|
||||
const { queryEditor: qe, actions } = this.props;
|
||||
const savedQuery = await actions.saveQuery(query);
|
||||
@@ -673,11 +608,10 @@ class SqlEditor extends React.PureComponent {
|
||||
? this.props.database.allow_run_async
|
||||
: false
|
||||
}
|
||||
queryEditor={qe}
|
||||
queryState={this.props.latestQuery?.state}
|
||||
runQuery={this.runQuery}
|
||||
selectedText={qe.selectedText}
|
||||
stopQuery={this.stopQuery}
|
||||
sql={this.props.queryEditor.sql}
|
||||
overlayCreateAsMenu={showMenu ? runMenuBtn : null}
|
||||
/>
|
||||
</span>
|
||||
@@ -687,27 +621,17 @@ class SqlEditor extends React.PureComponent {
|
||||
<span>
|
||||
<EstimateQueryCostButton
|
||||
getEstimate={this.getQueryCostEstimate}
|
||||
queryCostEstimate={qe.queryCostEstimate}
|
||||
selectedText={qe.selectedText}
|
||||
queryEditor={qe}
|
||||
tooltip={t('Estimate the cost before running a query')}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
<span>
|
||||
<LimitSelectStyled>
|
||||
<AntdDropdown overlay={this.renderQueryLimit()} trigger="click">
|
||||
<a onClick={e => e.preventDefault()}>
|
||||
<span>LIMIT:</span>
|
||||
<span className="limitDropdown">
|
||||
{this.convertToNumWithSpaces(
|
||||
this.props.queryEditor.queryLimit ||
|
||||
this.props.defaultQueryLimit,
|
||||
)}
|
||||
</span>
|
||||
<Icons.TriangleDown iconColor={theme.colors.grayscale.base} />
|
||||
</a>
|
||||
</AntdDropdown>
|
||||
</LimitSelectStyled>
|
||||
<QueryLimitSelect
|
||||
queryEditor={this.props.queryEditor}
|
||||
maxRow={this.props.maxRow}
|
||||
defaultQueryLimit={this.props.defaultQueryLimit}
|
||||
/>
|
||||
</span>
|
||||
{this.props.latestQuery && (
|
||||
<Timer
|
||||
@@ -721,11 +645,8 @@ class SqlEditor extends React.PureComponent {
|
||||
<div className="rightItems">
|
||||
<span>
|
||||
<SaveQuery
|
||||
query={{
|
||||
...qe,
|
||||
columns: this.props.latestQuery?.results?.columns || [],
|
||||
}}
|
||||
defaultLabel={qe.name || qe.description}
|
||||
queryEditor={qe}
|
||||
columns={this.props.latestQuery?.results?.columns || []}
|
||||
onSave={this.saveQuery}
|
||||
onUpdate={this.props.actions.updateSavedQuery}
|
||||
saveQueryWarning={this.props.saveQueryWarning}
|
||||
@@ -832,12 +753,22 @@ class SqlEditor extends React.PureComponent {
|
||||
SqlEditor.defaultProps = defaultProps;
|
||||
SqlEditor.propTypes = propTypes;
|
||||
|
||||
function mapStateToProps({ sqlLab }, props) {
|
||||
const queryEditor = sqlLab.queryEditors.find(
|
||||
editor => editor.id === props.queryEditorId,
|
||||
);
|
||||
function mapStateToProps({ sqlLab }, { queryEditor }) {
|
||||
let { latestQueryId, dbId } = queryEditor;
|
||||
if (sqlLab.unsavedQueryEditor.id === queryEditor.id) {
|
||||
const { latestQueryId: unsavedQID, dbId: unsavedDBID } =
|
||||
sqlLab.unsavedQueryEditor;
|
||||
latestQueryId = unsavedQID || latestQueryId;
|
||||
dbId = unsavedDBID || dbId;
|
||||
}
|
||||
const database = sqlLab.databases[dbId];
|
||||
const latestQuery = sqlLab.queries[latestQueryId];
|
||||
|
||||
return { sqlLab, ...props, queryEditor, queryEditors: sqlLab.queryEditors };
|
||||
return {
|
||||
queryEditors: sqlLab.queryEditors,
|
||||
latestQuery,
|
||||
database,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
@@ -848,10 +779,10 @@ function mapDispatchToProps(dispatch) {
|
||||
persistEditorHeight,
|
||||
postStopQuery,
|
||||
queryEditorSetAutorun,
|
||||
queryEditorSetQueryLimit,
|
||||
queryEditorSetSql,
|
||||
queryEditorSetAndSaveSql,
|
||||
queryEditorSetTemplateParams,
|
||||
runQueryFromSqlEditor,
|
||||
runQuery,
|
||||
saveQuery,
|
||||
addSavedQueryToTabState,
|
||||
|
||||
Reference in New Issue
Block a user