diff --git a/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx b/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx index 046b2e69c..17bb4d83b 100644 --- a/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx @@ -20,10 +20,19 @@ import React from 'react'; import { shallow } from 'enzyme'; import { defaultQueryEditor, initialState, queries, table } from './fixtures'; +import { + SQL_EDITOR_GUTTER_HEIGHT, + SQL_EDITOR_GUTTER_MARGIN, + SQL_TOOLBAR_HEIGHT, +} from '../../../src/SqlLab/constants'; +import AceEditorWrapper from '../../../src/SqlLab/components/AceEditorWrapper'; import LimitControl from '../../../src/SqlLab/components/LimitControl'; +import SouthPane from '../../../src/SqlLab/components/SouthPane'; import SqlEditor from '../../../src/SqlLab/components/SqlEditor'; import SqlEditorLeftBar from '../../../src/SqlLab/components/SqlEditorLeftBar'; +const MOCKED_SQL_EDITOR_HEIGHT = 500; + describe('SqlEditor', () => { const mockedProps = { actions: {}, @@ -40,7 +49,7 @@ describe('SqlEditor', () => { }; beforeAll(() => { - jest.spyOn(SqlEditor.prototype, 'getSqlEditorHeight').mockImplementation(() => 500); + jest.spyOn(SqlEditor.prototype, 'getSqlEditorHeight').mockImplementation(() => MOCKED_SQL_EDITOR_HEIGHT); }); it('is valid', () => { @@ -52,6 +61,33 @@ describe('SqlEditor', () => { const wrapper = shallow(); expect(wrapper.find(SqlEditorLeftBar)).toHaveLength(1); }); + it('render an AceEditorWrapper', () => { + const wrapper = shallow(); + expect(wrapper.find(AceEditorWrapper)).toHaveLength(1); + }); + it('render an SouthPane', () => { + const wrapper = shallow(); + expect(wrapper.find(SouthPane)).toHaveLength(1); + }); + it('does not overflow the editor window', () => { + const wrapper = shallow(); + const totalSize = parseFloat(wrapper.find(AceEditorWrapper).props().height) + + wrapper.find(SouthPane).props().height + + SQL_TOOLBAR_HEIGHT + + (SQL_EDITOR_GUTTER_MARGIN * 2) + + SQL_EDITOR_GUTTER_HEIGHT; + expect(totalSize).toEqual(MOCKED_SQL_EDITOR_HEIGHT); + }); + it('does not overflow the editor window after resizing', () => { + const wrapper = shallow(); + wrapper.setState({ height: 450 }); + const totalSize = parseFloat(wrapper.find(AceEditorWrapper).props().height) + + wrapper.find(SouthPane).props().height + + SQL_TOOLBAR_HEIGHT + + (SQL_EDITOR_GUTTER_MARGIN * 2) + + SQL_EDITOR_GUTTER_HEIGHT; + expect(totalSize).toEqual(450); + }); it('render a LimitControl with default limit', () => { const defaultQueryLimit = 101; const updatedProps = { ...mockedProps, defaultQueryLimit }; diff --git a/superset/assets/src/SqlLab/components/SqlEditor.jsx b/superset/assets/src/SqlLab/components/SqlEditor.jsx index a4aabb73f..84e510645 100644 --- a/superset/assets/src/SqlLab/components/SqlEditor.jsx +++ b/superset/assets/src/SqlLab/components/SqlEditor.jsx @@ -31,6 +31,7 @@ import { import Split from 'react-split'; import { t } from '@superset-ui/translation'; import debounce from 'lodash/debounce'; +import throttle from 'lodash/throttle'; import Button from '../../components/Button'; import LimitControl from './LimitControl'; @@ -43,17 +44,20 @@ import Timer from '../../components/Timer'; import Hotkeys from '../../components/Hotkeys'; import SqlEditorLeftBar from './SqlEditorLeftBar'; import AceEditorWrapper from './AceEditorWrapper'; -import { STATE_BSSTYLE_MAP } from '../constants'; +import { + STATE_BSSTYLE_MAP, + SQL_EDITOR_GUTTER_HEIGHT, + SQL_EDITOR_GUTTER_MARGIN, + SQL_TOOLBAR_HEIGHT, +} from '../constants'; import RunQueryActionButton from './RunQueryActionButton'; import { FeatureFlag, isFeatureEnabled } from '../../featureFlags'; const SQL_EDITOR_PADDING = 10; -const SQL_TOOLBAR_HEIGHT = 51; -const GUTTER_HEIGHT = 5; -const GUTTER_MARGIN = 3; const INITIAL_NORTH_PERCENT = 30; const INITIAL_SOUTH_PERCENT = 70; const VALIDATION_DEBOUNCE_MS = 600; +const WINDOW_RESIZE_THROTTLE_MS = 100; const propTypes = { actions: PropTypes.object.isRequired, @@ -83,6 +87,8 @@ class SqlEditor extends React.PureComponent { this.state = { autorun: props.queryEditor.autorun, ctas: '', + northPercent: INITIAL_NORTH_PERCENT, + southPercent: INITIAL_SOUTH_PERCENT, sql: props.queryEditor.sql, }; this.sqlEditorRef = React.createRef(); @@ -103,6 +109,10 @@ class SqlEditor extends React.PureComponent { this.requestValidation.bind(this), VALIDATION_DEBOUNCE_MS, ); + this.handleWindowResize = throttle( + this.handleWindowResize.bind(this), + WINDOW_RESIZE_THROTTLE_MS, + ); } componentWillMount() { if (this.state.autorun) { @@ -116,6 +126,11 @@ class SqlEditor extends React.PureComponent { // the south pane so it gets rendered properly // eslint-disable-next-line react/no-did-mount-set-state this.setState({ height: this.getSqlEditorHeight() }); + + window.addEventListener('resize', this.handleWindowResize); + } + componentWillUnmount() { + window.removeEventListener('resize', this.handleWindowResize); } onResizeStart() { // Set the heights on the ace editor and the ace content area after drag starts @@ -124,8 +139,7 @@ class SqlEditor extends React.PureComponent { document.getElementsByClassName('ace_content')[0].style.height = '100%'; } onResizeEnd([northPercent, southPercent]) { - this.setState(this.getAceEditorAndSouthPaneHeights( - this.state.height, northPercent, southPercent)); + this.setState({ northPercent, southPercent }); if (this.northPaneRef.current && this.northPaneRef.current.clientHeight) { this.props.actions.persistEditorHeight(this.props.queryEditor, @@ -149,9 +163,11 @@ class SqlEditor extends React.PureComponent { // given the height of the sql editor, north pane percent and south pane percent. getAceEditorAndSouthPaneHeights(height, northPercent, southPercent) { return { - aceEditorHeight: height * northPercent / 100 - (GUTTER_HEIGHT / 2 + GUTTER_MARGIN) + aceEditorHeight: height * northPercent / 100 + - (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN) - SQL_TOOLBAR_HEIGHT, - southPaneHeight: height * southPercent / 100 - (GUTTER_HEIGHT / 2 + GUTTER_MARGIN), + southPaneHeight: height * southPercent / 100 + - (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN), }; } getHotkeyConfig() { @@ -194,9 +210,12 @@ class SqlEditor extends React.PureComponent { setQueryLimit(queryLimit) { this.props.actions.queryEditorSetQueryLimit(this.props.queryEditor, queryLimit); } + handleWindowResize() { + this.setState({ height: this.getSqlEditorHeight() }); + } elementStyle(dimension, elementSize, gutterSize) { return { - [dimension]: `calc(${elementSize}% - ${gutterSize + GUTTER_MARGIN}px)`, + [dimension]: `calc(${elementSize}% - ${gutterSize + SQL_EDITOR_GUTTER_MARGIN}px)`, }; } requestValidation() { @@ -257,15 +276,18 @@ class SqlEditor extends React.PureComponent { queryPane() { const hotkeys = this.getHotkeyConfig(); const { aceEditorHeight, southPaneHeight } = this.getAceEditorAndSouthPaneHeights( - this.state.height, INITIAL_NORTH_PERCENT, INITIAL_SOUTH_PERCENT); + this.state.height, + this.state.northPercent, + this.state.southPercent, + ); return ( @@ -277,7 +299,7 @@ class SqlEditor extends React.PureComponent { queryEditor={this.props.queryEditor} sql={this.props.queryEditor.sql} tables={this.props.tables} - height={`${this.state.aceEditorHeight || aceEditorHeight}px`} + height={`${aceEditorHeight}px`} hotkeys={hotkeys} /> {this.renderEditorBottomBar(hotkeys)} @@ -286,7 +308,7 @@ class SqlEditor extends React.PureComponent { editorQueries={this.props.editorQueries} dataPreviewQueries={this.props.dataPreviewQueries} actions={this.props.actions} - height={this.state.southPaneHeight || southPaneHeight} + height={southPaneHeight} /> ); diff --git a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx index 43ea4873a..f38964111 100644 --- a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx +++ b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx @@ -33,9 +33,10 @@ const propTypes = { }; const defaultProps = { - tables: [], actions: {}, + height: 500, offline: false, + tables: [], }; export default class SqlEditorLeftBar extends React.PureComponent { diff --git a/superset/assets/src/SqlLab/constants.js b/superset/assets/src/SqlLab/constants.js index 3bf8ce0bf..4dd2118d7 100644 --- a/superset/assets/src/SqlLab/constants.js +++ b/superset/assets/src/SqlLab/constants.js @@ -43,3 +43,8 @@ export const TIME_OPTIONS = [ '90 days ago', '1 year ago', ]; + +// SqlEditor layout constants +export const SQL_EDITOR_GUTTER_HEIGHT = 5; +export const SQL_EDITOR_GUTTER_MARGIN = 3; +export const SQL_TOOLBAR_HEIGHT = 51;