Simplifying the viz interface (#2005)

This commit is contained in:
Maxime Beauchemin
2017-01-24 14:03:17 -08:00
committed by GitHub
parent 1c338ba742
commit e46ba2b4a4
26 changed files with 1899 additions and 2192 deletions

View File

@@ -1,6 +1,7 @@
/* eslint camelcase: 0 */
const $ = window.$ = require('jquery');
const FAVESTAR_BASE_URL = '/superset/favstar/slice';
import { getExploreUrl } from '../exploreUtils';
export const SET_DATASOURCE_TYPE = 'SET_DATASOURCE_TYPE';
export function setDatasourceType(datasourceType) {
@@ -89,13 +90,18 @@ export function chartUpdateStarted() {
}
export const CHART_UPDATE_SUCCEEDED = 'CHART_UPDATE_SUCCEEDED';
export function chartUpdateSucceeded(query) {
return { type: CHART_UPDATE_SUCCEEDED, query };
export function chartUpdateSucceeded(queryResponse) {
return { type: CHART_UPDATE_SUCCEEDED, queryResponse };
}
export const CHART_UPDATE_FAILED = 'CHART_UPDATE_FAILED';
export function chartUpdateFailed(error, query) {
return { type: CHART_UPDATE_FAILED, error, query };
export function chartUpdateFailed(queryResponse) {
return { type: CHART_UPDATE_FAILED, queryResponse };
}
export const CHART_RENDERING_FAILED = 'CHART_RENDERING_FAILED';
export function chartRenderingFailed(error) {
return { type: CHART_RENDERING_FAILED, error };
}
export const UPDATE_EXPLORE_ENDPOINTS = 'UPDATE_EXPLORE_ENDPOINTS';
@@ -167,3 +173,16 @@ export const UPDATE_CHART_STATUS = 'UPDATE_CHART_STATUS';
export function updateChartStatus(status) {
return { type: UPDATE_CHART_STATUS, status };
}
export const RUN_QUERY = 'RUN_QUERY';
export function runQuery(formData, datasourceType) {
return function (dispatch) {
dispatch(updateChartStatus('loading'));
const url = getExploreUrl(formData, datasourceType, 'json');
$.getJSON(url, function (queryResponse) {
dispatch(chartUpdateSucceeded(queryResponse));
}).fail(function (err) {
dispatch(chartUpdateFailed(err));
});
};
}

View File

@@ -23,12 +23,8 @@ const propTypes = {
viz_type: PropTypes.string.isRequired,
height: PropTypes.string.isRequired,
containerId: PropTypes.string.isRequired,
json_endpoint: PropTypes.string.isRequired,
csv_endpoint: PropTypes.string.isRequired,
standalone_endpoint: PropTypes.string.isRequired,
query: PropTypes.string,
column_formats: PropTypes.object,
data: PropTypes.any,
chartStatus: PropTypes.string,
isStarred: PropTypes.bool.isRequired,
chartUpdateStartTime: PropTypes.number.isRequired,
@@ -37,88 +33,59 @@ const propTypes = {
table_name: PropTypes.string,
};
class ChartContainer extends React.Component {
class ChartContainer extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
selector: `#${props.containerId}`,
mockSlice: {},
viz: {},
};
}
componentWillMount() {
const mockSlice = this.getMockedSliceObject(this.props);
this.setState({
mockSlice,
viz: visMap[this.props.viz_type](mockSlice),
});
}
componentDidMount() {
this.state.viz.render();
}
componentWillReceiveProps(nextProps) {
if (nextProps.chartStatus === 'loading') {
const mockSlice = this.getMockedSliceObject(nextProps);
this.setState({
mockSlice,
viz: visMap[nextProps.viz_type](mockSlice),
});
renderViz() {
const mockSlice = this.getMockedSliceObject();
try {
visMap[this.props.viz_type](mockSlice, this.props.queryResponse);
this.setState({ mockSlice });
} catch (e) {
this.props.actions.chartRenderingFailed(e);
}
}
componentDidUpdate(prevProps) {
if (this.props.chartStatus === 'loading') {
this.state.viz.render();
}
if (prevProps.height !== this.props.height) {
this.state.viz.resize();
if (
prevProps.queryResponse !== this.props.queryResponse ||
prevProps.height !== this.props.height
) {
this.renderViz();
}
}
getMockedSliceObject(props) {
getMockedSliceObject() {
const props = this.props;
return {
viewSqlQuery: props.query,
data: {
csv_endpoint: props.csv_endpoint,
json_endpoint: props.json_endpoint,
standalone_endpoint: props.standalone_endpoint,
},
containerId: props.containerId,
jsonEndpoint: () => props.json_endpoint,
selector: this.state.selector,
container: {
html: (data) => {
// this should be a callback to clear the contents of the slice container
$(this.state.selector).html(data);
},
css: (dim, size) => {
// dimension can be 'height'
// pixel string can be '300px'
// should call callback to adjust height of chart
$(this.state.selector).css(dim, size);
},
height: () => parseInt(this.props.height, 10) - 100,
show: () => { this.render(); },
height: () => parseInt(props.height, 10) - 100,
show: () => { },
get: (n) => ($(this.state.selector).get(n)),
find: (classname) => ($(this.state.selector).find(classname)),
},
width: () => this.chartContainerRef.getBoundingClientRect().width,
height: () => parseInt(this.props.height, 10) - 100,
selector: this.state.selector,
height: () => parseInt(props.height, 10) - 100,
setFilter: () => {
// set filter according to data in store
@@ -130,32 +97,25 @@ class ChartContainer extends React.Component {
{}
),
done: (payload) => {
// finished rendering callback
// Todo: end timer and chartLoading set to success
props.actions.chartUpdateSucceeded(payload.query);
},
done: () => {},
clearError: () => {
// no need to do anything here since Alert is closable
// query button will also remove Alert
},
error(msg) {
let payload = { error: msg };
try {
payload = JSON.parse(msg);
} catch (e) {
// pass
}
props.actions.chartUpdateFailed(payload.error, payload.query);
},
error() {},
d3format: (col, number) => {
// mock d3format function in Slice object in superset.js
const format = props.column_formats[col];
return d3format(format, number);
},
data: {
csv_endpoint: props.queryResponse.csv_endpoint,
json_endpoint: props.queryResponse.json_endpoint,
standalone_endpoint: props.queryResponse.standalone_endpoint,
},
};
}
@@ -199,7 +159,7 @@ class ChartContainer extends React.Component {
}
<div
id={this.props.containerId}
ref={(ref) => { this.chartContainerRef = ref; }}
ref={ref => { this.chartContainerRef = ref; }}
className={this.props.viz_type}
style={{
opacity: loading ? '0.25' : '1',
@@ -251,11 +211,13 @@ class ChartContainer extends React.Component {
state={CHART_STATUS_MAP[this.props.chartStatus]}
style={{ fontSize: '10px', marginRight: '5px' }}
/>
<ExploreActionButtons
slice={this.state.mockSlice}
canDownload={this.props.can_download}
query={this.props.query}
/>
{this.state.mockSlice &&
<ExploreActionButtons
slice={this.state.mockSlice}
canDownload={this.props.can_download}
query={this.props.queryResponse.query}
/>
}
</div>
</div>
}
@@ -276,18 +238,15 @@ function mapStateToProps(state) {
slice_name: state.viz.form_data.slice_name,
viz_type: state.viz.form_data.viz_type,
can_download: state.can_download,
csv_endpoint: state.viz.csv_endpoint,
json_endpoint: state.viz.json_endpoint,
standalone_endpoint: state.viz.standalone_endpoint,
chartUpdateStartTime: state.chartUpdateStartTime,
chartUpdateEndTime: state.chartUpdateEndTime,
query: state.viz.query,
column_formats: state.viz.column_formats,
data: state.viz.data,
chartStatus: state.chartStatus,
isStarred: state.isStarred,
alert: state.chartAlert,
table_name: state.viz.form_data.datasource_name,
queryResponse: state.queryResponse,
};
}

View File

@@ -30,6 +30,7 @@ class ExploreViewContainer extends React.Component {
componentDidMount() {
window.addEventListener('resize', this.handleResize.bind(this));
this.runQuery();
}
componentWillReceiveProps(nextProps) {
@@ -38,7 +39,7 @@ class ExploreViewContainer extends React.Component {
&& autoQueryFields.indexOf(field) !== -1)
);
if (refreshChart) {
this.onQuery(nextProps.form_data);
this.onQuery();
}
}
@@ -46,12 +47,12 @@ class ExploreViewContainer extends React.Component {
window.removeEventListener('resize', this.handleResize.bind(this));
}
onQuery(form_data) {
this.props.actions.chartUpdateStarted();
onQuery() {
this.runQuery();
history.pushState(
{},
document.title,
getExploreUrl(form_data, this.props.datasource_type)
getExploreUrl(this.props.form_data, this.props.datasource_type)
);
// remove alerts when query
this.props.actions.removeControlPanelAlert();
@@ -63,6 +64,11 @@ class ExploreViewContainer extends React.Component {
return `${window.innerHeight - navHeight}px`;
}
runQuery() {
this.props.actions.runQuery(this.props.form_data, this.props.datasource_type);
}
handleResize() {
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
@@ -118,7 +124,7 @@ class ExploreViewContainer extends React.Component {
<div className="col-sm-4">
<QueryAndSaveBtns
canAdd="True"
onQuery={this.onQuery.bind(this, this.props.form_data)}
onQuery={this.onQuery.bind(this)}
onSave={this.toggleModal.bind(this)}
disabled={this.props.chartStatus === 'loading'}
errorMessage={this.renderErrorMessage()}
@@ -128,7 +134,7 @@ class ExploreViewContainer extends React.Component {
actions={this.props.actions}
form_data={this.props.form_data}
datasource_type={this.props.datasource_type}
onQuery={this.onQuery.bind(this, this.props.form_data)}
onQuery={this.onQuery.bind(this)}
/>
</div>
<div className="col-sm-8">
@@ -147,10 +153,10 @@ ExploreViewContainer.propTypes = propTypes;
function mapStateToProps(state) {
return {
datasource_type: state.datasource_type,
form_data: state.viz.form_data,
chartStatus: state.chartStatus,
datasource_type: state.datasource_type,
fields: state.fields,
form_data: state.viz.form_data,
};
}

View File

@@ -32,6 +32,7 @@ const bootstrappedState = Object.assign(
chartUpdateStartTime: now(),
chartUpdateEndTime: null,
chartStatus: 'loading',
queryResponse: null,
}
);
bootstrappedState.viz.form_data.datasource = parseInt(bootstrapData.datasource_id, 10);

View File

@@ -2,7 +2,6 @@
import { defaultFormData } from '../stores/store';
import * as actions from '../actions/exploreActions';
import { now } from '../../modules/dates';
import { getExploreUrl } from '../exploreUtils';
export const exploreReducer = function (state, action) {
const actionHandlers = {
@@ -70,34 +69,30 @@ export const exploreReducer = function (state, action) {
state,
{
chartStatus: 'success',
viz: Object.assign({}, state.viz, { query: action.query }),
queryResponse: action.queryResponse,
}
);
},
[actions.CHART_UPDATE_STARTED]() {
const chartUpdateStartTime = now();
const form_data = Object.assign({}, state.viz.form_data);
const datasource_type = state.datasource_type;
const vizUpdates = {
json_endpoint: getExploreUrl(form_data, datasource_type, 'json'),
csv_endpoint: getExploreUrl(form_data, datasource_type, 'csv'),
standalone_endpoint:
getExploreUrl(form_data, datasource_type, 'standalone'),
};
return Object.assign({}, state,
{
chartStatus: 'loading',
chartUpdateEndTime: null,
chartUpdateStartTime,
viz: Object.assign({}, state.viz, vizUpdates),
chartUpdateStartTime: now(),
});
},
[actions.CHART_RENDERING_FAILED]() {
return Object.assign({}, state, {
chartStatus: 'failed',
chartAlert: 'An error occurred while rendering the visualization: ' + action.error,
});
},
[actions.CHART_UPDATE_FAILED]() {
return Object.assign({}, state, {
chartStatus: 'failed',
chartAlert: action.error,
chartAlert: action.queryResponse.error,
chartUpdateEndTime: now(),
viz: Object.assign({}, state.viz, { query: action.query }),
queryResponse: action.queryResponse,
});
},
[actions.UPDATE_CHART_STATUS]() {
@@ -108,7 +103,10 @@ export const exploreReducer = function (state, action) {
return newState;
},
[actions.REMOVE_CHART_ALERT]() {
return Object.assign({}, state, { chartAlert: null });
if (state.chartAlert !== null) {
return Object.assign({}, state, { chartAlert: null });
}
return state;
},
[actions.SAVE_SLICE_FAILED]() {
return Object.assign({}, state, { saveModalAlert: 'Failed to save slice' });