This is an automated email from the ASF dual-hosted git repository.
hugh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 1d5c58d refactor: SouthPane into functional component (#13676)
1d5c58d is described below
commit 1d5c58d005646aa4495223bcd49701b14dfae99a
Author: AAfghahi <[email protected]>
AuthorDate: Wed Mar 24 23:00:12 2021 -0400
refactor: SouthPane into functional component (#13676)
---
.../spec/javascripts/sqllab/SouthPane_spec.jsx | 100 +++++-----
.../spec/javascripts/sqllab/SqlEditor_spec.jsx | 2 +-
.../src/SqlLab/components/ResultSet.tsx | 1 +
.../src/SqlLab/components/SouthPane.jsx | 211 ---------------------
.../src/SqlLab/components/SouthPane/SouthPane.tsx | 187 ++++++++++++++++++
.../src/SqlLab/components/SouthPane/state.ts | 38 ++++
.../src/SqlLab/components/SqlEditor.jsx | 2 +-
7 files changed, 279 insertions(+), 262 deletions(-)
diff --git a/superset-frontend/spec/javascripts/sqllab/SouthPane_spec.jsx
b/superset-frontend/spec/javascripts/sqllab/SouthPane_spec.jsx
index b7c34c0..dbc7379 100644
--- a/superset-frontend/spec/javascripts/sqllab/SouthPane_spec.jsx
+++ b/superset-frontend/spec/javascripts/sqllab/SouthPane_spec.jsx
@@ -20,61 +20,62 @@ import React from 'react';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { styledShallow as shallow } from 'spec/helpers/theming';
-import SouthPaneContainer from 'src/SqlLab/components/SouthPane';
+import SouthPaneContainer from 'src/SqlLab/components/SouthPane/state';
import ResultSet from 'src/SqlLab/components/ResultSet';
+import '@testing-library/jest-dom/extend-expect';
import { STATUS_OPTIONS } from 'src/SqlLab/constants';
import { initialState } from './fixtures';
-describe('SouthPane', () => {
- const middlewares = [thunk];
- const mockStore = configureStore(middlewares);
- const store = mockStore(initialState);
+const mockedProps = {
+ editorQueries: [
+ {
+ cached: false,
+ changedOn: Date.now(),
+ db: 'main',
+ dbId: 1,
+ id: 'LCly_kkIN',
+ startDttm: Date.now(),
+ },
+ {
+ cached: false,
+ changedOn: 1559238500401,
+ db: 'main',
+ dbId: 1,
+ id: 'lXJa7F9_r',
+ startDttm: 1559238500401,
+ },
+ {
+ cached: false,
+ changedOn: 1559238506925,
+ db: 'main',
+ dbId: 1,
+ id: '2g2_iRFMl',
+ startDttm: 1559238506925,
+ },
+ {
+ cached: false,
+ changedOn: 1559238516395,
+ db: 'main',
+ dbId: 1,
+ id: 'erWdqEWPm',
+ startDttm: 1559238516395,
+ },
+ ],
+ latestQueryId: 'LCly_kkIN',
+ dataPreviewQueries: [],
+ actions: {},
+ activeSouthPaneTab: '',
+ height: 1,
+ displayLimit: 1,
+ databases: {},
+ offline: false,
+};
- const mockedProps = {
- editorQueries: [
- {
- cached: false,
- changedOn: Date.now(),
- db: 'main',
- dbId: 1,
- id: 'LCly_kkIN',
- startDttm: Date.now(),
- },
- {
- cached: false,
- changedOn: 1559238500401,
- db: 'main',
- dbId: 1,
- id: 'lXJa7F9_r',
- startDttm: 1559238500401,
- },
- {
- cached: false,
- changedOn: 1559238506925,
- db: 'main',
- dbId: 1,
- id: '2g2_iRFMl',
- startDttm: 1559238506925,
- },
- {
- cached: false,
- changedOn: 1559238516395,
- db: 'main',
- dbId: 1,
- id: 'erWdqEWPm',
- startDttm: 1559238516395,
- },
- ],
- latestQueryId: 'LCly_kkIN',
- dataPreviewQueries: [],
- actions: {},
- activeSouthPaneTab: '',
- height: 1,
- displayLimit: 1,
- databases: {},
- offline: false,
- };
+const middlewares = [thunk];
+const mockStore = configureStore(middlewares);
+const store = mockStore(initialState);
+describe('SouthPane', () => {
const getWrapper = () =>
shallow(<SouthPaneContainer store={store} {...mockedProps} />).dive();
@@ -85,6 +86,7 @@ describe('SouthPane', () => {
wrapper.setProps({ offline: true });
expect(wrapper.childAt(0).text()).toBe(STATUS_OPTIONS.offline);
});
+
it('should pass latest query down to ResultSet component', () => {
wrapper = getWrapper().dive();
expect(wrapper.find(ResultSet)).toExist();
diff --git a/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx
b/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx
index cce5e1b..b634d2c 100644
--- a/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx
+++ b/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx
@@ -29,7 +29,7 @@ import {
SQL_TOOLBAR_HEIGHT,
} from 'src/SqlLab/constants';
import AceEditorWrapper from 'src/SqlLab/components/AceEditorWrapper';
-import ConnectedSouthPane from 'src/SqlLab/components/SouthPane';
+import ConnectedSouthPane from 'src/SqlLab/components/SouthPane/state';
import SqlEditor from 'src/SqlLab/components/SqlEditor';
import SqlEditorLeftBar from 'src/SqlLab/components/SqlEditorLeftBar';
import { Dropdown } from 'src/common/components';
diff --git a/superset-frontend/src/SqlLab/components/ResultSet.tsx
b/superset-frontend/src/SqlLab/components/ResultSet.tsx
index a33fcd1..04a455c 100644
--- a/superset-frontend/src/SqlLab/components/ResultSet.tsx
+++ b/superset-frontend/src/SqlLab/components/ResultSet.tsx
@@ -65,6 +65,7 @@ interface DatasetOptionAutocomplete {
}
interface ResultSetProps {
+ showControls?: boolean;
actions: Record<string, any>;
cache?: boolean;
csv?: boolean;
diff --git a/superset-frontend/src/SqlLab/components/SouthPane.jsx
b/superset-frontend/src/SqlLab/components/SouthPane.jsx
deleted file mode 100644
index 3d99d17..0000000
--- a/superset-frontend/src/SqlLab/components/SouthPane.jsx
+++ /dev/null
@@ -1,211 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import shortid from 'shortid';
-import Alert from 'src/components/Alert';
-import Tabs from 'src/common/components/Tabs';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import { t, styled } from '@superset-ui/core';
-
-import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
-
-import Label from 'src/components/Label';
-import * as Actions from '../actions/sqlLab';
-import QueryHistory from './QueryHistory';
-import ResultSet from './ResultSet';
-import {
- STATUS_OPTIONS,
- STATE_TYPE_MAP,
- LOCALSTORAGE_MAX_QUERY_AGE_MS,
-} from '../constants';
-
-const TAB_HEIGHT = 64;
-
-/*
- editorQueries are queries executed by users passed from SqlEditor component
- dataPrebiewQueries are all queries executed for preview of table data
(from SqlEditorLeft)
-*/
-const propTypes = {
- editorQueries: PropTypes.array.isRequired,
- latestQueryId: PropTypes.string,
- dataPreviewQueries: PropTypes.array.isRequired,
- actions: PropTypes.object.isRequired,
- activeSouthPaneTab: PropTypes.string,
- height: PropTypes.number,
- databases: PropTypes.object.isRequired,
- offline: PropTypes.bool,
- displayLimit: PropTypes.number.isRequired,
-};
-
-const defaultProps = {
- activeSouthPaneTab: 'Results',
- offline: false,
-};
-
-const StyledPane = styled.div`
- width: 100%;
-
- .ant-tabs .ant-tabs-content-holder {
- overflow: visible;
- }
- .SouthPaneTabs {
- height: 100%;
- display: flex;
- flex-direction: column;
- }
- .tab-content {
- overflow: hidden;
- .alert {
- margin-top: ${({ theme }) => theme.gridUnit * 2}px;
- }
-
- button.fetch {
- margin-top: ${({ theme }) => theme.gridUnit * 2}px;
- }
- }
-`;
-
-export class SouthPane extends React.PureComponent {
- constructor(props) {
- super(props);
- this.southPaneRef = React.createRef();
- this.switchTab = this.switchTab.bind(this);
- }
-
- switchTab(id) {
- this.props.actions.setActiveSouthPaneTab(id);
- }
-
- render() {
- if (this.props.offline) {
- return (
- <Label className="m-r-3" type={STATE_TYPE_MAP[STATUS_OPTIONS.offline]}>
- {STATUS_OPTIONS.offline}
- </Label>
- );
- }
- const innerTabContentHeight = this.props.height - TAB_HEIGHT;
- let latestQuery;
- const { props } = this;
- if (props.editorQueries.length > 0) {
- // get the latest query
- latestQuery = props.editorQueries.find(
- q => q.id === this.props.latestQueryId,
- );
- }
- let results;
- if (latestQuery) {
- if (
- isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE) &&
- latestQuery.state === 'success' &&
- !latestQuery.resultsKey &&
- !latestQuery.results
- ) {
- results = (
- <Alert
- type="warning"
- message={t(
- 'No stored results found, you need to re-run your query',
- )}
- />
- );
- } else if (
- Date.now() - latestQuery.startDttm <=
- LOCALSTORAGE_MAX_QUERY_AGE_MS
- ) {
- results = (
- <ResultSet
- showControls
- search
- query={latestQuery}
- actions={props.actions}
- height={innerTabContentHeight}
- database={this.props.databases[latestQuery.dbId]}
- displayLimit={this.props.displayLimit}
- />
- );
- }
- } else {
- results = (
- <Alert type="info" message={t('Run a query to display results here')}
/>
- );
- }
- const dataPreviewTabs = props.dataPreviewQueries.map(query => (
- <Tabs.TabPane
- tab={t('Preview: `%s`', decodeURIComponent(query.tableName))}
- key={query.id}
- >
- <ResultSet
- query={query}
- visualize={false}
- csv={false}
- actions={props.actions}
- cache
- height={innerTabContentHeight}
- displayLimit={this.props.displayLimit}
- />
- </Tabs.TabPane>
- ));
-
- return (
- <StyledPane className="SouthPane" ref={this.southPaneRef}>
- <Tabs
- activeKey={this.props.activeSouthPaneTab}
- className="SouthPaneTabs"
- onChange={this.switchTab}
- id={shortid.generate()}
- fullWidth={false}
- >
- <Tabs.TabPane tab={t('Results')} key="Results">
- {results}
- </Tabs.TabPane>
- <Tabs.TabPane tab={t('Query history')} key="History">
- <QueryHistory
- queries={props.editorQueries}
- actions={props.actions}
- displayLimit={props.displayLimit}
- />
- </Tabs.TabPane>
- {dataPreviewTabs}
- </Tabs>
- </StyledPane>
- );
- }
-}
-
-function mapStateToProps({ sqlLab }) {
- return {
- activeSouthPaneTab: sqlLab.activeSouthPaneTab,
- databases: sqlLab.databases,
- offline: sqlLab.offline,
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators(Actions, dispatch),
- };
-}
-
-SouthPane.propTypes = propTypes;
-SouthPane.defaultProps = defaultProps;
-
-export default connect(mapStateToProps, mapDispatchToProps)(SouthPane);
diff --git a/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.tsx
b/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.tsx
new file mode 100644
index 0000000..10d9aca
--- /dev/null
+++ b/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.tsx
@@ -0,0 +1,187 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React, { createRef } from 'react';
+import shortid from 'shortid';
+import Alert from 'src/components/Alert';
+import Tabs from 'src/common/components/Tabs';
+import { t, styled } from '@superset-ui/core';
+
+import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
+
+import Label from 'src/components/Label';
+import QueryHistory from '../QueryHistory';
+import ResultSet from '../ResultSet';
+import {
+ STATUS_OPTIONS,
+ STATE_TYPE_MAP,
+ LOCALSTORAGE_MAX_QUERY_AGE_MS,
+} from '../../constants';
+
+const TAB_HEIGHT = 64;
+
+/*
+ editorQueries are queries executed by users passed from SqlEditor component
+ dataPrebiewQueries are all queries executed for preview of table data
(from SqlEditorLeft)
+*/
+interface SouthPanePropTypes {
+ editorQueries: any[];
+ latestQueryId?: string;
+ dataPreviewQueries: any[];
+ actions: Record<string, Function>;
+ activeSouthPaneTab?: string;
+ height: number;
+ databases: Record<string, any>;
+ offline?: boolean;
+ displayLimit: number;
+}
+
+const StyledPane = styled.div`
+ width: 100%;
+
+ .ant-tabs .ant-tabs-content-holder {
+ overflow: visible;
+ }
+ .SouthPaneTabs {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ }
+ .tab-content {
+ overflow: hidden;
+ .alert {
+ margin-top: ${({ theme }) => theme.gridUnit * 2}px;
+ }
+
+ button.fetch {
+ margin-top: ${({ theme }) => theme.gridUnit * 2}px;
+ }
+ }
+`;
+
+export default function SouthPane({
+ editorQueries,
+ latestQueryId,
+ dataPreviewQueries,
+ actions,
+ activeSouthPaneTab = 'Results',
+ height,
+ databases,
+ offline = false,
+ displayLimit,
+}: SouthPanePropTypes) {
+ const innerTabContentHeight = height - TAB_HEIGHT;
+ const southPaneRef = createRef<HTMLDivElement>();
+ const switchTab = (id: string) => {
+ actions.setActiveSouthPaneTab(id);
+ };
+
+ const renderOfflineStatus = () => (
+ <Label className="m-r-3" type={STATE_TYPE_MAP[STATUS_OPTIONS.offline]}>
+ {STATUS_OPTIONS.offline}
+ </Label>
+ );
+
+ const renderResults = () => {
+ let latestQuery;
+ if (editorQueries.length > 0) {
+ // get the latest query
+ latestQuery = editorQueries.find(({ id }) => id === latestQueryId);
+ }
+ let results;
+ if (latestQuery) {
+ if (
+ isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE) &&
+ latestQuery.state === 'success' &&
+ !latestQuery.resultsKey &&
+ !latestQuery.results
+ ) {
+ results = (
+ <Alert
+ type="warning"
+ message={t(
+ 'No stored results found, you need to re-run your query',
+ )}
+ />
+ );
+ return results;
+ }
+ if (Date.now() - latestQuery.startDttm <= LOCALSTORAGE_MAX_QUERY_AGE_MS)
{
+ results = (
+ <ResultSet
+ showControls
+ search
+ query={latestQuery}
+ actions={actions}
+ height={innerTabContentHeight}
+ database={databases[latestQuery.dbId]}
+ displayLimit={displayLimit}
+ />
+ );
+ }
+ } else {
+ results = (
+ <Alert type="info" message={t('Run a query to display results here')}
/>
+ );
+ }
+ return results;
+ };
+
+ const renderDataPreviewTabs = () =>
+ dataPreviewQueries.map(query => (
+ <Tabs.TabPane
+ tab={t('Preview: `%s`', decodeURIComponent(query.tableName))}
+ key={query.id}
+ >
+ <ResultSet
+ query={query}
+ visualize={false}
+ csv={false}
+ actions={actions}
+ cache
+ height={innerTabContentHeight}
+ displayLimit={displayLimit}
+ />
+ </Tabs.TabPane>
+ ));
+ return offline ? (
+ renderOfflineStatus()
+ ) : (
+ <StyledPane className="SouthPane" ref={southPaneRef}>
+ <Tabs
+ activeKey={activeSouthPaneTab}
+ className="SouthPaneTabs"
+ onChange={switchTab}
+ id={shortid.generate()}
+ fullWidth={false}
+ >
+ <Tabs.TabPane tab={t('Results')} key="Results">
+ {renderResults()}
+ </Tabs.TabPane>
+ <Tabs.TabPane tab={t('Query history')} key="History">
+ <QueryHistory
+ queries={editorQueries}
+ actions={actions}
+ displayLimit={displayLimit}
+ />
+ </Tabs.TabPane>
+ {renderDataPreviewTabs()}
+ </Tabs>
+ </StyledPane>
+ );
+}
diff --git a/superset-frontend/src/SqlLab/components/SouthPane/state.ts
b/superset-frontend/src/SqlLab/components/SouthPane/state.ts
new file mode 100644
index 0000000..075cd59
--- /dev/null
+++ b/superset-frontend/src/SqlLab/components/SouthPane/state.ts
@@ -0,0 +1,38 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { connect } from 'react-redux';
+import { bindActionCreators, Dispatch } from 'redux';
+import * as Actions from '../../actions/sqlLab';
+import SouthPane from './SouthPane';
+
+function mapStateToProps({ sqlLab }: Record<string, any>) {
+ return {
+ activeSouthPaneTab: sqlLab.activeSouthPaneTab,
+ databases: sqlLab.databases,
+ offline: sqlLab.offline,
+ };
+}
+
+function mapDispatchToProps(dispatch: Dispatch) {
+ return {
+ actions: bindActionCreators<any, any>(Actions, dispatch),
+ };
+}
+
+export default connect<any>(mapStateToProps, mapDispatchToProps)(SouthPane);
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor.jsx
b/superset-frontend/src/SqlLab/components/SqlEditor.jsx
index 79153f9..1db78b1 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor.jsx
@@ -60,7 +60,7 @@ import {
} from '../actions/sqlLab';
import TemplateParamsEditor from './TemplateParamsEditor';
-import ConnectedSouthPane from './SouthPane';
+import ConnectedSouthPane from './SouthPane/state';
import SaveQuery from './SaveQuery';
import ScheduleQueryButton from './ScheduleQueryButton';
import EstimateQueryCostButton from './EstimateQueryCostButton';