You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by hu...@apache.org on 2022/09/29 20:27:21 UTC

[superset] branch revert-2224ebecf created (now af1347c61e)

This is an automated email from the ASF dual-hosted git repository.

hugh pushed a change to branch revert-2224ebecf
in repository https://gitbox.apache.org/repos/asf/superset.git


      at af1347c61e revert 2224ebecf

This branch includes the following new commits:

     new af1347c61e revert 2224ebecf

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[superset] 01/01: revert 2224ebecf

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hugh pushed a commit to branch revert-2224ebecf
in repository https://gitbox.apache.org/repos/asf/superset.git

commit af1347c61ead9fdc4300440fb4c7668fa832be09
Author: hughhhh <hu...@gmail.com>
AuthorDate: Thu Sep 29 16:26:53 2022 -0400

    revert 2224ebecf
---
 .../src/SqlLab/components/SqlEditor/index.jsx      | 890 ++++++++++++---------
 superset-frontend/src/SqlLab/constants.ts          |   6 -
 2 files changed, 495 insertions(+), 401 deletions(-)

diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
index 3e8ee84f6c..c48594d304 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
@@ -18,15 +18,16 @@
  */
 /* eslint-disable jsx-a11y/anchor-is-valid */
 /* eslint-disable jsx-a11y/no-static-element-interactions */
-import React, { useState, useEffect, useMemo, useRef } from 'react';
+import React from 'react';
 import { CSSTransition } from 'react-transition-group';
-import { useDispatch, useSelector } from 'react-redux';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
 import PropTypes from 'prop-types';
 import Split from 'react-split';
-import { t, styled, useTheme } from '@superset-ui/core';
+import { t, styled, withTheme } from '@superset-ui/core';
 import debounce from 'lodash/debounce';
 import throttle from 'lodash/throttle';
-import Modal from 'src/components/Modal';
+import StyledModal from 'src/components/Modal';
 import Mousetrap from 'mousetrap';
 import Button from 'src/components/Button';
 import Timer from 'src/components/Timer';
@@ -47,6 +48,7 @@ import {
   queryEditorSetAndSaveSql,
   queryEditorSetTemplateParams,
   runQueryFromSqlEditor,
+  runQuery,
   saveQuery,
   addSavedQueryToTabState,
   scheduleQuery,
@@ -60,12 +62,6 @@ import {
   SQL_EDITOR_GUTTER_MARGIN,
   SQL_TOOLBAR_HEIGHT,
   SQL_EDITOR_LEFTBAR_WIDTH,
-  SQL_EDITOR_PADDING,
-  INITIAL_NORTH_PERCENT,
-  INITIAL_SOUTH_PERCENT,
-  SET_QUERY_EDITOR_SQL_DEBOUNCE_MS,
-  VALIDATION_DEBOUNCE_MS,
-  WINDOW_RESIZE_THROTTLE_MS,
 } from 'src/SqlLab/constants';
 import {
   getItem,
@@ -87,6 +83,13 @@ import RunQueryActionButton from '../RunQueryActionButton';
 import { newQueryTabName } from '../../utils/newQueryTabName';
 import QueryLimitSelect from '../QueryLimitSelect';
 
+const SQL_EDITOR_PADDING = 10;
+const INITIAL_NORTH_PERCENT = 30;
+const INITIAL_SOUTH_PERCENT = 70;
+const SET_QUERY_EDITOR_SQL_DEBOUNCE_MS = 2000;
+const VALIDATION_DEBOUNCE_MS = 600;
+const WINDOW_RESIZE_THROTTLE_MS = 100;
+
 const appContainer = document.getElementById('app');
 const bootstrapData = JSON.parse(
   appContainer.getAttribute('data-bootstrap') || '{}',
@@ -129,7 +132,7 @@ const StyledToolbar = styled.div`
 const StyledSidebar = styled.div`
   flex: 0 0 ${({ width }) => width}px;
   width: ${({ width }) => width}px;
-  padding: ${({ theme, hide }) => (hide ? 0 : theme.gridUnit * 2.5)}px;
+  padding: ${({ hide }) => (hide ? 0 : 10)}px;
   border-right: 1px solid
     ${({ theme, hide }) =>
       hide ? 'transparent' : theme.colors.grayscale.light2};
@@ -137,10 +140,13 @@ const StyledSidebar = styled.div`
 
 const propTypes = {
   actions: PropTypes.object.isRequired,
+  database: PropTypes.object,
+  latestQuery: PropTypes.object,
   tables: PropTypes.array.isRequired,
   editorQueries: PropTypes.array.isRequired,
   dataPreviewQueries: PropTypes.array.isRequired,
   queryEditor: PropTypes.object.isRequired,
+  hideLeftBar: PropTypes.bool,
   defaultQueryLimit: PropTypes.number.isRequired,
   maxRow: PropTypes.number.isRequired,
   displayLimit: PropTypes.number.isRequired,
@@ -148,102 +154,158 @@ const propTypes = {
   scheduleQueryWarning: PropTypes.string,
 };
 
-const SqlEditor = ({
-  actions,
-  tables,
-  editorQueries,
-  dataPreviewQueries,
-  queryEditor,
-  defaultQueryLimit,
-  maxRow,
-  displayLimit,
-  saveQueryWarning,
-  scheduleQueryWarning = null,
-}) => {
-  const theme = useTheme();
-  const dispatch = useDispatch();
-
-  const { currentQueryEditor, database, latestQuery, hideLeftBar } =
-    useSelector(({ sqlLab: { unsavedQueryEditor, databases, queries } }) => {
-      const currentQueryEditor = {
-        ...queryEditor,
-        ...(queryEditor.id === unsavedQueryEditor.id && unsavedQueryEditor),
-      };
+const defaultProps = {
+  database: null,
+  latestQuery: null,
+  hideLeftBar: false,
+  scheduleQueryWarning: null,
+};
 
-      let { dbId, latestQueryId, hideLeftBar } = queryEditor;
-      if (unsavedQueryEditor.id === queryEditor.id) {
-        dbId = unsavedQueryEditor.dbId || dbId;
-        latestQueryId = unsavedQueryEditor.latestQueryId || latestQueryId;
-        hideLeftBar = unsavedQueryEditor.hideLeftBar || hideLeftBar;
-      }
-      return {
-        currentQueryEditor,
-        database: databases[dbId],
-        latestQuery: queries[latestQueryId],
-        hideLeftBar,
-      };
+class SqlEditor extends React.PureComponent {
+  constructor(props) {
+    super(props);
+    this.state = {
+      autorun: props.queryEditor.autorun,
+      ctas: '',
+      northPercent: props.queryEditor.northPercent || INITIAL_NORTH_PERCENT,
+      southPercent: props.queryEditor.southPercent || INITIAL_SOUTH_PERCENT,
+      autocompleteEnabled: getItem(
+        LocalStorageKeys.sqllab__is_autocomplete_enabled,
+        true,
+      ),
+      showCreateAsModal: false,
+      createAs: '',
+      showEmptyState: false,
+    };
+    this.sqlEditorRef = React.createRef();
+    this.northPaneRef = React.createRef();
+
+    this.elementStyle = this.elementStyle.bind(this);
+    this.onResizeStart = this.onResizeStart.bind(this);
+    this.onResizeEnd = this.onResizeEnd.bind(this);
+    this.canValidateQuery = this.canValidateQuery.bind(this);
+    this.runQuery = this.runQuery.bind(this);
+    this.setEmptyState = this.setEmptyState.bind(this);
+    this.stopQuery = this.stopQuery.bind(this);
+    this.saveQuery = this.saveQuery.bind(this);
+    this.onSqlChanged = this.onSqlChanged.bind(this);
+    this.setQueryEditorAndSaveSql = this.setQueryEditorAndSaveSql.bind(this);
+    this.setQueryEditorAndSaveSqlWithDebounce = debounce(
+      this.setQueryEditorAndSaveSql.bind(this),
+      SET_QUERY_EDITOR_SQL_DEBOUNCE_MS,
+    );
+    this.queryPane = this.queryPane.bind(this);
+    this.getHotkeyConfig = this.getHotkeyConfig.bind(this);
+    this.getAceEditorAndSouthPaneHeights =
+      this.getAceEditorAndSouthPaneHeights.bind(this);
+    this.getSqlEditorHeight = this.getSqlEditorHeight.bind(this);
+    this.requestValidation = debounce(
+      this.requestValidation.bind(this),
+      VALIDATION_DEBOUNCE_MS,
+    );
+    this.getQueryCostEstimate = this.getQueryCostEstimate.bind(this);
+    this.handleWindowResize = throttle(
+      this.handleWindowResize.bind(this),
+      WINDOW_RESIZE_THROTTLE_MS,
+    );
+
+    this.onBeforeUnload = this.onBeforeUnload.bind(this);
+    this.renderDropdown = this.renderDropdown.bind(this);
+  }
+
+  UNSAFE_componentWillMount() {
+    if (this.state.autorun) {
+      this.setState({ autorun: false });
+      this.props.queryEditorSetAutorun(this.props.queryEditor, false);
+      this.startQuery();
+    }
+  }
+
+  componentDidMount() {
+    // We need to measure the height of the sql editor post render to figure the height of
+    // the south pane so it gets rendered properly
+    // eslint-disable-next-line react/no-did-mount-set-state
+    const db = this.props.database;
+    this.setState({ height: this.getSqlEditorHeight() });
+    if (!db || isEmpty(db)) {
+      this.setEmptyState(true);
+    }
+
+    window.addEventListener('resize', this.handleWindowResize);
+    window.addEventListener('beforeunload', this.onBeforeUnload);
+
+    // setup hotkeys
+    const hotkeys = this.getHotkeyConfig();
+    hotkeys.forEach(keyConfig => {
+      Mousetrap.bind([keyConfig.key], keyConfig.func);
     });
+  }
 
-  const queryEditors = useSelector(({ sqlLab }) => sqlLab.queryEditors);
+  componentWillUnmount() {
+    window.removeEventListener('resize', this.handleWindowResize);
+    window.removeEventListener('beforeunload', this.onBeforeUnload);
+  }
 
-  const [height, setHeight] = useState(0);
-  const [autorun, setAutorun] = useState(queryEditor.autorun);
-  const [ctas, setCtas] = useState('');
-  const [northPercent, setNorthPercent] = useState(
-    queryEditor.northPercent || INITIAL_NORTH_PERCENT,
-  );
-  const [southPercent, setSouthPercent] = useState(
-    queryEditor.southPercent || INITIAL_SOUTH_PERCENT,
-  );
-  const [autocompleteEnabled, setAutocompleteEnabled] = useState(
-    getItem(LocalStorageKeys.sqllab__is_autocomplete_enabled, true),
-  );
-  const [showCreateAsModal, setShowCreateAsModal] = useState(false);
-  const [createAs, setCreateAs] = useState('');
-  const [showEmptyState, setShowEmptyState] = useState(false);
+  onResizeStart() {
+    // Set the heights on the ace editor and the ace content area after drag starts
+    // to smooth out the visual transition to the new heights when drag ends
+    document.getElementsByClassName('ace_content')[0].style.height = '100%';
+  }
 
-  const sqlEditorRef = useRef(null);
-  const northPaneRef = useRef(null);
+  onResizeEnd([northPercent, southPercent]) {
+    this.setState({ northPercent, southPercent });
 
-  const startQuery = (ctasArg = false, ctas_method = CtasEnum.TABLE) => {
-    if (!database) {
-      return;
+    if (this.northPaneRef.current && this.northPaneRef.current.clientHeight) {
+      this.props.persistEditorHeight(
+        this.props.queryEditor,
+        northPercent,
+        southPercent,
+      );
     }
+  }
 
-    dispatch(
-      runQueryFromSqlEditor(
-        database,
-        queryEditor,
-        defaultQueryLimit,
-        ctasArg ? ctas : '',
-        ctasArg,
-        ctas_method,
-      ),
-    );
-    dispatch(setActiveSouthPaneTab('Results'));
-  };
-
-  const stopQuery = () => {
-    if (latestQuery && ['running', 'pending'].indexOf(latestQuery.state) >= 0) {
-      dispatch(postStopQuery(latestQuery));
+  onBeforeUnload(event) {
+    if (
+      this.props.database?.extra_json?.cancel_query_on_windows_unload &&
+      this.props.latestQuery?.state === 'running'
+    ) {
+      event.preventDefault();
+      this.stopQuery();
     }
-  };
+  }
 
-  useState(() => {
-    if (autorun) {
-      setAutorun(false);
-      dispatch(queryEditorSetAutorun(queryEditor, false));
-      startQuery();
+  onSqlChanged(sql) {
+    this.props.queryEditorSetSql(this.props.queryEditor, sql);
+    this.setQueryEditorAndSaveSqlWithDebounce(sql);
+    // Request server-side validation of the query text
+    if (this.canValidateQuery()) {
+      // NB. requestValidation is debounced
+      this.requestValidation(sql);
     }
-  });
+  }
 
   // One layer of abstraction for easy spying in unit tests
-  const getSqlEditorHeight = () =>
-    sqlEditorRef.current
-      ? sqlEditorRef.current.clientHeight - SQL_EDITOR_PADDING * 2
+  getSqlEditorHeight() {
+    return this.sqlEditorRef.current
+      ? this.sqlEditorRef.current.clientHeight - SQL_EDITOR_PADDING * 2
       : 0;
+  }
 
-  const getHotkeyConfig = () => {
+  // Return the heights for the ace editor and the south pane as an object
+  // given the height of the sql editor, north pane percent and south pane percent.
+  getAceEditorAndSouthPaneHeights(height, northPercent, southPercent) {
+    return {
+      aceEditorHeight:
+        (height * northPercent) / 100 -
+        (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN) -
+        SQL_TOOLBAR_HEIGHT,
+      southPaneHeight:
+        (height * southPercent) / 100 -
+        (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN),
+    };
+  }
+
+  getHotkeyConfig() {
     // Get the user's OS
     const userOS = detectOS();
 
@@ -253,8 +315,8 @@ const SqlEditor = ({
         key: 'ctrl+r',
         descr: t('Run query'),
         func: () => {
-          if (queryEditor.sql.trim() !== '') {
-            startQuery();
+          if (this.props.queryEditor.sql.trim() !== '') {
+            this.runQuery();
           }
         },
       },
@@ -263,8 +325,8 @@ const SqlEditor = ({
         key: 'ctrl+enter',
         descr: t('Run query'),
         func: () => {
-          if (queryEditor.sql.trim() !== '') {
-            startQuery();
+          if (this.props.queryEditor.sql.trim() !== '') {
+            this.runQuery();
           }
         },
       },
@@ -273,20 +335,18 @@ const SqlEditor = ({
         key: userOS === 'Windows' ? 'ctrl+q' : 'ctrl+t',
         descr: t('New tab'),
         func: () => {
-          const name = newQueryTabName(queryEditors || []);
-          dispatch(
-            addQueryEditor({
-              ...queryEditor,
-              name,
-            }),
-          );
+          const name = newQueryTabName(this.props.queryEditors || []);
+          this.props.addQueryEditor({
+            ...this.props.queryEditor,
+            name,
+          });
         },
       },
       {
         name: 'stopQuery',
         key: userOS === 'MacOS' ? 'ctrl+x' : 'ctrl+e',
         descr: t('Stop query'),
-        func: stopQuery,
+        func: this.stopQuery,
       },
     ];
 
@@ -302,170 +362,176 @@ const SqlEditor = ({
     }
 
     return base;
-  };
-
-  const handleWindowResize = () => {
-    setHeight(getSqlEditorHeight());
-  };
+  }
 
-  const handleWindowResizeWithThrottle = useMemo(
-    () => throttle(handleWindowResize, WINDOW_RESIZE_THROTTLE_MS),
-    [],
-  );
+  setEmptyState(bool) {
+    this.setState({ showEmptyState: bool });
+  }
 
-  const onBeforeUnload = event => {
-    if (
-      database?.extra_json?.cancel_query_on_windows_unload &&
-      latestQuery?.state === 'running'
-    ) {
-      event.preventDefault();
-      stopQuery();
-    }
-  };
+  setQueryEditorAndSaveSql(sql) {
+    this.props.queryEditorSetAndSaveSql(this.props.queryEditor, sql);
+  }
 
-  useEffect(() => {
-    // We need to measure the height of the sql editor post render to figure the height of
-    // the south pane so it gets rendered properly
-    setHeight(getSqlEditorHeight());
-    if (!database || isEmpty(database)) {
-      setShowEmptyState(true);
+  getQueryCostEstimate() {
+    if (this.props.database) {
+      const qe = this.props.queryEditor;
+      this.props.estimateQueryCost(qe);
     }
+  }
 
-    window.addEventListener('resize', handleWindowResizeWithThrottle);
-    window.addEventListener('beforeunload', onBeforeUnload);
-
-    // setup hotkeys
-    const hotkeys = getHotkeyConfig();
-    hotkeys.forEach(keyConfig => {
-      Mousetrap.bind([keyConfig.key], keyConfig.func);
+  handleToggleAutocompleteEnabled = () => {
+    this.setState(prevState => {
+      setItem(
+        LocalStorageKeys.sqllab__is_autocomplete_enabled,
+        !prevState.autocompleteEnabled,
+      );
+      return {
+        autocompleteEnabled: !prevState.autocompleteEnabled,
+      };
     });
-
-    return () => {
-      window.removeEventListener('resize', handleWindowResizeWithThrottle);
-      window.removeEventListener('beforeunload', onBeforeUnload);
-    };
-  }, []);
-
-  const onResizeStart = () => {
-    // Set the heights on the ace editor and the ace content area after drag starts
-    // to smooth out the visual transition to the new heights when drag ends
-    document.getElementsByClassName('ace_content')[0].style.height = '100%';
   };
 
-  const onResizeEnd = ([northPercent, southPercent]) => {
-    setNorthPercent(northPercent);
-    setSouthPercent(southPercent);
-
-    if (northPaneRef.current?.clientHeight) {
-      dispatch(persistEditorHeight(queryEditor, northPercent, southPercent));
-    }
-  };
+  handleWindowResize() {
+    this.setState({ height: this.getSqlEditorHeight() });
+  }
 
-  const setQueryEditorAndSaveSql = sql => {
-    dispatch(queryEditorSetAndSaveSql(queryEditor, sql));
-  };
+  elementStyle(dimension, elementSize, gutterSize) {
+    return {
+      [dimension]: `calc(${elementSize}% - ${
+        gutterSize + SQL_EDITOR_GUTTER_MARGIN
+      }px)`,
+    };
+  }
 
-  const setQueryEditorAndSaveSqlWithDebounce = useMemo(
-    () => debounce(setQueryEditorAndSaveSql, SET_QUERY_EDITOR_SQL_DEBOUNCE_MS),
-    [],
-  );
+  requestValidation(sql) {
+    const { database, queryEditor, validateQuery } = this.props;
+    if (database) {
+      validateQuery(queryEditor, sql);
+    }
+  }
 
-  const canValidateQuery = () => {
+  canValidateQuery() {
     // Check whether or not we can validate the current query based on whether
     // or not the backend has a validator configured for it.
-    if (database) {
-      return validatorMap.hasOwnProperty(database.backend);
+    if (this.props.database) {
+      return validatorMap.hasOwnProperty(this.props.database.backend);
     }
     return false;
-  };
+  }
 
-  const requestValidation = sql => {
-    if (database) {
-      dispatch(validateQuery(queryEditor, sql));
+  runQuery() {
+    if (this.props.database) {
+      this.startQuery();
     }
-  };
+  }
 
-  const requestValidationWithDebounce = useMemo(
-    () => debounce(requestValidation, VALIDATION_DEBOUNCE_MS),
-    [],
-  );
+  convertToNumWithSpaces(num) {
+    return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1 ');
+  }
 
-  const onSqlChanged = sql => {
-    dispatch(queryEditorSetSql(queryEditor, sql));
-    setQueryEditorAndSaveSqlWithDebounce(sql);
-    // Request server-side validation of the query text
-    if (canValidateQuery()) {
-      // NB. requestValidation is debounced
-      requestValidationWithDebounce(sql);
-    }
-  };
+  startQuery(ctas = false, ctas_method = CtasEnum.TABLE) {
+    const {
+      database,
+      runQueryFromSqlEditor,
+      setActiveSouthPaneTab,
+      queryEditor,
+      defaultQueryLimit,
+    } = this.props;
+    runQueryFromSqlEditor(
+      database,
+      queryEditor,
+      defaultQueryLimit,
+      ctas ? this.state.ctas : '',
+      ctas,
+      ctas_method,
+    );
+    setActiveSouthPaneTab('Results');
+  }
 
-  // Return the heights for the ace editor and the south pane as an object
-  // given the height of the sql editor, north pane percent and south pane percent.
-  const getAceEditorAndSouthPaneHeights = (
-    height,
-    northPercent,
-    southPercent,
-  ) => ({
-    aceEditorHeight:
-      (height * northPercent) / (theme.gridUnit * 25) -
-      (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN) -
-      SQL_TOOLBAR_HEIGHT,
-    southPaneHeight:
-      (height * southPercent) / (theme.gridUnit * 25) -
-      (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN),
-  });
-
-  const getQueryCostEstimate = () => {
-    if (database) {
-      dispatch(estimateQueryCost(queryEditor));
+  stopQuery() {
+    if (
+      this.props.latestQuery &&
+      ['running', 'pending'].indexOf(this.props.latestQuery.state) >= 0
+    ) {
+      this.props.postStopQuery(this.props.latestQuery);
     }
-  };
-
-  const handleToggleAutocompleteEnabled = () => {
-    setItem(
-      LocalStorageKeys.sqllab__is_autocomplete_enabled,
-      !autocompleteEnabled,
-    );
-    setAutocompleteEnabled(!autocompleteEnabled);
-  };
+  }
 
-  const elementStyle = (dimension, elementSize, gutterSize) => ({
-    [dimension]: `calc(${elementSize}% - ${
-      gutterSize + SQL_EDITOR_GUTTER_MARGIN
-    }px)`,
-  });
+  createTableAs() {
+    this.startQuery(true, CtasEnum.TABLE);
+    this.setState({ showCreateAsModal: false, ctas: '' });
+  }
 
-  const createTableAs = () => {
-    startQuery(true, CtasEnum.TABLE);
-    setShowCreateAsModal(false);
-    setCtas('');
-  };
+  createViewAs() {
+    this.startQuery(true, CtasEnum.VIEW);
+    this.setState({ showCreateAsModal: false, ctas: '' });
+  }
 
-  const createViewAs = () => {
-    startQuery(true, CtasEnum.VIEW);
-    setShowCreateAsModal(false);
-    setCtas('');
-  };
+  ctasChanged(event) {
+    this.setState({ ctas: event.target.value });
+  }
 
-  const ctasChanged = event => {
-    setCtas(event.target.value);
-  };
+  queryPane() {
+    const hotkeys = this.getHotkeyConfig();
+    const { aceEditorHeight, southPaneHeight } =
+      this.getAceEditorAndSouthPaneHeights(
+        this.state.height,
+        this.state.northPercent,
+        this.state.southPercent,
+      );
+    return (
+      <Split
+        expandToMin
+        className="queryPane"
+        sizes={[this.state.northPercent, this.state.southPercent]}
+        elementStyle={this.elementStyle}
+        minSize={200}
+        direction="vertical"
+        gutterSize={SQL_EDITOR_GUTTER_HEIGHT}
+        onDragStart={this.onResizeStart}
+        onDragEnd={this.onResizeEnd}
+      >
+        <div ref={this.northPaneRef} className="north-pane">
+          <AceEditorWrapper
+            actions={this.props.actions}
+            autocomplete={this.state.autocompleteEnabled}
+            onBlur={this.setQueryEditorSql}
+            onChange={this.onSqlChanged}
+            queryEditor={this.props.queryEditor}
+            database={this.props.database}
+            extendedTables={this.props.tables}
+            height={`${aceEditorHeight}px`}
+            hotkeys={hotkeys}
+          />
+          {this.renderEditorBottomBar(hotkeys)}
+        </div>
+        <ConnectedSouthPane
+          editorQueries={this.props.editorQueries}
+          latestQueryId={this.props.latestQuery && this.props.latestQuery.id}
+          dataPreviewQueries={this.props.dataPreviewQueries}
+          actions={this.props.actions}
+          height={southPaneHeight}
+          displayLimit={this.props.displayLimit}
+          defaultQueryLimit={this.props.defaultQueryLimit}
+        />
+      </Split>
+    );
+  }
 
-  const renderDropdown = () => {
-    const qe = queryEditor;
-    const successful = latestQuery?.state === 'success';
+  renderDropdown() {
+    const qe = this.props.queryEditor;
+    const successful = this.props.latestQuery?.state === 'success';
     const scheduleToolTip = successful
       ? t('Schedule the query periodically')
       : t('You must run the query successfully first');
     return (
-      <Menu css={{ width: theme.gridUnit * 44 }}>
-        <Menu.Item css={{ display: 'flex', justifyContent: 'space-between' }}>
+      <Menu onClick={this.handleMenuClick} style={{ width: 176 }}>
+        <Menu.Item style={{ display: 'flex', justifyContent: 'space-between' }}>
           {' '}
           <span>{t('Autocomplete')}</span>{' '}
           <AntdSwitch
-            checked={autocompleteEnabled}
-            onChange={handleToggleAutocompleteEnabled}
+            checked={this.state.autocompleteEnabled}
+            onChange={this.handleToggleAutocompleteEnabled}
             name="autocomplete-switch"
           />{' '}
         </Menu.Item>
@@ -474,7 +540,7 @@ const SqlEditor = ({
             <TemplateParamsEditor
               language="json"
               onChange={params => {
-                dispatch(queryEditorSetTemplateParams(qe, params));
+                this.props.actions.queryEditorSetTemplateParams(qe, params);
               }}
               queryEditor={qe}
             />
@@ -485,10 +551,10 @@ const SqlEditor = ({
             <ScheduleQueryButton
               defaultLabel={qe.name}
               sql={qe.sql}
-              onSchedule={query => dispatch(scheduleQuery(query))}
+              onSchedule={this.props.actions.scheduleQuery}
               schema={qe.schema}
               dbId={qe.dbId}
-              scheduleQueryWarning={scheduleQueryWarning}
+              scheduleQueryWarning={this.props.scheduleQueryWarning}
               tooltip={scheduleToolTip}
               disabled={!successful}
             />
@@ -496,24 +562,31 @@ const SqlEditor = ({
         )}
       </Menu>
     );
-  };
+  }
 
-  const onSaveQuery = async query => {
-    const savedQuery = await dispatch(saveQuery(query));
-    dispatch(addSavedQueryToTabState(queryEditor, savedQuery));
-  };
+  async saveQuery(query) {
+    const { queryEditor: qe, actions } = this.props;
+    const savedQuery = await actions.saveQuery(query);
+    actions.addSavedQueryToTabState(qe, savedQuery);
+  }
+
+  renderEditorBottomBar() {
+    const { queryEditor: qe } = this.props;
 
-  const renderEditorBottomBar = () => {
-    const { allow_ctas: allowCTAS, allow_cvas: allowCVAS } = database || {};
+    const { allow_ctas: allowCTAS, allow_cvas: allowCVAS } =
+      this.props.database || {};
 
     const showMenu = allowCTAS || allowCVAS;
+    const { theme } = this.props;
     const runMenuBtn = (
       <Menu>
         {allowCTAS && (
           <Menu.Item
             onClick={() => {
-              setShowCreateAsModal(true);
-              setCreateAs(CtasEnum.TABLE);
+              this.setState({
+                showCreateAsModal: true,
+                createAs: CtasEnum.TABLE,
+              });
             }}
             key="1"
           >
@@ -523,8 +596,10 @@ const SqlEditor = ({
         {allowCVAS && (
           <Menu.Item
             onClick={() => {
-              setShowCreateAsModal(true);
-              setCreateAs(CtasEnum.VIEW);
+              this.setState({
+                showCreateAsModal: true,
+                createAs: CtasEnum.VIEW,
+              });
             }}
             key="2"
           >
@@ -539,189 +614,214 @@ const SqlEditor = ({
         <div className="leftItems">
           <span>
             <RunQueryActionButton
-              allowAsync={database ? database.allow_run_async : false}
-              queryEditor={queryEditor}
-              queryState={latestQuery?.state}
-              runQuery={startQuery}
-              stopQuery={stopQuery}
+              allowAsync={
+                this.props.database
+                  ? this.props.database.allow_run_async
+                  : false
+              }
+              queryEditor={qe}
+              queryState={this.props.latestQuery?.state}
+              runQuery={this.runQuery}
+              stopQuery={this.stopQuery}
               overlayCreateAsMenu={showMenu ? runMenuBtn : null}
             />
           </span>
           {isFeatureEnabled(FeatureFlag.ESTIMATE_QUERY_COST) &&
-            database?.allows_cost_estimate && (
+            this.props.database &&
+            this.props.database.allows_cost_estimate && (
               <span>
                 <EstimateQueryCostButton
-                  getEstimate={getQueryCostEstimate}
-                  queryEditor={queryEditor}
+                  getEstimate={this.getQueryCostEstimate}
+                  queryEditor={qe}
                   tooltip={t('Estimate the cost before running a query')}
                 />
               </span>
             )}
           <span>
             <QueryLimitSelect
-              queryEditor={queryEditor}
-              maxRow={maxRow}
-              defaultQueryLimit={defaultQueryLimit}
+              queryEditor={this.props.queryEditor}
+              maxRow={this.props.maxRow}
+              defaultQueryLimit={this.props.defaultQueryLimit}
             />
           </span>
-          {latestQuery && (
+          {this.props.latestQuery && (
             <Timer
-              startTime={latestQuery.startDttm}
-              endTime={latestQuery.endDttm}
-              state={STATE_TYPE_MAP[latestQuery.state]}
-              isRunning={latestQuery.state === 'running'}
+              startTime={this.props.latestQuery.startDttm}
+              endTime={this.props.latestQuery.endDttm}
+              state={STATE_TYPE_MAP[this.props.latestQuery.state]}
+              isRunning={this.props.latestQuery.state === 'running'}
             />
           )}
         </div>
         <div className="rightItems">
           <span>
             <SaveQuery
-              queryEditor={queryEditor}
-              columns={latestQuery?.results?.columns || []}
-              onSave={onSaveQuery}
-              onUpdate={query => dispatch(updateSavedQuery(query))}
-              saveQueryWarning={saveQueryWarning}
-              database={database}
+              queryEditor={qe}
+              columns={this.props.latestQuery?.results?.columns || []}
+              onSave={this.saveQuery}
+              onUpdate={this.props.actions.updateSavedQuery}
+              saveQueryWarning={this.props.saveQueryWarning}
+              database={this.props.database}
             />
           </span>
           <span>
-            <ShareSqlLabQuery queryEditor={queryEditor} />
+            <ShareSqlLabQuery queryEditor={qe} />
           </span>
-          <AntdDropdown overlay={renderDropdown()} trigger="click">
+          <AntdDropdown overlay={this.renderDropdown()} trigger="click">
             <Icons.MoreHoriz iconColor={theme.colors.grayscale.base} />
           </AntdDropdown>
         </div>
       </StyledToolbar>
     );
-  };
+  }
 
-  const queryPane = () => {
-    const hotkeys = getHotkeyConfig();
-    const { aceEditorHeight, southPaneHeight } =
-      getAceEditorAndSouthPaneHeights(height, northPercent, southPercent);
+  render() {
+    const createViewModalTitle =
+      this.state.createAs === CtasEnum.VIEW
+        ? 'CREATE VIEW AS'
+        : 'CREATE TABLE AS';
+
+    const createModalPlaceHolder =
+      this.state.createAs === CtasEnum.VIEW
+        ? 'Specify name to CREATE VIEW AS schema in: public'
+        : 'Specify name to CREATE TABLE AS schema in: public';
+    const leftBarStateClass = this.props.hideLeftBar
+      ? 'schemaPane-exit-done'
+      : 'schemaPane-enter-done';
     return (
-      <Split
-        expandToMin
-        className="queryPane"
-        sizes={[northPercent, southPercent]}
-        elementStyle={elementStyle}
-        minSize={200}
-        direction="vertical"
-        gutterSize={SQL_EDITOR_GUTTER_HEIGHT}
-        onDragStart={onResizeStart}
-        onDragEnd={onResizeEnd}
-      >
-        <div ref={northPaneRef} className="north-pane">
-          <AceEditorWrapper
-            autocomplete={autocompleteEnabled}
-            onBlur={setQueryEditorAndSaveSql}
-            onChange={onSqlChanged}
-            queryEditor={currentQueryEditor}
-            database={database}
-            extendedTables={tables}
-            height={`${aceEditorHeight}px`}
-            hotkeys={hotkeys}
-          />
-          {renderEditorBottomBar(hotkeys)}
-        </div>
-        <ConnectedSouthPane
-          editorQueries={editorQueries}
-          latestQueryId={latestQuery?.id}
-          dataPreviewQueries={dataPreviewQueries}
-          actions={actions}
-          height={southPaneHeight}
-          displayLimit={displayLimit}
-          defaultQueryLimit={defaultQueryLimit}
-        />
-      </Split>
-    );
-  };
-
-  const createViewModalTitle =
-    createAs === CtasEnum.VIEW ? 'CREATE VIEW AS' : 'CREATE TABLE AS';
-
-  const createModalPlaceHolder =
-    createAs === CtasEnum.VIEW
-      ? t('Specify name to CREATE VIEW AS schema in: public')
-      : t('Specify name to CREATE TABLE AS schema in: public');
-
-  const leftBarStateClass = hideLeftBar
-    ? 'schemaPane-exit-done'
-    : 'schemaPane-enter-done';
-  return (
-    <div ref={sqlEditorRef} className="SqlEditor">
-      <CSSTransition classNames="schemaPane" in={!hideLeftBar} timeout={300}>
-        <ResizableSidebar
-          id={`sqllab:${queryEditor.id}`}
-          minWidth={SQL_EDITOR_LEFTBAR_WIDTH}
-          initialWidth={SQL_EDITOR_LEFTBAR_WIDTH}
-          enable={!hideLeftBar}
+      <div ref={this.sqlEditorRef} className="SqlEditor">
+        <CSSTransition
+          classNames="schemaPane"
+          in={!this.props.hideLeftBar}
+          timeout={300}
         >
-          {adjustedWidth => (
-            <StyledSidebar
-              className={`schemaPane ${leftBarStateClass}`}
-              width={adjustedWidth}
-              hide={hideLeftBar}
-            >
-              <SqlEditorLeftBar
-                database={database}
-                queryEditor={queryEditor}
-                tables={tables}
-                actions={actions}
-                setEmptyState={bool => setShowEmptyState(bool)}
-              />
-            </StyledSidebar>
-          )}
-        </ResizableSidebar>
-      </CSSTransition>
-      {showEmptyState ? (
-        <EmptyStateBig
-          image="vector.svg"
-          title={t('Select a database to write a query')}
-          description={t(
-            'Choose one of the available databases from the panel on the left.',
-          )}
-        />
-      ) : (
-        queryPane()
-      )}
-      <Modal
-        visible={showCreateAsModal}
-        title={t(createViewModalTitle)}
-        onHide={() => setShowCreateAsModal(false)}
-        footer={
-          <>
-            <Button onClick={() => setShowCreateAsModal(false)}>
-              {t('Cancel')}
-            </Button>
-            {createAs === CtasEnum.TABLE && (
-              <Button
-                buttonStyle="primary"
-                disabled={ctas.length === 0}
-                onClick={createTableAs}
+          <ResizableSidebar
+            id={`sqllab:${this.props.queryEditor.id}`}
+            minWidth={SQL_EDITOR_LEFTBAR_WIDTH}
+            initialWidth={SQL_EDITOR_LEFTBAR_WIDTH}
+            enable={!this.props.hideLeftBar}
+          >
+            {adjustedWidth => (
+              <StyledSidebar
+                className={`schemaPane ${leftBarStateClass}`}
+                width={adjustedWidth}
+                hide={this.props.hideLeftBar}
               >
-                {t('Create')}
-              </Button>
+                <SqlEditorLeftBar
+                  database={this.props.database}
+                  queryEditor={this.props.queryEditor}
+                  tables={this.props.tables}
+                  actions={this.props.actions}
+                  setEmptyState={this.setEmptyState}
+                />
+              </StyledSidebar>
+            )}
+          </ResizableSidebar>
+        </CSSTransition>
+        {this.state.showEmptyState ? (
+          <EmptyStateBig
+            image="vector.svg"
+            title={t('Select a database to write a query')}
+            description={t(
+              'Choose one of the available databases from the panel on the left.',
             )}
-            {createAs === CtasEnum.VIEW && (
+          />
+        ) : (
+          this.queryPane()
+        )}
+        <StyledModal
+          visible={this.state.showCreateAsModal}
+          title={t(createViewModalTitle)}
+          onHide={() => {
+            this.setState({ showCreateAsModal: false });
+          }}
+          footer={
+            <>
               <Button
-                buttonStyle="primary"
-                disabled={ctas.length === 0}
-                onClick={createViewAs}
+                onClick={() => this.setState({ showCreateAsModal: false })}
               >
-                {t('Create')}
+                Cancel
               </Button>
-            )}
-          </>
-        }
-      >
-        <span>{t('Name')}</span>
-        <Input placeholder={createModalPlaceHolder} onChange={ctasChanged} />
-      </Modal>
-    </div>
-  );
-};
-
+              {this.state.createAs === CtasEnum.TABLE && (
+                <Button
+                  buttonStyle="primary"
+                  disabled={this.state.ctas.length === 0}
+                  onClick={this.createTableAs.bind(this)}
+                >
+                  Create
+                </Button>
+              )}
+              {this.state.createAs === CtasEnum.VIEW && (
+                <Button
+                  buttonStyle="primary"
+                  disabled={this.state.ctas.length === 0}
+                  onClick={this.createViewAs.bind(this)}
+                >
+                  Create
+                </Button>
+              )}
+            </>
+          }
+        >
+          <span>Name</span>
+          <Input
+            placeholder={createModalPlaceHolder}
+            onChange={this.ctasChanged.bind(this)}
+          />
+        </StyledModal>
+      </div>
+    );
+  }
+}
+SqlEditor.defaultProps = defaultProps;
 SqlEditor.propTypes = propTypes;
 
-export default SqlEditor;
+function mapStateToProps({ sqlLab }, { queryEditor }) {
+  let { latestQueryId, dbId, hideLeftBar } = queryEditor;
+  if (sqlLab.unsavedQueryEditor.id === queryEditor.id) {
+    const {
+      latestQueryId: unsavedQID,
+      dbId: unsavedDBID,
+      hideLeftBar: unsavedHideLeftBar,
+    } = sqlLab.unsavedQueryEditor;
+    latestQueryId = unsavedQID || latestQueryId;
+    dbId = unsavedDBID || dbId;
+    hideLeftBar = unsavedHideLeftBar || hideLeftBar;
+  }
+  const database = sqlLab.databases[dbId];
+  const latestQuery = sqlLab.queries[latestQueryId];
+
+  return {
+    hideLeftBar,
+    queryEditors: sqlLab.queryEditors,
+    latestQuery,
+    database,
+  };
+}
+
+function mapDispatchToProps(dispatch) {
+  return bindActionCreators(
+    {
+      addQueryEditor,
+      estimateQueryCost,
+      persistEditorHeight,
+      postStopQuery,
+      queryEditorSetAutorun,
+      queryEditorSetSql,
+      queryEditorSetAndSaveSql,
+      queryEditorSetTemplateParams,
+      runQueryFromSqlEditor,
+      runQuery,
+      saveQuery,
+      addSavedQueryToTabState,
+      scheduleQuery,
+      setActiveSouthPaneTab,
+      updateSavedQuery,
+      validateQuery,
+    },
+    dispatch,
+  );
+}
+
+const themedSqlEditor = withTheme(SqlEditor);
+export default connect(mapStateToProps, mapDispatchToProps)(themedSqlEditor);
diff --git a/superset-frontend/src/SqlLab/constants.ts b/superset-frontend/src/SqlLab/constants.ts
index 29b0f6cf6b..11d990032d 100644
--- a/superset-frontend/src/SqlLab/constants.ts
+++ b/superset-frontend/src/SqlLab/constants.ts
@@ -49,12 +49,6 @@ export const SQL_EDITOR_GUTTER_HEIGHT = 5;
 export const SQL_EDITOR_GUTTER_MARGIN = 3;
 export const SQL_TOOLBAR_HEIGHT = 51;
 export const SQL_EDITOR_LEFTBAR_WIDTH = 400;
-export const SQL_EDITOR_PADDING = 10;
-export const INITIAL_NORTH_PERCENT = 30;
-export const INITIAL_SOUTH_PERCENT = 70;
-export const SET_QUERY_EDITOR_SQL_DEBOUNCE_MS = 2000;
-export const VALIDATION_DEBOUNCE_MS = 600;
-export const WINDOW_RESIZE_THROTTLE_MS = 100;
 
 // kilobyte storage
 export const KB_STORAGE = 1024;