PanelChrome.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. // Libraries
  2. import React, { PureComponent } from 'react';
  3. import { AutoSizer } from 'react-virtualized';
  4. // Services
  5. import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
  6. // Components
  7. import { PanelHeader } from './PanelHeader/PanelHeader';
  8. import { DataPanel } from './DataPanel';
  9. import ErrorBoundary from '../../../core/components/ErrorBoundary/ErrorBoundary';
  10. // Utils
  11. import { applyPanelTimeOverrides, snapshotDataToPanelData } from 'app/features/dashboard/utils/panel';
  12. import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
  13. import { profiler } from 'app/core/profiler';
  14. // Types
  15. import { DashboardModel, PanelModel } from '../state';
  16. import { PanelPlugin } from 'app/types';
  17. import { DataQueryResponse, TimeRange, LoadingState, PanelData, DataQueryError } from '@grafana/ui';
  18. import variables from 'sass/_variables.generated.scss';
  19. import templateSrv from 'app/features/templating/template_srv';
  20. const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
  21. export interface Props {
  22. panel: PanelModel;
  23. dashboard: DashboardModel;
  24. plugin: PanelPlugin;
  25. isFullscreen: boolean;
  26. }
  27. export interface State {
  28. refreshCounter: number;
  29. renderCounter: number;
  30. timeInfo?: string;
  31. timeRange?: TimeRange;
  32. errorMessage: string | null;
  33. }
  34. export class PanelChrome extends PureComponent<Props, State> {
  35. timeSrv: TimeSrv = getTimeSrv();
  36. constructor(props) {
  37. super(props);
  38. this.state = {
  39. refreshCounter: 0,
  40. renderCounter: 0,
  41. errorMessage: null,
  42. };
  43. }
  44. componentDidMount() {
  45. this.props.panel.events.on('refresh', this.onRefresh);
  46. this.props.panel.events.on('render', this.onRender);
  47. this.props.dashboard.panelInitialized(this.props.panel);
  48. }
  49. componentWillUnmount() {
  50. this.props.panel.events.off('refresh', this.onRefresh);
  51. }
  52. onRefresh = () => {
  53. console.log('onRefresh');
  54. if (!this.isVisible) {
  55. return;
  56. }
  57. const { panel } = this.props;
  58. const timeData = applyPanelTimeOverrides(panel, this.timeSrv.timeRange());
  59. this.setState({
  60. refreshCounter: this.state.refreshCounter + 1,
  61. timeRange: timeData.timeRange,
  62. timeInfo: timeData.timeInfo,
  63. });
  64. };
  65. onRender = () => {
  66. this.setState({
  67. renderCounter: this.state.renderCounter + 1,
  68. });
  69. };
  70. onInterpolate = (value: string, format?: string) => {
  71. return templateSrv.replace(value, this.props.panel.scopedVars, format);
  72. };
  73. onDataResponse = (dataQueryResponse: DataQueryResponse) => {
  74. if (this.props.dashboard.isSnapshot()) {
  75. this.props.panel.snapshotData = dataQueryResponse.data;
  76. }
  77. // clear error state (if any)
  78. this.clearErrorState();
  79. // This event is used by old query editors and panel editor options
  80. this.props.panel.events.emit('data-received', dataQueryResponse.data);
  81. };
  82. onDataError = (message: string, error: DataQueryError) => {
  83. if (this.state.errorMessage !== message) {
  84. this.setState({ errorMessage: message });
  85. }
  86. // this event is used by old query editors
  87. this.props.panel.events.emit('data-error', error);
  88. };
  89. onPanelError = (message: string) => {
  90. if (this.state.errorMessage !== message) {
  91. this.setState({ errorMessage: message });
  92. }
  93. };
  94. clearErrorState() {
  95. if (this.state.errorMessage) {
  96. this.setState({ errorMessage: null });
  97. }
  98. }
  99. get isVisible() {
  100. return !this.props.dashboard.otherPanelInFullscreen(this.props.panel);
  101. }
  102. get hasPanelSnapshot() {
  103. const { panel } = this.props;
  104. return panel.snapshotData && panel.snapshotData.length;
  105. }
  106. get needsQueryExecution() {
  107. return this.hasPanelSnapshot || this.props.plugin.dataFormats.length > 0;
  108. }
  109. get getDataForPanel() {
  110. return this.hasPanelSnapshot ? snapshotDataToPanelData(this.props.panel) : null;
  111. }
  112. renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element {
  113. const { panel, plugin } = this.props;
  114. const { timeRange, renderCounter } = this.state;
  115. const PanelComponent = plugin.exports.reactPanel.panel;
  116. // This is only done to increase a counter that is used by backend
  117. // image rendering (phantomjs/headless chrome) to know when to capture image
  118. if (loading === LoadingState.Done) {
  119. profiler.renderingCompleted(panel.id);
  120. }
  121. return (
  122. <div className="panel-content">
  123. <PanelComponent
  124. loading={loading}
  125. panelData={panelData}
  126. timeRange={timeRange}
  127. options={panel.getOptions(plugin.exports.reactPanel.defaults)}
  128. width={width - 2 * variables.panelhorizontalpadding}
  129. height={height - PANEL_HEADER_HEIGHT - variables.panelverticalpadding}
  130. renderCounter={renderCounter}
  131. onInterpolate={this.onInterpolate}
  132. />
  133. </div>
  134. );
  135. }
  136. renderPanelBody = (width: number, height: number): JSX.Element => {
  137. const { panel } = this.props;
  138. const { refreshCounter, timeRange } = this.state;
  139. const { datasource, targets } = panel;
  140. return (
  141. <>
  142. {this.needsQueryExecution ? (
  143. <DataPanel
  144. panelId={panel.id}
  145. datasource={datasource}
  146. queries={targets}
  147. timeRange={timeRange}
  148. isVisible={this.isVisible}
  149. widthPixels={width}
  150. refreshCounter={refreshCounter}
  151. onDataResponse={this.onDataResponse}
  152. onError={this.onDataError}
  153. >
  154. {({ loading, panelData }) => {
  155. return this.renderPanelPlugin(loading, panelData, width, height);
  156. }}
  157. </DataPanel>
  158. ) : (
  159. this.renderPanelPlugin(LoadingState.Done, this.getDataForPanel, width, height)
  160. )}
  161. </>
  162. );
  163. };
  164. render() {
  165. const { dashboard, panel, isFullscreen } = this.props;
  166. const { errorMessage, timeInfo } = this.state;
  167. const { transparent } = panel;
  168. const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`;
  169. return (
  170. <AutoSizer>
  171. {({ width, height }) => {
  172. if (width === 0) {
  173. return null;
  174. }
  175. return (
  176. <div className={containerClassNames}>
  177. <PanelHeader
  178. panel={panel}
  179. dashboard={dashboard}
  180. timeInfo={timeInfo}
  181. title={panel.title}
  182. description={panel.description}
  183. scopedVars={panel.scopedVars}
  184. links={panel.links}
  185. error={errorMessage}
  186. isFullscreen={isFullscreen}
  187. />
  188. <ErrorBoundary>
  189. {({ error, errorInfo }) => {
  190. if (errorInfo) {
  191. this.onPanelError(error.message || DEFAULT_PLUGIN_ERROR);
  192. return null;
  193. }
  194. return this.renderPanelBody(width, height);
  195. }}
  196. </ErrorBoundary>
  197. </div>
  198. );
  199. }}
  200. </AutoSizer>
  201. );
  202. }
  203. }