mirror of
https://github.com/apache/superset.git
synced 2026-04-20 16:44:46 +00:00
230 lines
6.3 KiB
JavaScript
230 lines
6.3 KiB
JavaScript
/* eslint camelcase: 0 */
|
|
import React from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import Mustache from 'mustache';
|
|
import { Tooltip } from 'react-bootstrap';
|
|
|
|
import { d3format } from '../modules/utils';
|
|
import ChartBody from './ChartBody';
|
|
import Loading from '../components/Loading';
|
|
import { Logger, LOG_ACTIONS_RENDER_EVENT } from '../logger';
|
|
import StackTraceMessage from '../components/StackTraceMessage';
|
|
import visMap from '../../visualizations/main';
|
|
import sandboxedEval from '../modules/sandbox';
|
|
import './chart.css';
|
|
|
|
const propTypes = {
|
|
annotationData: PropTypes.object,
|
|
actions: PropTypes.object,
|
|
chartKey: PropTypes.string.isRequired,
|
|
containerId: PropTypes.string.isRequired,
|
|
datasource: PropTypes.object.isRequired,
|
|
formData: PropTypes.object.isRequired,
|
|
height: PropTypes.number,
|
|
width: PropTypes.number,
|
|
setControlValue: PropTypes.func,
|
|
timeout: PropTypes.number,
|
|
vizType: PropTypes.string.isRequired,
|
|
// state
|
|
chartAlert: PropTypes.string,
|
|
chartStatus: PropTypes.string,
|
|
chartUpdateEndTime: PropTypes.number,
|
|
chartUpdateStartTime: PropTypes.number,
|
|
latestQueryFormData: PropTypes.object,
|
|
queryRequest: PropTypes.object,
|
|
queryResponse: PropTypes.object,
|
|
lastRendered: PropTypes.number,
|
|
triggerQuery: PropTypes.bool,
|
|
// dashboard callbacks
|
|
addFilter: PropTypes.func,
|
|
getFilters: PropTypes.func,
|
|
clearFilter: PropTypes.func,
|
|
removeFilter: PropTypes.func,
|
|
};
|
|
|
|
const defaultProps = {
|
|
addFilter: () => ({}),
|
|
getFilters: () => ({}),
|
|
clearFilter: () => ({}),
|
|
removeFilter: () => ({}),
|
|
};
|
|
|
|
class Chart extends React.PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {};
|
|
// these properties are used by visualizations
|
|
this.annotationData = props.annotationData;
|
|
this.containerId = props.containerId;
|
|
this.selector = `#${this.containerId}`;
|
|
this.formData = props.formData;
|
|
this.datasource = props.datasource;
|
|
this.addFilter = this.addFilter.bind(this);
|
|
this.getFilters = this.getFilters.bind(this);
|
|
this.clearFilter = this.clearFilter.bind(this);
|
|
this.removeFilter = this.removeFilter.bind(this);
|
|
this.height = this.height.bind(this);
|
|
this.width = this.width.bind(this);
|
|
}
|
|
|
|
componentDidMount() {
|
|
if (this.props.triggerQuery) {
|
|
this.props.actions.runQuery(this.props.formData, false,
|
|
this.props.timeout,
|
|
this.props.chartKey,
|
|
);
|
|
}
|
|
}
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
this.annotationData = nextProps.annotationData;
|
|
this.containerId = nextProps.containerId;
|
|
this.selector = `#${this.containerId}`;
|
|
this.formData = nextProps.formData;
|
|
this.datasource = nextProps.datasource;
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
if (
|
|
this.props.queryResponse &&
|
|
['success', 'rendered'].indexOf(this.props.chartStatus) > -1 &&
|
|
!this.props.queryResponse.error && (
|
|
prevProps.annotationData !== this.props.annotationData ||
|
|
prevProps.queryResponse !== this.props.queryResponse ||
|
|
prevProps.height !== this.props.height ||
|
|
prevProps.width !== this.props.width ||
|
|
prevProps.lastRendered !== this.props.lastRendered)
|
|
) {
|
|
this.renderViz();
|
|
}
|
|
}
|
|
|
|
getFilters() {
|
|
return this.props.getFilters();
|
|
}
|
|
|
|
setTooltip(tooltip) {
|
|
this.setState({ tooltip });
|
|
}
|
|
|
|
addFilter(col, vals, merge = true, refresh = true) {
|
|
this.props.addFilter(col, vals, merge, refresh);
|
|
}
|
|
|
|
clearFilter() {
|
|
this.props.clearFilter();
|
|
}
|
|
|
|
removeFilter(col, vals, refresh = true) {
|
|
this.props.removeFilter(col, vals, refresh);
|
|
}
|
|
|
|
clearError() {
|
|
this.setState({
|
|
errorMsg: null,
|
|
});
|
|
}
|
|
|
|
width() {
|
|
return this.props.width || this.container.el.offsetWidth;
|
|
}
|
|
|
|
height() {
|
|
return this.props.height || this.container.el.offsetHeight;
|
|
}
|
|
|
|
d3format(col, number) {
|
|
const { datasource } = this.props;
|
|
const format = (datasource.column_formats && datasource.column_formats[col]) || '0.3s';
|
|
|
|
return d3format(format, number);
|
|
}
|
|
|
|
render_template(s) {
|
|
const context = {
|
|
width: this.width(),
|
|
height: this.height(),
|
|
};
|
|
return Mustache.render(s, context);
|
|
}
|
|
|
|
renderTooltip() {
|
|
if (this.state.tooltip) {
|
|
/* eslint-disable react/no-danger */
|
|
return (
|
|
<Tooltip
|
|
className="chart-tooltip"
|
|
id="chart-tooltip"
|
|
placement="right"
|
|
positionTop={this.state.tooltip.y - 10}
|
|
positionLeft={this.state.tooltip.x + 30}
|
|
arrowOffsetTop={10}
|
|
>
|
|
<div dangerouslySetInnerHTML={{ __html: this.state.tooltip.content }} />
|
|
</Tooltip>
|
|
);
|
|
/* eslint-enable react/no-danger */
|
|
}
|
|
return null;
|
|
}
|
|
|
|
renderViz() {
|
|
const viz = visMap[this.props.vizType];
|
|
const fd = this.props.formData;
|
|
const qr = this.props.queryResponse;
|
|
const renderStart = Logger.getTimestamp();
|
|
try {
|
|
// Executing user-defined data mutator function
|
|
if (fd.js_data) {
|
|
qr.data = sandboxedEval(fd.js_data)(qr.data);
|
|
}
|
|
// [re]rendering the visualization
|
|
viz(this, qr, this.props.setControlValue);
|
|
Logger.append(LOG_ACTIONS_RENDER_EVENT, {
|
|
label: this.props.chartKey,
|
|
vis_type: this.props.vizType,
|
|
start_offset: renderStart,
|
|
duration: Logger.getTimestamp() - renderStart,
|
|
});
|
|
this.props.actions.chartRenderingSucceeded(this.props.chartKey);
|
|
} catch (e) {
|
|
this.props.actions.chartRenderingFailed(e, this.props.chartKey);
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const isLoading = this.props.chartStatus === 'loading';
|
|
return (
|
|
<div className={`token col-md-12 ${isLoading ? 'is-loading' : ''}`}>
|
|
{this.renderTooltip()}
|
|
{isLoading &&
|
|
<Loading size={25} />
|
|
}
|
|
{this.props.chartAlert &&
|
|
<StackTraceMessage
|
|
message={this.props.chartAlert}
|
|
queryResponse={this.props.queryResponse}
|
|
/>
|
|
}
|
|
|
|
{!this.props.chartAlert &&
|
|
<ChartBody
|
|
containerId={this.containerId}
|
|
vizType={this.props.vizType}
|
|
height={this.height}
|
|
width={this.width}
|
|
ref={(inner) => {
|
|
this.container = inner;
|
|
}}
|
|
/>
|
|
}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
Chart.propTypes = propTypes;
|
|
Chart.defaultProps = defaultProps;
|
|
|
|
export default Chart;
|