PanelChrome.tsx 7.1 KB

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