grafana/public/app/features/dashboard/dashgrid/PanelChrome.tsx

257 lines
7.3 KiB
TypeScript
Raw Normal View History

// Libraries
2018-11-20 10:01:58 -06:00
import React, { PureComponent } from 'react';
2018-11-13 11:50:12 -06:00
import { AutoSizer } from 'react-virtualized';
// Services
import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
// Components
import { PanelHeader } from './PanelHeader/PanelHeader';
import { DataPanel } from './DataPanel';
import ErrorBoundary from '../../../core/components/ErrorBoundary/ErrorBoundary';
// Utils
2019-03-07 15:09:53 -06:00
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
2018-11-14 06:20:19 -06:00
import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
import { profiler } from 'app/core/profiler';
import config from 'app/core/config';
// Types
import { DashboardModel, PanelModel } from '../state';
import { PanelPlugin } from 'app/types';
import { TimeRange, LoadingState, DataQueryError, SeriesData, toLegacyResponseData } from '@grafana/ui';
2019-03-05 23:56:52 -06:00
import { ScopedVars } from '@grafana/ui';
2018-06-19 07:51:57 -05:00
2019-01-15 10:15:46 -06:00
import templateSrv from 'app/features/templating/template_srv';
import { getProcessedSeriesData } from './DataPanel';
2019-03-21 23:52:58 -05:00
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
export interface Props {
2018-06-19 07:51:57 -05:00
panel: PanelModel;
dashboard: DashboardModel;
2018-11-20 10:01:58 -06:00
plugin: PanelPlugin;
isFullscreen: boolean;
isEditing: boolean;
2018-06-19 07:51:57 -05:00
}
export interface State {
refreshCounter: number;
2018-11-05 10:46:09 -06:00
renderCounter: number;
timeInfo?: string;
timeRange?: TimeRange;
errorMessage: string | null;
}
export class PanelChrome extends PureComponent<Props, State> {
timeSrv: TimeSrv = getTimeSrv();
2018-06-19 07:51:57 -05:00
constructor(props) {
super(props);
this.state = {
refreshCounter: 0,
2018-11-05 10:46:09 -06:00
renderCounter: 0,
errorMessage: null,
};
}
componentDidMount() {
this.props.panel.events.on('refresh', this.onRefresh);
2018-11-05 10:46:09 -06:00
this.props.panel.events.on('render', this.onRender);
2018-10-14 09:31:20 -05:00
this.props.dashboard.panelInitialized(this.props.panel);
}
componentWillUnmount() {
this.props.panel.events.off('refresh', this.onRefresh);
}
onRefresh = () => {
console.log('onRefresh');
if (!this.isVisible) {
return;
}
const { panel } = this.props;
const timeData = applyPanelTimeOverrides(panel, this.timeSrv.timeRange());
this.setState({
refreshCounter: this.state.refreshCounter + 1,
timeRange: timeData.timeRange,
timeInfo: timeData.timeInfo,
});
};
2018-11-05 10:46:09 -06:00
onRender = () => {
this.setState({
renderCounter: this.state.renderCounter + 1,
});
};
2019-03-05 23:56:52 -06:00
replaceVariables = (value: string, extraVars?: ScopedVars, format?: string) => {
let vars = this.props.panel.scopedVars;
if (extraVars) {
vars = vars ? { ...vars, ...extraVars } : extraVars;
}
return templateSrv.replace(value, vars, format);
2019-01-15 10:15:46 -06:00
};
onDataResponse = (data?: SeriesData[]) => {
2019-01-30 03:39:42 -06:00
if (this.props.dashboard.isSnapshot()) {
this.props.panel.snapshotData = data;
2019-01-30 03:39:42 -06:00
}
// clear error state (if any)
this.clearErrorState();
if (this.props.isEditing) {
const events = this.props.panel.events;
if (!data) {
data = [];
}
// Angular query editors expect TimeSeries|TableData
events.emit('data-received', data.map(v => toLegacyResponseData(v)));
// Notify react query editors
events.emit('series-data-received', data);
}
};
onDataError = (message: string, error: DataQueryError) => {
if (this.state.errorMessage !== message) {
this.setState({ errorMessage: message });
}
// this event is used by old query editors
this.props.panel.events.emit('data-error', error);
};
onPanelError = (message: string) => {
if (this.state.errorMessage !== message) {
this.setState({ errorMessage: message });
}
2019-01-30 03:39:42 -06:00
};
clearErrorState() {
if (this.state.errorMessage) {
this.setState({ errorMessage: null });
}
}
get isVisible() {
2018-10-14 09:31:20 -05:00
return !this.props.dashboard.otherPanelInFullscreen(this.props.panel);
}
get hasPanelSnapshot() {
const { panel } = this.props;
return panel.snapshotData && panel.snapshotData.length;
}
get needsQueryExecution() {
return this.hasPanelSnapshot || this.props.plugin.dataFormats.length > 0;
}
get getDataForPanel() {
return this.hasPanelSnapshot ? getProcessedSeriesData(this.props.panel.snapshotData) : null;
}
renderPanelPlugin(loading: LoadingState, data: SeriesData[], width: number, height: number): JSX.Element {
2019-01-30 03:39:42 -06:00
const { panel, plugin } = this.props;
const { timeRange, renderCounter } = this.state;
const PanelComponent = plugin.exports.reactPanel.panel;
2019-01-30 03:39:42 -06:00
// This is only done to increase a counter that is used by backend
// image rendering (phantomjs/headless chrome) to know when to capture image
if (loading === LoadingState.Done) {
profiler.renderingCompleted(panel.id);
}
2019-01-30 03:39:42 -06:00
return (
<div className="panel-content">
<PanelComponent
loading={loading}
2019-03-07 14:13:38 -06:00
data={data}
2019-01-30 03:39:42 -06:00
timeRange={timeRange}
options={panel.getOptions(plugin.exports.reactPanel.defaults)}
width={width - 2 * config.theme.panelPadding.horizontal}
height={height - PANEL_HEADER_HEIGHT - config.theme.panelPadding.vertical}
2019-01-30 03:39:42 -06:00
renderCounter={renderCounter}
replaceVariables={this.replaceVariables}
2019-01-30 03:39:42 -06:00
/>
</div>
);
}
renderPanelBody = (width: number, height: number): JSX.Element => {
const { panel } = this.props;
const { refreshCounter, timeRange } = this.state;
const { datasource, targets } = panel;
return (
<>
{this.needsQueryExecution ? (
<DataPanel
panelId={panel.id}
datasource={datasource}
queries={targets}
timeRange={timeRange}
isVisible={this.isVisible}
widthPixels={width}
refreshCounter={refreshCounter}
2019-03-04 03:42:59 -06:00
scopedVars={panel.scopedVars}
onDataResponse={this.onDataResponse}
onError={this.onDataError}
>
2019-03-07 14:13:38 -06:00
{({ loading, data }) => {
return this.renderPanelPlugin(loading, data, width, height);
}}
</DataPanel>
) : (
this.renderPanelPlugin(LoadingState.Done, this.getDataForPanel, width, height)
)}
</>
);
};
render() {
const { dashboard, panel, isFullscreen } = this.props;
const { errorMessage, timeInfo } = this.state;
const { transparent } = panel;
2018-12-06 03:34:27 -06:00
const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`;
2018-06-19 07:51:57 -05:00
return (
<AutoSizer>
{({ width, height }) => {
if (width === 0) {
return null;
}
return (
2018-12-06 03:34:27 -06:00
<div className={containerClassNames}>
<PanelHeader
panel={panel}
dashboard={dashboard}
timeInfo={timeInfo}
title={panel.title}
description={panel.description}
scopedVars={panel.scopedVars}
links={panel.links}
error={errorMessage}
isFullscreen={isFullscreen}
/>
<ErrorBoundary>
{({ error, errorInfo }) => {
if (errorInfo) {
this.onPanelError(error.message || DEFAULT_PLUGIN_ERROR);
return null;
}
return this.renderPanelBody(width, height);
}}
</ErrorBoundary>
</div>
);
}}
</AutoSizer>
2018-06-19 07:51:57 -05:00
);
}
}