This is an automated email from the ASF dual-hosted git repository. timi pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
The following commit(s) were added to refs/heads/master by this push: new fc3b68e [Sqllab] Add offline state to sqllab (#6013) fc3b68e is described below commit fc3b68e23433522b3a6d7327cc95199bf01dcf96 Author: timifasubaa <30888507+timifasu...@users.noreply.github.com> AuthorDate: Mon Oct 22 18:36:07 2018 -0700 [Sqllab] Add offline state to sqllab (#6013) * add timeout and refresh for failed backend * show offline state instead of refreshing * add southpane tests --- .../spec/javascripts/sqllab/SouthPane_spec.jsx | 37 ++++++++++++++++++++++ .../javascripts/sqllab/SqlEditorLeftBar_spec.jsx | 11 +++++-- .../javascripts/sqllab/TabbedSqlEditors_spec.jsx | 6 ++++ superset/assets/src/SqlLab/actions.js | 5 +++ .../src/SqlLab/components/QueryAutoRefresh.jsx | 7 +++- .../assets/src/SqlLab/components/QuerySearch.jsx | 2 +- .../assets/src/SqlLab/components/SouthPane.jsx | 12 ++++++- .../src/SqlLab/components/SqlEditorLeftBar.jsx | 17 +++++++--- .../src/SqlLab/components/TabbedSqlEditors.jsx | 4 +++ superset/assets/src/SqlLab/constants.js | 14 ++++---- superset/assets/src/SqlLab/getInitialState.js | 5 +-- superset/assets/src/SqlLab/reducers.js | 3 ++ 12 files changed, 106 insertions(+), 17 deletions(-) diff --git a/superset/assets/spec/javascripts/sqllab/SouthPane_spec.jsx b/superset/assets/spec/javascripts/sqllab/SouthPane_spec.jsx new file mode 100644 index 0000000..9836583 --- /dev/null +++ b/superset/assets/spec/javascripts/sqllab/SouthPane_spec.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; + +import { shallow } from 'enzyme'; + +import { STATUS_OPTIONS } from '../../../src/SqlLab/constants'; +import { initialState } from './fixtures'; +import SouthPane from '../../../src/SqlLab/components/SouthPane'; + +describe('SouthPane', () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore(initialState); + + const mockedProps = { + editorQueries: [], + dataPreviewQueries: [], + actions: {}, + activeSouthPaneTab: '', + height: 1, + databases: {}, + offline: false, + }; + + const getWrapper = () => ( + shallow(<SouthPane {...mockedProps} />, { + context: { store }, + }).dive()); + + let wrapper; + it('should render offline when the state is offline', () => { + wrapper = getWrapper(); + wrapper.setProps({ offline: true }); + expect(wrapper.find('.m-r-3').render().text()).toBe(STATUS_OPTIONS.offline); + }); +}); diff --git a/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx b/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx index b233e19..9d3c3f6 100644 --- a/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx @@ -1,9 +1,11 @@ import React from 'react'; +import configureStore from 'redux-mock-store'; import { shallow } from 'enzyme'; import sinon from 'sinon'; import fetchMock from 'fetch-mock'; +import thunk from 'redux-thunk'; -import { table, defaultQueryEditor, databases, tables } from './fixtures'; +import { table, defaultQueryEditor, databases, initialState, tables } from './fixtures'; import SqlEditorLeftBar from '../../../src/SqlLab/components/SqlEditorLeftBar'; import TableElement from '../../../src/SqlLab/components/TableElement'; @@ -21,11 +23,16 @@ describe('SqlEditorLeftBar', () => { database: {}, height: 0, }; + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore(initialState); let wrapper; beforeEach(() => { - wrapper = shallow(<SqlEditorLeftBar {...mockedProps} />); + wrapper = shallow(<SqlEditorLeftBar {...mockedProps} />, { + context: { store }, + }).dive(); }); it('is valid', () => { diff --git a/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx b/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx index 046e2a6..33d1e47 100644 --- a/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx @@ -166,4 +166,10 @@ describe('TabbedSqlEditors', () => { const lastTab = wrapper.find(Tab).last(); expect(lastTab.props().eventKey).toContain('add_tab'); }); + it('should disable new tab when offline', () => { + wrapper = getWrapper(); + expect(wrapper.find(Tab).last().props().disabled).toBe(false); + wrapper.setProps({ offline: true }); + expect(wrapper.find(Tab).last().props().disabled).toBe(true); + }); }); diff --git a/superset/assets/src/SqlLab/actions.js b/superset/assets/src/SqlLab/actions.js index 8c9ef2d..91e8486 100644 --- a/superset/assets/src/SqlLab/actions.js +++ b/superset/assets/src/SqlLab/actions.js @@ -34,6 +34,7 @@ export const SET_DATABASES = 'SET_DATABASES'; export const SET_ACTIVE_QUERY_EDITOR = 'SET_ACTIVE_QUERY_EDITOR'; export const SET_ACTIVE_SOUTHPANE_TAB = 'SET_ACTIVE_SOUTHPANE_TAB'; export const REFRESH_QUERIES = 'REFRESH_QUERIES'; +export const SET_USER_OFFLINE = 'SET_USER_OFFLINE'; export const RUN_QUERY = 'RUN_QUERY'; export const START_QUERY = 'START_QUERY'; export const STOP_QUERY = 'STOP_QUERY'; @@ -342,6 +343,10 @@ export function refreshQueries(alteredQueries) { return { type: REFRESH_QUERIES, alteredQueries }; } +export function setUserOffline(offline) { + return { type: SET_USER_OFFLINE, offline }; +} + export function persistEditorHeight(queryEditor, currentHeight) { return { type: QUERY_EDITOR_PERSIST_HEIGHT, queryEditor, currentHeight }; } diff --git a/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx b/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx index 0b09364..ea6e780 100644 --- a/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx +++ b/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx @@ -9,6 +9,7 @@ import * as Actions from '../actions'; const QUERY_UPDATE_FREQ = 2000; const QUERY_UPDATE_BUFFER_MS = 5000; const MAX_QUERY_AGE_TO_POLL = 21600000; +const QUERY_TIMEOUT_LIMIT = 7000; class QueryAutoRefresh extends React.PureComponent { componentWillMount() { @@ -44,11 +45,15 @@ class QueryAutoRefresh extends React.PureComponent { if (this.shouldCheckForQueries()) { SupersetClient.get({ endpoint: `/superset/queries/${this.props.queriesLastUpdate - QUERY_UPDATE_BUFFER_MS}`, + timeout: QUERY_TIMEOUT_LIMIT, }).then(({ json }) => { if (Object.keys(json).length > 0) { this.props.actions.refreshQueries(json); } - }); + this.props.actions.setUserOffline(false); + }).catch(() => { + this.props.actions.setUserOffline(true); + }); } } render() { diff --git a/superset/assets/src/SqlLab/components/QuerySearch.jsx b/superset/assets/src/SqlLab/components/QuerySearch.jsx index a3d9ddf..9e92029 100644 --- a/superset/assets/src/SqlLab/components/QuerySearch.jsx +++ b/superset/assets/src/SqlLab/components/QuerySearch.jsx @@ -227,7 +227,7 @@ class QuerySearch extends React.PureComponent { <Select name="select-status" placeholder={t('[Query Status]')} - options={STATUS_OPTIONS.map(s => ({ value: s, label: s }))} + options={Object.keys(STATUS_OPTIONS).map(s => ({ value: s, label: s }))} value={this.state.status} isLoading={false} autosize={false} diff --git a/superset/assets/src/SqlLab/components/SouthPane.jsx b/superset/assets/src/SqlLab/components/SouthPane.jsx index 65d17f7..ce81c8d 100644 --- a/superset/assets/src/SqlLab/components/SouthPane.jsx +++ b/superset/assets/src/SqlLab/components/SouthPane.jsx @@ -1,13 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import shortid from 'shortid'; -import { Alert, Tab, Tabs } from 'react-bootstrap'; +import { Alert, Label, Tab, Tabs } from 'react-bootstrap'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as Actions from '../actions'; import QueryHistory from './QueryHistory'; import ResultSet from './ResultSet'; +import { STATUS_OPTIONS, STATE_BSSTYLE_MAP } from '../constants'; import { t } from '../../locales'; /* @@ -21,10 +22,12 @@ const propTypes = { activeSouthPaneTab: PropTypes.string, height: PropTypes.number, databases: PropTypes.object.isRequired, + offline: PropTypes.bool, }; const defaultProps = { activeSouthPaneTab: 'Results', + offline: false, }; class SouthPane extends React.PureComponent { @@ -32,6 +35,12 @@ class SouthPane extends React.PureComponent { this.props.actions.setActiveSouthPaneTab(id); } render() { + if (this.props.offline) { + return ( + <Label className="m-r-3" bsStyle={STATE_BSSTYLE_MAP[STATUS_OPTIONS.offline]}> + { STATUS_OPTIONS.offline } + </Label>); + } const innerTabHeight = this.props.height - 55; let latestQuery; const props = this.props; @@ -103,6 +112,7 @@ function mapStateToProps({ sqlLab }) { return { activeSouthPaneTab: sqlLab.activeSouthPaneTab, databases: sqlLab.databases, + offline: sqlLab.offline, }; } diff --git a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx index f17f810..12530eb 100644 --- a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx +++ b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { ControlLabel, Button } from 'react-bootstrap'; +import { connect } from 'react-redux'; import Select from 'react-virtualized-select'; import createFilterOptions from 'react-select-fast-filter-options'; import { SupersetClient } from '@superset-ui/core'; @@ -16,11 +17,13 @@ const propTypes = { tables: PropTypes.array, actions: PropTypes.object, database: PropTypes.object, + offline: PropTypes.bool, }; const defaultProps = { tables: [], actions: {}, + offline: false, }; class SqlEditorLeftBar extends React.PureComponent { @@ -50,7 +53,7 @@ class SqlEditorLeftBar extends React.PureComponent { } getTableNamesBySubStr(input) { - if (!this.props.queryEditor.dbId || !input) { + if (this.props.offline || !this.props.queryEditor.dbId || !input) { return Promise.resolve({ options: [] }); } @@ -77,7 +80,7 @@ class SqlEditorLeftBar extends React.PureComponent { fetchTables(dbId, schema, force, substr) { // This can be large so it shouldn't be put in the Redux store const forceRefresh = force || false; - if (dbId && schema) { + if (!this.props.offline && dbId && schema) { this.setState(() => ({ tableLoading: true, tableOptions: [] })); const endpoint = `/superset/tables/${dbId}/${schema}/${substr}/${forceRefresh}/`; @@ -130,7 +133,7 @@ class SqlEditorLeftBar extends React.PureComponent { fetchSchemas(dbId, force) { const actualDbId = dbId || this.props.queryEditor.dbId; const forceRefresh = force || false; - if (actualDbId) { + if (!this.props.offline && actualDbId) { this.setState({ schemaLoading: true }); const endpoint = `/superset/schemas/${actualDbId}/${forceRefresh}/`; @@ -286,7 +289,13 @@ class SqlEditorLeftBar extends React.PureComponent { } } +function mapStateToProps({ sqlLab }) { + return { + offline: sqlLab.offline, + }; +} + SqlEditorLeftBar.propTypes = propTypes; SqlEditorLeftBar.defaultProps = defaultProps; -export default SqlEditorLeftBar; +export default connect(mapStateToProps)(SqlEditorLeftBar); diff --git a/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx b/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx index cf94b5f..e693295 100644 --- a/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx +++ b/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx @@ -21,9 +21,11 @@ const propTypes = { tabHistory: PropTypes.array.isRequired, tables: PropTypes.array.isRequired, getHeight: PropTypes.func.isRequired, + offline: PropTypes.bool, }; const defaultProps = { queryEditors: [], + offline: false, }; let queryCount = 1; @@ -234,6 +236,7 @@ class TabbedSqlEditors extends React.PureComponent { </div> } eventKey="add_tab" + disabled={this.props.offline} /> </Tabs> ); @@ -250,6 +253,7 @@ function mapStateToProps({ sqlLab }) { tabHistory: sqlLab.tabHistory, tables: sqlLab.tables, defaultDbId: sqlLab.defaultDbId, + offline: sqlLab.offline, }; } function mapDispatchToProps(dispatch) { diff --git a/superset/assets/src/SqlLab/constants.js b/superset/assets/src/SqlLab/constants.js index 2a34112..1e98aa0 100644 --- a/superset/assets/src/SqlLab/constants.js +++ b/superset/assets/src/SqlLab/constants.js @@ -1,4 +1,5 @@ export const STATE_BSSTYLE_MAP = { + offline: 'danger', failed: 'danger', pending: 'info', fetching: 'info', @@ -8,12 +9,13 @@ export const STATE_BSSTYLE_MAP = { success: 'success', }; -export const STATUS_OPTIONS = [ - 'success', - 'failed', - 'running', - 'pending', -]; +export const STATUS_OPTIONS = { + success: 'success', + failed: 'failed', + running: 'running', + offline: 'offline', + pending: 'pending', +}; export const TIME_OPTIONS = [ 'now', diff --git a/superset/assets/src/SqlLab/getInitialState.js b/superset/assets/src/SqlLab/getInitialState.js index 9c9210f..3848c99 100644 --- a/superset/assets/src/SqlLab/getInitialState.js +++ b/superset/assets/src/SqlLab/getInitialState.js @@ -16,14 +16,15 @@ export default function getInitialState({ defaultDbId, ...restBootstrapData }) { return { featureFlags: restBootstrapData.common.feature_flags, sqlLab: { + activeSouthPaneTab: 'Results', alerts: [], - queries: {}, databases: {}, + offline: false, + queries: {}, queryEditors: [defaultQueryEditor], tabHistory: [defaultQueryEditor.id], tables: [], queriesLastUpdate: Date.now(), - activeSouthPaneTab: 'Results', ...restBootstrapData, }, messageToasts: getToastsFromPyFlashMessages( diff --git a/superset/assets/src/SqlLab/reducers.js b/superset/assets/src/SqlLab/reducers.js index e357111..ed8ccff 100644 --- a/superset/assets/src/SqlLab/reducers.js +++ b/superset/assets/src/SqlLab/reducers.js @@ -250,6 +250,9 @@ export const sqlLabReducer = function (state = {}, action) { } return Object.assign({}, state, { queries: newQueries, queriesLastUpdate }); }, + [actions.SET_USER_OFFLINE]() { + return Object.assign({}, state, { offline: action.offline }); + }, [actions.CREATE_DATASOURCE_STARTED]() { return Object.assign({}, state, { isDatasourceLoading: true,