mirror of https://github.com/apache/superset.git
Relayout SQL Editor (#6872)
* Relayout SQL Editor - Refactor SQL editor to remove usage of bootstrap col, row and collapse to simplify the layout - Replace the react-split-pane libraray with react-split to allow custom styling of the gutter area without sacrifice correctness of the ace editor height calculation - Rewrite the left pane animation via plain css transition and animate it to slide in and out - General code and css clean up * Smooth out the visual transition during dragging (cherry picked from commit 19f82b729c7a939f12b1c5da6022c0fd76fa3ec9) * Adjust how the height of the south pane is computed, fixing cypress tests
This commit is contained in:
parent
8302b9a276
commit
ec6657ab2d
|
@ -17339,6 +17339,15 @@
|
||||||
"prop-types": "^15.5.7"
|
"prop-types": "^15.5.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-split": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-split/-/react-split-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-NBKm9MaqzG/00laMUaS8GS9RnItVSekNNwItgGLMbFTeUa9w4bIY8Co/LszNBnpza9n2am0MXIw3SmyiMnhs+w==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.5.7",
|
||||||
|
"split.js": "^1.5.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-split-pane": {
|
"react-split-pane": {
|
||||||
"version": "0.1.85",
|
"version": "0.1.85",
|
||||||
"resolved": "https://registry.npmjs.org/react-split-pane/-/react-split-pane-0.1.85.tgz",
|
"resolved": "https://registry.npmjs.org/react-split-pane/-/react-split-pane-0.1.85.tgz",
|
||||||
|
@ -17380,6 +17389,17 @@
|
||||||
"refractor": "^2.4.1"
|
"refractor": "^2.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-transition-group": {
|
||||||
|
"version": "2.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.3.tgz",
|
||||||
|
"integrity": "sha512-2DGFck6h99kLNr8pOFk+z4Soq3iISydwOFeeEVPjTN6+Y01CmvbWmnN02VuTWyFdnRtIDPe+wy2q6Ui8snBPZg==",
|
||||||
|
"requires": {
|
||||||
|
"dom-helpers": "^3.3.1",
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-lifecycles-compat": "^3.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-virtualized": {
|
"react-virtualized": {
|
||||||
"version": "9.19.1",
|
"version": "9.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.19.1.tgz",
|
||||||
|
@ -19391,6 +19411,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"split.js": {
|
||||||
|
"version": "1.5.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/split.js/-/split.js-1.5.10.tgz",
|
||||||
|
"integrity": "sha512-/J52X5c4ZypVwu4WAhD8E1T9uXQtNokvG6mIBHauzyA1aKH6bmETVSv3RPjBXEz6Gcc4mIThgmjGQL39LD16jQ=="
|
||||||
|
},
|
||||||
"sprintf-js": {
|
"sprintf-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
"resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
|
|
|
@ -123,9 +123,10 @@
|
||||||
"react-select": "1.2.1",
|
"react-select": "1.2.1",
|
||||||
"react-select-fast-filter-options": "^0.2.1",
|
"react-select-fast-filter-options": "^0.2.1",
|
||||||
"react-sortable-hoc": "^0.8.3",
|
"react-sortable-hoc": "^0.8.3",
|
||||||
"react-split-pane": "^0.1.66",
|
"react-split": "^2.0.4",
|
||||||
"react-sticky": "^6.0.2",
|
"react-sticky": "^6.0.2",
|
||||||
"react-syntax-highlighter": "^7.0.4",
|
"react-syntax-highlighter": "^7.0.4",
|
||||||
|
"react-transition-group": "^2.5.3",
|
||||||
"react-virtualized": "9.19.1",
|
"react-virtualized": "9.19.1",
|
||||||
"react-virtualized-select": "^3.1.3",
|
"react-virtualized-select": "^3.1.3",
|
||||||
"reactable-arc": "0.14.42",
|
"reactable-arc": "0.14.42",
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { STATUS_OPTIONS } from '../../../src/SqlLab/constants';
|
import { STATUS_OPTIONS } from '../../../src/SqlLab/constants';
|
||||||
import { initialState } from './fixtures';
|
import { initialState } from './fixtures';
|
||||||
import SouthPane from '../../../src/SqlLab/components/SouthPane';
|
import SouthPaneContainer, { SouthPane } from '../../../src/SqlLab/components/SouthPane';
|
||||||
|
|
||||||
describe('SouthPane', () => {
|
describe('SouthPane', () => {
|
||||||
const middlewares = [thunk];
|
const middlewares = [thunk];
|
||||||
|
@ -42,11 +42,16 @@ describe('SouthPane', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getWrapper = () => (
|
const getWrapper = () => (
|
||||||
shallow(<SouthPane {...mockedProps} />, {
|
shallow(<SouthPaneContainer {...mockedProps} />, {
|
||||||
context: { store },
|
context: { store },
|
||||||
}).dive());
|
}).dive());
|
||||||
|
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.spyOn(SouthPane.prototype, 'getSouthPaneHeight').mockImplementation(() => 500);
|
||||||
|
});
|
||||||
|
|
||||||
it('should render offline when the state is offline', () => {
|
it('should render offline when the state is offline', () => {
|
||||||
wrapper = getWrapper();
|
wrapper = getWrapper();
|
||||||
wrapper.setProps({ offline: true });
|
wrapper.setProps({ offline: true });
|
||||||
|
|
|
@ -38,6 +38,11 @@ describe('SqlEditor', () => {
|
||||||
defaultQueryLimit: 1000,
|
defaultQueryLimit: 1000,
|
||||||
maxRow: 100000,
|
maxRow: 100000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.spyOn(SqlEditor.prototype, 'getSqlEditorHeight').mockImplementation(() => 500);
|
||||||
|
});
|
||||||
|
|
||||||
it('is valid', () => {
|
it('is valid', () => {
|
||||||
expect(
|
expect(
|
||||||
React.isValidElement(<SqlEditor {...mockedProps} />),
|
React.isValidElement(<SqlEditor {...mockedProps} />),
|
||||||
|
|
|
@ -84,7 +84,7 @@ class App extends React.PureComponent {
|
||||||
content = (
|
content = (
|
||||||
<div>
|
<div>
|
||||||
<QueryAutoRefresh />
|
<QueryAutoRefresh />
|
||||||
<TabbedSqlEditors getHeight={this.getHeight} />
|
<TabbedSqlEditors />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,24 @@ const defaultProps = {
|
||||||
offline: false,
|
offline: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SouthPane extends React.PureComponent {
|
export class SouthPane extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
height: props.height,
|
||||||
|
};
|
||||||
|
this.southPaneRef = React.createRef();
|
||||||
|
this.getSouthPaneHeight = this.getSouthPaneHeight.bind(this);
|
||||||
|
this.switchTab = this.switchTab.bind(this);
|
||||||
|
}
|
||||||
|
componentWillReceiveProps() {
|
||||||
|
// south pane expands the entire height of the tab content on mount
|
||||||
|
this.setState({ height: this.getSouthPaneHeight() });
|
||||||
|
}
|
||||||
|
// One layer of abstraction for easy spying in unit tests
|
||||||
|
getSouthPaneHeight() {
|
||||||
|
return this.southPaneRef.current.clientHeight;
|
||||||
|
}
|
||||||
switchTab(id) {
|
switchTab(id) {
|
||||||
this.props.actions.setActiveSouthPaneTab(id);
|
this.props.actions.setActiveSouthPaneTab(id);
|
||||||
}
|
}
|
||||||
|
@ -59,7 +76,7 @@ class SouthPane extends React.PureComponent {
|
||||||
{ STATUS_OPTIONS.offline }
|
{ STATUS_OPTIONS.offline }
|
||||||
</Label>);
|
</Label>);
|
||||||
}
|
}
|
||||||
const innerTabHeight = this.props.height - 55;
|
const innerTabHeight = this.state.height - 55;
|
||||||
let latestQuery;
|
let latestQuery;
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
if (props.editorQueries.length > 0) {
|
if (props.editorQueries.length > 0) {
|
||||||
|
@ -98,12 +115,12 @@ class SouthPane extends React.PureComponent {
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="SouthPane">
|
<div className="SouthPane" ref={this.southPaneRef}>
|
||||||
<Tabs
|
<Tabs
|
||||||
bsStyle="tabs"
|
bsStyle="tabs"
|
||||||
id={shortid.generate()}
|
id={shortid.generate()}
|
||||||
activeKey={this.props.activeSouthPaneTab}
|
activeKey={this.props.activeSouthPaneTab}
|
||||||
onSelect={this.switchTab.bind(this)}
|
onSelect={this.switchTab}
|
||||||
>
|
>
|
||||||
<Tab
|
<Tab
|
||||||
title={t('Results')}
|
title={t('Results')}
|
||||||
|
|
|
@ -17,21 +17,18 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { CSSTransition } from 'react-transition-group';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { throttle } from 'lodash';
|
|
||||||
import {
|
import {
|
||||||
Col,
|
|
||||||
FormGroup,
|
FormGroup,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
Label,
|
Label,
|
||||||
OverlayTrigger,
|
OverlayTrigger,
|
||||||
Row,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Collapse,
|
|
||||||
} from 'react-bootstrap';
|
} from 'react-bootstrap';
|
||||||
import SplitPane from 'react-split-pane';
|
import Split from 'react-split';
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
|
|
||||||
import Button from '../../components/Button';
|
import Button from '../../components/Button';
|
||||||
|
@ -47,9 +44,13 @@ import AceEditorWrapper from './AceEditorWrapper';
|
||||||
import { STATE_BSSTYLE_MAP } from '../constants';
|
import { STATE_BSSTYLE_MAP } from '../constants';
|
||||||
import RunQueryActionButton from './RunQueryActionButton';
|
import RunQueryActionButton from './RunQueryActionButton';
|
||||||
|
|
||||||
|
const SQL_TOOLBAR_HEIGHT = 51;
|
||||||
|
const GUTTER_HEIGHT = 5;
|
||||||
|
const INITIAL_NORTH_PERCENT = 30;
|
||||||
|
const INITIAL_SOUTH_PERCENT = 70;
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
actions: PropTypes.object.isRequired,
|
actions: PropTypes.object.isRequired,
|
||||||
getHeight: PropTypes.func.isRequired,
|
|
||||||
database: PropTypes.object,
|
database: PropTypes.object,
|
||||||
latestQuery: PropTypes.object,
|
latestQuery: PropTypes.object,
|
||||||
tables: PropTypes.array.isRequired,
|
tables: PropTypes.array.isRequired,
|
||||||
|
@ -75,13 +76,18 @@ class SqlEditor extends React.PureComponent {
|
||||||
ctas: '',
|
ctas: '',
|
||||||
sql: props.queryEditor.sql,
|
sql: props.queryEditor.sql,
|
||||||
};
|
};
|
||||||
|
this.sqlEditorRef = React.createRef();
|
||||||
|
this.northPaneRef = React.createRef();
|
||||||
|
|
||||||
this.onResize = this.onResize.bind(this);
|
this.onResizeStart = this.onResizeStart.bind(this);
|
||||||
this.throttledResize = throttle(this.onResize, 250);
|
this.onResizeEnd = this.onResizeEnd.bind(this);
|
||||||
this.runQuery = this.runQuery.bind(this);
|
this.runQuery = this.runQuery.bind(this);
|
||||||
this.stopQuery = this.stopQuery.bind(this);
|
this.stopQuery = this.stopQuery.bind(this);
|
||||||
this.onSqlChanged = this.onSqlChanged.bind(this);
|
this.onSqlChanged = this.onSqlChanged.bind(this);
|
||||||
this.setQueryEditorSql = this.setQueryEditorSql.bind(this);
|
this.setQueryEditorSql = this.setQueryEditorSql.bind(this);
|
||||||
|
this.queryPane = this.queryPane.bind(this);
|
||||||
|
this.getAceEditorAndSouthPaneHeights = this.getAceEditorAndSouthPaneHeights.bind(this);
|
||||||
|
this.getSqlEditorHeight = this.getSqlEditorHeight.bind(this);
|
||||||
}
|
}
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
if (this.state.autorun) {
|
if (this.state.autorun) {
|
||||||
|
@ -91,29 +97,41 @@ class SqlEditor extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.onResize();
|
// We need to measure the height of the sql editor post render to figure the height of
|
||||||
window.addEventListener('resize', this.throttledResize);
|
// the south pane so it gets rendered properly
|
||||||
|
// eslint-disable-next-line react/no-did-mount-set-state
|
||||||
|
this.setState({ height: this.getSqlEditorHeight() });
|
||||||
}
|
}
|
||||||
componentWillUnmount() {
|
onResizeStart() {
|
||||||
window.removeEventListener('resize', this.throttledResize);
|
// 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.getElementById('brace-editor').style.height = `calc(100% - ${SQL_TOOLBAR_HEIGHT}px)`;
|
||||||
|
document.getElementsByClassName('ace_content')[0].style.height = '100%';
|
||||||
}
|
}
|
||||||
onResize() {
|
onResizeEnd([northPercent, southPercent]) {
|
||||||
const height = this.sqlEditorHeight();
|
this.setState(this.getAceEditorAndSouthPaneHeights(
|
||||||
const editorPaneHeight = this.props.queryEditor.height || 200;
|
this.state.height, northPercent, southPercent));
|
||||||
const splitPaneHandlerHeight = 8; // 4px of height + 4px of top-margin
|
|
||||||
this.setState({
|
|
||||||
editorPaneHeight,
|
|
||||||
southPaneHeight: height - editorPaneHeight - splitPaneHandlerHeight,
|
|
||||||
height,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.refs.ace && this.refs.ace.clientHeight) {
|
if (this.northPaneRef.current && this.northPaneRef.current.clientHeight) {
|
||||||
this.props.actions.persistEditorHeight(this.props.queryEditor, this.refs.ace.clientHeight);
|
this.props.actions.persistEditorHeight(this.props.queryEditor,
|
||||||
|
this.northPaneRef.current.clientHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onSqlChanged(sql) {
|
onSqlChanged(sql) {
|
||||||
this.setState({ sql });
|
this.setState({ sql });
|
||||||
}
|
}
|
||||||
|
// One layer of abstraction for easy spying in unit tests
|
||||||
|
getSqlEditorHeight() {
|
||||||
|
return this.sqlEditorRef.current.clientHeight;
|
||||||
|
}
|
||||||
|
// 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_TOOLBAR_HEIGHT - GUTTER_HEIGHT / 2,
|
||||||
|
southPaneHeight: height * southPercent / 100,
|
||||||
|
};
|
||||||
|
}
|
||||||
getHotkeyConfig() {
|
getHotkeyConfig() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -187,9 +205,42 @@ class SqlEditor extends React.PureComponent {
|
||||||
ctasChanged(event) {
|
ctasChanged(event) {
|
||||||
this.setState({ ctas: event.target.value });
|
this.setState({ ctas: event.target.value });
|
||||||
}
|
}
|
||||||
sqlEditorHeight() {
|
queryPane() {
|
||||||
const horizontalScrollbarHeight = 25;
|
const hotkeys = this.getHotkeyConfig();
|
||||||
return parseInt(this.props.getHeight(), 10) - horizontalScrollbarHeight;
|
const { aceEditorHeight, southPaneHeight } = this.getAceEditorAndSouthPaneHeights(
|
||||||
|
this.state.height, INITIAL_NORTH_PERCENT, INITIAL_SOUTH_PERCENT);
|
||||||
|
return (
|
||||||
|
<div className="queryPane">
|
||||||
|
<Split
|
||||||
|
sizes={[INITIAL_NORTH_PERCENT, INITIAL_SOUTH_PERCENT]}
|
||||||
|
minSize={200}
|
||||||
|
direction="vertical"
|
||||||
|
gutterSize={GUTTER_HEIGHT}
|
||||||
|
onDragStart={this.onResizeStart}
|
||||||
|
onDragEnd={this.onResizeEnd}
|
||||||
|
>
|
||||||
|
<div ref={this.northPaneRef}>
|
||||||
|
<AceEditorWrapper
|
||||||
|
actions={this.props.actions}
|
||||||
|
onBlur={this.setQueryEditorSql}
|
||||||
|
onChange={this.onSqlChanged}
|
||||||
|
queryEditor={this.props.queryEditor}
|
||||||
|
sql={this.props.queryEditor.sql}
|
||||||
|
tables={this.props.tables}
|
||||||
|
height={`${this.state.aceEditorHeight || aceEditorHeight}px`}
|
||||||
|
hotkeys={hotkeys}
|
||||||
|
/>
|
||||||
|
{this.renderEditorBottomBar(hotkeys)}
|
||||||
|
</div>
|
||||||
|
<SouthPane
|
||||||
|
editorQueries={this.props.editorQueries}
|
||||||
|
dataPreviewQueries={this.props.dataPreviewQueries}
|
||||||
|
actions={this.props.actions}
|
||||||
|
height={this.state.southPaneHeight || southPaneHeight}
|
||||||
|
/>
|
||||||
|
</Split>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
renderEditorBottomBar(hotkeys) {
|
renderEditorBottomBar(hotkeys) {
|
||||||
let ctasControls;
|
let ctasControls;
|
||||||
|
@ -305,74 +356,23 @@ class SqlEditor extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const height = this.sqlEditorHeight();
|
|
||||||
const defaultNorthHeight = this.props.queryEditor.height || 200;
|
|
||||||
const hotkeys = this.getHotkeyConfig();
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div ref={this.sqlEditorRef} className="SqlEditor">
|
||||||
className="SqlEditor"
|
<CSSTransition
|
||||||
style={{
|
classNames="schemaPane"
|
||||||
height: height + 'px',
|
in={!this.props.hideLeftBar}
|
||||||
}}
|
timeout={300}
|
||||||
>
|
>
|
||||||
<Row>
|
<div className="schemaPane">
|
||||||
<Collapse
|
<SqlEditorLeftBar
|
||||||
in={!this.props.hideLeftBar}
|
database={this.props.database}
|
||||||
>
|
queryEditor={this.props.queryEditor}
|
||||||
<Col
|
tables={this.props.tables}
|
||||||
xs={6}
|
actions={this.props.actions}
|
||||||
sm={5}
|
/>
|
||||||
md={4}
|
</div>
|
||||||
lg={3}
|
</CSSTransition>
|
||||||
>
|
{this.queryPane()}
|
||||||
<SqlEditorLeftBar
|
|
||||||
height={height}
|
|
||||||
database={this.props.database}
|
|
||||||
queryEditor={this.props.queryEditor}
|
|
||||||
tables={this.props.tables}
|
|
||||||
actions={this.props.actions}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Collapse>
|
|
||||||
<Col
|
|
||||||
xs={this.props.hideLeftBar ? 12 : 6}
|
|
||||||
sm={this.props.hideLeftBar ? 12 : 7}
|
|
||||||
md={this.props.hideLeftBar ? 12 : 8}
|
|
||||||
lg={this.props.hideLeftBar ? 12 : 9}
|
|
||||||
style={{ height: this.state.height }}
|
|
||||||
>
|
|
||||||
<SplitPane
|
|
||||||
split="horizontal"
|
|
||||||
defaultSize={defaultNorthHeight}
|
|
||||||
minSize={100}
|
|
||||||
onChange={this.onResize}
|
|
||||||
>
|
|
||||||
<div ref="ace" style={{ width: '100%' }}>
|
|
||||||
<div>
|
|
||||||
<AceEditorWrapper
|
|
||||||
actions={this.props.actions}
|
|
||||||
onBlur={this.setQueryEditorSql}
|
|
||||||
onChange={this.onSqlChanged}
|
|
||||||
queryEditor={this.props.queryEditor}
|
|
||||||
sql={this.props.queryEditor.sql}
|
|
||||||
tables={this.props.tables}
|
|
||||||
height={((this.state.editorPaneHeight || defaultNorthHeight) - 50) + 'px'}
|
|
||||||
hotkeys={hotkeys}
|
|
||||||
/>
|
|
||||||
{this.renderEditorBottomBar(hotkeys)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ref="south">
|
|
||||||
<SouthPane
|
|
||||||
editorQueries={this.props.editorQueries}
|
|
||||||
dataPreviewQueries={this.props.dataPreviewQueries}
|
|
||||||
actions={this.props.actions}
|
|
||||||
height={this.state.southPaneHeight || 0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</SplitPane>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button } from 'react-bootstrap';
|
import { Button } from 'react-bootstrap';
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
|
|
||||||
import TableElement from './TableElement';
|
import TableElement from './TableElement';
|
||||||
import TableSelector from '../../components/TableSelector';
|
import TableSelector from '../../components/TableSelector';
|
||||||
|
|
||||||
|
@ -106,7 +105,7 @@ export default class SqlEditorLeftBar extends React.PureComponent {
|
||||||
const tableMetaDataHeight = this.props.height - 130; // 130 is the height of the selects above
|
const tableMetaDataHeight = this.props.height - 130; // 130 is the height of the selects above
|
||||||
const qe = this.props.queryEditor;
|
const qe = this.props.queryEditor;
|
||||||
return (
|
return (
|
||||||
<div className="clearfix">
|
<div className="sqlEditorLeftBar">
|
||||||
<TableSelector
|
<TableSelector
|
||||||
dbId={qe.dbId}
|
dbId={qe.dbId}
|
||||||
schema={qe.schema}
|
schema={qe.schema}
|
||||||
|
|
|
@ -39,7 +39,6 @@ const propTypes = {
|
||||||
queryEditors: PropTypes.array,
|
queryEditors: PropTypes.array,
|
||||||
tabHistory: PropTypes.array.isRequired,
|
tabHistory: PropTypes.array.isRequired,
|
||||||
tables: PropTypes.array.isRequired,
|
tables: PropTypes.array.isRequired,
|
||||||
getHeight: PropTypes.func.isRequired,
|
|
||||||
offline: PropTypes.bool,
|
offline: PropTypes.bool,
|
||||||
};
|
};
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
|
@ -238,7 +237,6 @@ class TabbedSqlEditors extends React.PureComponent {
|
||||||
<div className="panel-body">
|
<div className="panel-body">
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<SqlEditor
|
<SqlEditor
|
||||||
getHeight={this.props.getHeight}
|
|
||||||
tables={this.props.tables.filter(xt => xt.queryEditorId === qe.id)}
|
tables={this.props.tables.filter(xt => xt.queryEditorId === qe.id)}
|
||||||
queryEditor={qe}
|
queryEditor={qe}
|
||||||
editorQueries={this.state.queriesArray}
|
editorQueries={this.state.queriesArray}
|
||||||
|
|
|
@ -135,7 +135,8 @@ div.Workspace {
|
||||||
background-color: #e8e8e8;
|
background-color: #e8e8e8;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border-bottom: 2px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
border-top: 0;
|
||||||
|
|
||||||
form {
|
form {
|
||||||
margin-block-end: 0;
|
margin-block-end: 0;
|
||||||
|
@ -193,21 +194,67 @@ div.Workspace {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.SqlEditor {
|
.SqlLab {
|
||||||
.Resizer {
|
.tab-content {
|
||||||
-moz-box-sizing: border-box;
|
height: 100%;
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Resizer.horizontal {
|
#brace-editor {
|
||||||
height: 4px;
|
height: calc(100% - 51px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace_content {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SouthPane {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.SqlEditor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.schemaPane {
|
||||||
|
flex-grow: 1;
|
||||||
|
transition: all .3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schemaPane-enter-done, .schemaPane-exit {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.schemaPane-enter-active, .schemaPane-exit-active {
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.schemaPane-enter, .schemaPane-exit-done {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
max-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queryPane {
|
||||||
|
flex-grow: 8;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schemaPane-exit-done + .queryPane {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gutter {
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
|
width: 3%;
|
||||||
|
margin: 3px 47%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gutter.gutter-vertical {
|
||||||
cursor: row-resize;
|
cursor: row-resize;
|
||||||
width: 4%;
|
|
||||||
margin-top: 4px;
|
|
||||||
margin-left: 47%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,9 +345,6 @@ a.Link {
|
||||||
.tooltip-inner {
|
.tooltip-inner {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
.SplitPane.horizontal {
|
|
||||||
padding-right: 4px;
|
|
||||||
}
|
|
||||||
.SouthPane {
|
.SouthPane {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -83,17 +83,15 @@ class AsyncSelect extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Select
|
||||||
<Select
|
placeholder={this.props.placeholder}
|
||||||
placeholder={this.props.placeholder}
|
options={this.state.options}
|
||||||
options={this.state.options}
|
value={this.props.value}
|
||||||
value={this.props.value}
|
isLoading={this.state.isLoading}
|
||||||
isLoading={this.state.isLoading}
|
onChange={this.onChange}
|
||||||
onChange={this.onChange}
|
valueRenderer={this.props.valueRenderer}
|
||||||
valueRenderer={this.props.valueRenderer}
|
{...this.props}
|
||||||
{...this.props}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,22 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
.TableSelector .fa-refresh {
|
.TableSelector .fa-refresh {
|
||||||
padding-top: 7px
|
padding-left: 9px;
|
||||||
}
|
}
|
||||||
.TableSelector .refresh-col {
|
.TableSelector .refresh-col {
|
||||||
padding-left: 0px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
.TableSelector .section {
|
||||||
|
padding-bottom: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.TableSelector .select {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.TableSelector .divider {
|
||||||
|
border-bottom: 1px solid #f2f2f2;
|
||||||
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Select from 'react-virtualized-select';
|
import Select from 'react-virtualized-select';
|
||||||
import createFilterOptions from 'react-select-fast-filter-options';
|
import createFilterOptions from 'react-select-fast-filter-options';
|
||||||
import { ControlLabel, Col, Label, Row } from 'react-bootstrap';
|
import { ControlLabel, Label } from 'react-bootstrap';
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
import { SupersetClient } from '@superset-ui/connection';
|
import { SupersetClient } from '@superset-ui/connection';
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ const propTypes = {
|
||||||
tableNameSticky: PropTypes.bool,
|
tableNameSticky: PropTypes.bool,
|
||||||
tableName: PropTypes.string,
|
tableName: PropTypes.string,
|
||||||
database: PropTypes.object,
|
database: PropTypes.object,
|
||||||
horizontal: PropTypes.bool,
|
|
||||||
sqlLabMode: PropTypes.bool,
|
sqlLabMode: PropTypes.bool,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
clearable: PropTypes.bool,
|
clearable: PropTypes.bool,
|
||||||
|
@ -52,7 +51,6 @@ const defaultProps = {
|
||||||
onTableChange: () => {},
|
onTableChange: () => {},
|
||||||
onChange: () => {},
|
onChange: () => {},
|
||||||
tableNameSticky: true,
|
tableNameSticky: true,
|
||||||
horizontal: false,
|
|
||||||
sqlLabMode: true,
|
sqlLabMode: true,
|
||||||
clearable: true,
|
clearable: true,
|
||||||
};
|
};
|
||||||
|
@ -199,10 +197,10 @@ export default class TableSelector extends React.PureComponent {
|
||||||
}
|
}
|
||||||
renderSelectRow(select, refreshBtn) {
|
renderSelectRow(select, refreshBtn) {
|
||||||
return (
|
return (
|
||||||
<Row>
|
<div className="section">
|
||||||
<Col md={11}>{select}</Col>
|
<span className="select">{select}</span>
|
||||||
<Col md={1} className="refresh-col">{refreshBtn}</Col>
|
<span className="refresh-col">{refreshBtn}</span>
|
||||||
</Row>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
renderDatabaseSelect() {
|
renderDatabaseSelect() {
|
||||||
|
@ -232,29 +230,25 @@ export default class TableSelector extends React.PureComponent {
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
renderSchema() {
|
renderSchema() {
|
||||||
return (
|
return this.renderSelectRow(
|
||||||
<div className="m-t-5">
|
<Select
|
||||||
{this.renderSelectRow(
|
name="select-schema"
|
||||||
<Select
|
placeholder={t('Select a schema (%s)', this.state.schemaOptions.length)}
|
||||||
name="select-schema"
|
options={this.state.schemaOptions}
|
||||||
placeholder={t('Select a schema (%s)', this.state.schemaOptions.length)}
|
value={this.props.schema}
|
||||||
options={this.state.schemaOptions}
|
valueRenderer={o => (
|
||||||
value={this.props.schema}
|
<div>
|
||||||
valueRenderer={o => (
|
<span className="text-muted">{t('Schema:')}</span> {o.label}
|
||||||
<div>
|
</div>
|
||||||
<span className="text-muted">{t('Schema:')}</span> {o.label}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
isLoading={this.state.schemaLoading}
|
|
||||||
autosize={false}
|
|
||||||
onChange={this.changeSchema}
|
|
||||||
/>,
|
|
||||||
<RefreshLabel
|
|
||||||
onClick={() => this.onDatabaseChange({ id: this.props.dbId }, true)}
|
|
||||||
tooltipContent={t('Force refresh schema list')}
|
|
||||||
/>,
|
|
||||||
)}
|
)}
|
||||||
</div>
|
isLoading={this.state.schemaLoading}
|
||||||
|
autosize={false}
|
||||||
|
onChange={this.changeSchema}
|
||||||
|
/>,
|
||||||
|
<RefreshLabel
|
||||||
|
onClick={() => this.onDatabaseChange({ id: this.props.dbId }, true)}
|
||||||
|
tooltipContent={t('Force refresh schema list')}
|
||||||
|
/>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
renderTable() {
|
renderTable() {
|
||||||
|
@ -290,20 +284,16 @@ export default class TableSelector extends React.PureComponent {
|
||||||
value={this.state.tableName}
|
value={this.state.tableName}
|
||||||
loadOptions={this.getTableNamesBySubStr}
|
loadOptions={this.getTableNamesBySubStr}
|
||||||
/>);
|
/>);
|
||||||
return (
|
return this.renderSelectRow(
|
||||||
<div className="m-t-5">
|
select,
|
||||||
{this.renderSelectRow(
|
<RefreshLabel
|
||||||
select,
|
onClick={() => this.changeSchema({ value: this.props.schema }, true)}
|
||||||
<RefreshLabel
|
tooltipContent={t('Force refresh table list')}
|
||||||
onClick={() => this.changeSchema({ value: this.props.schema }, true)}
|
/>);
|
||||||
tooltipContent={t('Force refresh table list')}
|
|
||||||
/>)}
|
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
renderSeeTableLabel() {
|
renderSeeTableLabel() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="section">
|
||||||
<hr />
|
|
||||||
<ControlLabel>
|
<ControlLabel>
|
||||||
{t('See table schema')}{' '}
|
{t('See table schema')}{' '}
|
||||||
<small>
|
<small>
|
||||||
|
@ -319,18 +309,11 @@ export default class TableSelector extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="TableSelector">
|
<div className="TableSelector">
|
||||||
{this.props.horizontal ?
|
{this.renderDatabaseSelect()}
|
||||||
<div>
|
{this.renderSchema()}
|
||||||
<Col md={4}>{this.renderDatabaseSelect()}</Col>
|
<div className="divider" />
|
||||||
<Col md={4}>{this.renderSchema()}</Col>
|
{this.props.sqlLabMode && this.renderSeeTableLabel()}
|
||||||
<Col md={4}>{this.renderTable()}</Col>
|
{this.renderTable()}
|
||||||
</div> :
|
|
||||||
<div>
|
|
||||||
<div>{this.renderDatabaseSelect()}</div>
|
|
||||||
<div className="m-t-5">{this.renderSchema()}</div>
|
|
||||||
{this.props.sqlLabMode && this.renderSeeTableLabel()}
|
|
||||||
<div className="m-t-5">{this.renderTable()}</div>
|
|
||||||
</div>}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue