[Sqllab] Add offline state to sqllab (#6013)
* add timeout and refresh for failed backend * show offline state instead of refreshing * add southpane tests
This commit is contained in:
parent
b9257b2a09
commit
fc3b68e234
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue