diff --git a/superset-frontend/spec/javascripts/sqllab/SouthPane_spec.jsx b/superset-frontend/spec/javascripts/sqllab/SouthPane_spec.jsx index b7c34c0aa69..dbc7379e2dd 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'; +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 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 getWrapper = () => shallow().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 cce5e1bfbe8..b634d2c41f8 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 a33fcd1ce72..04a455c0197 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; 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 3d99d173c6b..00000000000 --- 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 ( - - ); - } - 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 = ( - - ); - } else if ( - Date.now() - latestQuery.startDttm <= - LOCALSTORAGE_MAX_QUERY_AGE_MS - ) { - results = ( - - ); - } - } else { - results = ( - - ); - } - const dataPreviewTabs = props.dataPreviewQueries.map(query => ( - - - - )); - - return ( - - - - {results} - - - - - {dataPreviewTabs} - - - ); - } -} - -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 00000000000..10d9aca6dbc --- /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; + activeSouthPaneTab?: string; + height: number; + databases: Record; + 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(); + const switchTab = (id: string) => { + actions.setActiveSouthPaneTab(id); + }; + + const renderOfflineStatus = () => ( + + ); + + 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 = ( + + ); + return results; + } + if (Date.now() - latestQuery.startDttm <= LOCALSTORAGE_MAX_QUERY_AGE_MS) { + results = ( + + ); + } + } else { + results = ( + + ); + } + return results; + }; + + const renderDataPreviewTabs = () => + dataPreviewQueries.map(query => ( + + + + )); + return offline ? ( + renderOfflineStatus() + ) : ( + + + + {renderResults()} + + + + + {renderDataPreviewTabs()} + + + ); +} 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 00000000000..075cd59739c --- /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) { + return { + activeSouthPaneTab: sqlLab.activeSouthPaneTab, + databases: sqlLab.databases, + offline: sqlLab.offline, + }; +} + +function mapDispatchToProps(dispatch: Dispatch) { + return { + actions: bindActionCreators(Actions, dispatch), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(SouthPane); diff --git a/superset-frontend/src/SqlLab/components/SqlEditor.jsx b/superset-frontend/src/SqlLab/components/SqlEditor.jsx index 79153f960b0..1db78b18cae 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';