mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
Added timer to explore v2 and share it with sqllab (#1802)
* Added timer to explore v2 and share it with sqllab * Fixed js tests * Add timer to initial load * Make timer smaller * nits
This commit is contained in:
parent
866cfe5279
commit
8ef730b5fe
@ -15,10 +15,10 @@ import {
|
|||||||
} from 'react-bootstrap';
|
} from 'react-bootstrap';
|
||||||
|
|
||||||
import SouthPane from './SouthPane';
|
import SouthPane from './SouthPane';
|
||||||
import Timer from './Timer';
|
import Timer from '../../components/Timer';
|
||||||
|
|
||||||
import SqlEditorLeftBar from './SqlEditorLeftBar';
|
import SqlEditorLeftBar from './SqlEditorLeftBar';
|
||||||
import AceEditorWrapper from './AceEditorWrapper';
|
import AceEditorWrapper from './AceEditorWrapper';
|
||||||
|
import { STATE_BSSTYLE_MAP } from '../constants.js';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
actions: React.PropTypes.object.isRequired,
|
actions: React.PropTypes.object.isRequired,
|
||||||
@ -208,7 +208,14 @@ class SqlEditor extends React.PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
<div className="pull-right">
|
<div className="pull-right">
|
||||||
{limitWarning}
|
{limitWarning}
|
||||||
<Timer query={this.props.latestQuery} />
|
{this.props.latestQuery &&
|
||||||
|
<Timer
|
||||||
|
startTime={this.props.latestQuery.startDttm}
|
||||||
|
endTime={this.props.latestQuery.endDttm}
|
||||||
|
state={STATE_BSSTYLE_MAP[this.props.latestQuery.state]}
|
||||||
|
isRunning={this.props.latestQuery.state === 'running'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { now, fDuration } from '../../modules/dates';
|
|
||||||
|
|
||||||
import { STATE_BSSTYLE_MAP } from '../constants.js';
|
|
||||||
|
|
||||||
class Timer extends React.PureComponent {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
clockStr: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
componentWillMount() {
|
|
||||||
this.startTimer();
|
|
||||||
}
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.stopTimer();
|
|
||||||
}
|
|
||||||
startTimer() {
|
|
||||||
if (!(this.timer)) {
|
|
||||||
this.timer = setInterval(this.stopwatch.bind(this), 30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stopTimer() {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
}
|
|
||||||
stopwatch() {
|
|
||||||
if (this.props && this.props.query) {
|
|
||||||
const endDttm = this.props.query.endDttm || now();
|
|
||||||
const clockStr = fDuration(this.props.query.startDttm, endDttm);
|
|
||||||
this.setState({ clockStr });
|
|
||||||
if (this.props.query.state !== 'running') {
|
|
||||||
this.stopTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
if (this.props.query && this.props.query.state === 'running') {
|
|
||||||
this.startTimer();
|
|
||||||
}
|
|
||||||
let timerSpan = null;
|
|
||||||
if (this.props && this.props.query) {
|
|
||||||
const bsStyle = STATE_BSSTYLE_MAP[this.props.query.state];
|
|
||||||
timerSpan = (
|
|
||||||
<span className={'inlineBlock m-r-5 label label-' + bsStyle}>
|
|
||||||
{this.state.clockStr}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return timerSpan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Timer.propTypes = {
|
|
||||||
query: React.PropTypes.object,
|
|
||||||
};
|
|
||||||
Timer.defaultProps = {
|
|
||||||
query: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Timer;
|
|
71
superset/assets/javascripts/components/Timer.jsx
Normal file
71
superset/assets/javascripts/components/Timer.jsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { now, fDuration } from '../modules/dates';
|
||||||
|
|
||||||
|
class Timer extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
clockStr: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentWillMount() {
|
||||||
|
this.startTimer();
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.stopTimer();
|
||||||
|
}
|
||||||
|
startTimer() {
|
||||||
|
if (!(this.timer)) {
|
||||||
|
this.timer = setInterval(this.stopwatch.bind(this), 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stopTimer() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
stopwatch() {
|
||||||
|
if (this.props && this.props.startTime) {
|
||||||
|
const endDttm = this.props.endTime || now();
|
||||||
|
if (this.props.startTime < endDttm) {
|
||||||
|
const clockStr = fDuration(this.props.startTime, endDttm);
|
||||||
|
this.setState({ clockStr });
|
||||||
|
}
|
||||||
|
if (!this.props.isRunning) {
|
||||||
|
this.stopTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
if (this.props && this.props.isRunning) {
|
||||||
|
this.startTimer();
|
||||||
|
}
|
||||||
|
let timerSpan = null;
|
||||||
|
if (this.props) {
|
||||||
|
timerSpan = (
|
||||||
|
<span
|
||||||
|
className={`inlineBlock m-r-5 label label-${this.props.status}`}
|
||||||
|
style={this.props.style}
|
||||||
|
>
|
||||||
|
{this.state.clockStr}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return timerSpan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timer.propTypes = {
|
||||||
|
startTime: React.PropTypes.number,
|
||||||
|
endTime: React.PropTypes.number,
|
||||||
|
isRunning: React.PropTypes.bool.isRequired,
|
||||||
|
status: React.PropTypes.string,
|
||||||
|
style: React.PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
Timer.defaultProps = {
|
||||||
|
startTime: null,
|
||||||
|
endTime: null,
|
||||||
|
status: 'success',
|
||||||
|
style: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Timer;
|
@ -115,7 +115,7 @@ export function chartUpdateFailed(error) {
|
|||||||
|
|
||||||
export function updateExplore(datasource_type, datasource_id, form_data) {
|
export function updateExplore(datasource_type, datasource_id, form_data) {
|
||||||
return function (dispatch) {
|
return function (dispatch) {
|
||||||
dispatch(chartUpdateStarted);
|
dispatch(chartUpdateStarted());
|
||||||
const updateUrl =
|
const updateUrl =
|
||||||
`/superset/update_explore/${datasource_type}/${datasource_id}/`;
|
`/superset/update_explore/${datasource_type}/${datasource_id}/`;
|
||||||
|
|
||||||
@ -194,3 +194,8 @@ export function saveSlice(url) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const UPDATE_CHART_STATUS = 'UPDATE_CHART_STATUS';
|
||||||
|
export function updateChartStatus(status) {
|
||||||
|
return { type: UPDATE_CHART_STATUS, status };
|
||||||
|
}
|
||||||
|
@ -7,6 +7,13 @@ import { d3format } from '../../modules/utils';
|
|||||||
import ExploreActionButtons from '../../explore/components/ExploreActionButtons';
|
import ExploreActionButtons from '../../explore/components/ExploreActionButtons';
|
||||||
import FaveStar from '../../components/FaveStar';
|
import FaveStar from '../../components/FaveStar';
|
||||||
import TooltipWrapper from '../../components/TooltipWrapper';
|
import TooltipWrapper from '../../components/TooltipWrapper';
|
||||||
|
import Timer from '../../components/Timer';
|
||||||
|
|
||||||
|
const CHART_STATUS_MAP = {
|
||||||
|
failed: 'danger',
|
||||||
|
loading: 'warning',
|
||||||
|
success: 'success',
|
||||||
|
};
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
actions: PropTypes.object.isRequired,
|
actions: PropTypes.object.isRequired,
|
||||||
@ -22,8 +29,10 @@ const propTypes = {
|
|||||||
query: PropTypes.string.isRequired,
|
query: PropTypes.string.isRequired,
|
||||||
column_formats: PropTypes.object,
|
column_formats: PropTypes.object,
|
||||||
data: PropTypes.any,
|
data: PropTypes.any,
|
||||||
isChartLoading: PropTypes.bool,
|
chartStatus: PropTypes.bool,
|
||||||
isStarred: PropTypes.bool.isRequired,
|
isStarred: PropTypes.bool.isRequired,
|
||||||
|
chartUpdateStartTime: PropTypes.string.isRequired,
|
||||||
|
chartUpdateEndTime: PropTypes.string.isRequired,
|
||||||
alert: PropTypes.string,
|
alert: PropTypes.string,
|
||||||
table_name: PropTypes.string,
|
table_name: PropTypes.string,
|
||||||
};
|
};
|
||||||
@ -157,7 +166,7 @@ class ChartContainer extends React.Component {
|
|||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.props.isChartLoading) {
|
if (this.props.chartStatus === 'loading') {
|
||||||
return (<img alt="loading" width="25" src="/static/assets/images/loading.gif" />);
|
return (<img alt="loading" width="25" src="/static/assets/images/loading.gif" />);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@ -205,6 +214,13 @@ class ChartContainer extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div className="pull-right">
|
<div className="pull-right">
|
||||||
|
<Timer
|
||||||
|
startTime={this.props.chartUpdateStartTime}
|
||||||
|
endTime={this.props.chartUpdateEndTime}
|
||||||
|
isRunning={this.props.chartStatus === 'loading'}
|
||||||
|
state={CHART_STATUS_MAP[this.props.chartStatus]}
|
||||||
|
style={{ 'font-size': '10px', 'margin-right': '5px' }}
|
||||||
|
/>
|
||||||
<ExploreActionButtons
|
<ExploreActionButtons
|
||||||
slice={this.state.mockSlice}
|
slice={this.state.mockSlice}
|
||||||
canDownload={this.props.can_download}
|
canDownload={this.props.can_download}
|
||||||
@ -232,10 +248,12 @@ function mapStateToProps(state) {
|
|||||||
csv_endpoint: state.viz.csv_endpoint,
|
csv_endpoint: state.viz.csv_endpoint,
|
||||||
json_endpoint: state.viz.json_endpoint,
|
json_endpoint: state.viz.json_endpoint,
|
||||||
standalone_endpoint: state.viz.standalone_endpoint,
|
standalone_endpoint: state.viz.standalone_endpoint,
|
||||||
|
chartUpdateStartTime: state.chartUpdateStartTime,
|
||||||
|
chartUpdateEndTime: state.chartUpdateEndTime,
|
||||||
query: state.viz.query,
|
query: state.viz.query,
|
||||||
column_formats: state.viz.column_formats,
|
column_formats: state.viz.column_formats,
|
||||||
data: state.viz.data,
|
data: state.viz.data,
|
||||||
isChartLoading: state.isChartLoading,
|
chartStatus: state.chartStatus,
|
||||||
isStarred: state.isStarred,
|
isStarred: state.isStarred,
|
||||||
alert: state.chartAlert,
|
alert: state.chartAlert,
|
||||||
table_name: state.viz.form_data.datasource_name,
|
table_name: state.viz.form_data.datasource_name,
|
||||||
|
@ -30,6 +30,7 @@ class ExploreViewContainer extends React.Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
window.addEventListener('resize', this.handleResize.bind(this));
|
window.addEventListener('resize', this.handleResize.bind(this));
|
||||||
|
this.props.actions.updateChartStatus('success');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
@ -5,6 +5,7 @@ import ExploreViewContainer from './components/ExploreViewContainer';
|
|||||||
import { createStore, applyMiddleware, compose } from 'redux';
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
|
import { now } from '../modules/dates';
|
||||||
|
|
||||||
// jquery and bootstrap required to make bootstrap dropdown menu's work
|
// jquery and bootstrap required to make bootstrap dropdown menu's work
|
||||||
const $ = window.$ = require('jquery'); // eslint-disable-line
|
const $ = window.$ = require('jquery'); // eslint-disable-line
|
||||||
@ -26,6 +27,9 @@ const bootstrappedState = Object.assign(
|
|||||||
datasource_type: bootstrapData.datasource_type,
|
datasource_type: bootstrapData.datasource_type,
|
||||||
viz: bootstrapData.viz,
|
viz: bootstrapData.viz,
|
||||||
user_id: bootstrapData.user_id,
|
user_id: bootstrapData.user_id,
|
||||||
|
chartUpdateStartTime: now(),
|
||||||
|
chartUpdateEndTime: null,
|
||||||
|
chartStatus: 'loading',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
bootstrappedState.viz.form_data.datasource = parseInt(bootstrapData.datasource_id, 10);
|
bootstrappedState.viz.form_data.datasource = parseInt(bootstrapData.datasource_id, 10);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { defaultFormData } from '../stores/store';
|
import { defaultFormData } from '../stores/store';
|
||||||
import * as actions from '../actions/exploreActions';
|
import * as actions from '../actions/exploreActions';
|
||||||
import { addToArr, removeFromArr, alterInArr } from '../../../utils/reducerUtils';
|
import { addToArr, removeFromArr, alterInArr } from '../../../utils/reducerUtils';
|
||||||
|
import { now } from '../../modules/dates';
|
||||||
|
|
||||||
export const exploreReducer = function (state, action) {
|
export const exploreReducer = function (state, action) {
|
||||||
const actionHandlers = {
|
const actionHandlers = {
|
||||||
@ -113,19 +114,32 @@ export const exploreReducer = function (state, action) {
|
|||||||
query: action.viz.query,
|
query: action.viz.query,
|
||||||
data: action.viz.data,
|
data: action.viz.data,
|
||||||
};
|
};
|
||||||
|
const chartUpdateEndTime = now();
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
{},
|
{},
|
||||||
state,
|
state,
|
||||||
{
|
{
|
||||||
viz: Object.assign({}, state.viz, vizUpdates),
|
viz: Object.assign({}, state.viz, vizUpdates),
|
||||||
isChartLoading: false,
|
chartStatus: 'success',
|
||||||
|
chartUpdateEndTime,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[actions.CHART_UPDATE_STARTED]() {
|
[actions.CHART_UPDATE_STARTED]() {
|
||||||
return Object.assign({}, state, { isChartLoading: true });
|
const chartUpdateStartTime = now();
|
||||||
|
return Object.assign({}, state,
|
||||||
|
{ chartStatus: 'loading', chartUpdateEndTime: null, chartUpdateStartTime });
|
||||||
},
|
},
|
||||||
[actions.CHART_UPDATE_FAILED]() {
|
[actions.CHART_UPDATE_FAILED]() {
|
||||||
return Object.assign({}, state, { isChartLoading: false, chartAlert: action.error });
|
const chartUpdateEndTime = now();
|
||||||
|
return Object.assign({}, state,
|
||||||
|
{ chartStatus: 'failed', chartAlert: action.error, chartUpdateEndTime });
|
||||||
|
},
|
||||||
|
[actions.UPDATE_CHART_STATUS]() {
|
||||||
|
const newState = Object.assign({}, state, { chartStatus: action.status });
|
||||||
|
if (action.status === 'success' || action.status === 'failed') {
|
||||||
|
newState.chartUpdateEndTime = now();
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
},
|
},
|
||||||
[actions.REMOVE_CHART_ALERT]() {
|
[actions.REMOVE_CHART_ALERT]() {
|
||||||
return Object.assign({}, state, { chartAlert: null });
|
return Object.assign({}, state, { chartAlert: null });
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Timer from '../../../javascripts/SqlLab/components/Timer';
|
import Timer from '../../../javascripts/components/Timer';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { describe, it } from 'mocha';
|
import { describe, it } from 'mocha';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { queries } from './fixtures';
|
import { now } from '../../../javascripts/modules/dates';
|
||||||
|
|
||||||
|
|
||||||
describe('Timer', () => {
|
describe('Timer', () => {
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
query: queries[0],
|
startTime: now(),
|
||||||
|
endTime: null,
|
||||||
|
isRunning: true,
|
||||||
|
state: 'warning',
|
||||||
};
|
};
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(React.isValidElement(<Timer />)).to.equal(true);
|
expect(React.isValidElement(<Timer />)).to.equal(true);
|
||||||
|
Loading…
Reference in New Issue
Block a user