DataPanel.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. // Library
  2. import React, { Component } from 'react';
  3. import { Tooltip } from '@grafana/ui';
  4. import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary';
  5. // Services
  6. import { DatasourceSrv, getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  7. // Utils
  8. import kbn from 'app/core/utils/kbn';
  9. // Types
  10. import {
  11. DataQueryOptions,
  12. DataQueryResponse,
  13. LoadingState,
  14. PanelData,
  15. TableData,
  16. TimeRange,
  17. TimeSeries,
  18. } from '@grafana/ui';
  19. const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
  20. interface RenderProps {
  21. loading: LoadingState;
  22. panelData: PanelData;
  23. }
  24. export interface Props {
  25. datasource: string | null;
  26. queries: any[];
  27. panelId: number;
  28. dashboardId?: number;
  29. isVisible?: boolean;
  30. timeRange?: TimeRange;
  31. widthPixels: number;
  32. refreshCounter: number;
  33. minInterval?: string;
  34. maxDataPoints?: number;
  35. children: (r: RenderProps) => JSX.Element;
  36. onDataResponse?: (data: DataQueryResponse) => void;
  37. }
  38. export interface State {
  39. isFirstLoad: boolean;
  40. loading: LoadingState;
  41. errorMessage: string;
  42. response: DataQueryResponse;
  43. }
  44. export class DataPanel extends Component<Props, State> {
  45. static defaultProps = {
  46. isVisible: true,
  47. dashboardId: 1,
  48. };
  49. dataSourceSrv: DatasourceSrv = getDatasourceSrv();
  50. isUnmounted = false;
  51. constructor(props: Props) {
  52. super(props);
  53. this.state = {
  54. loading: LoadingState.NotStarted,
  55. errorMessage: '',
  56. response: {
  57. data: [],
  58. },
  59. isFirstLoad: true,
  60. };
  61. }
  62. componentDidMount() {
  63. this.issueQueries();
  64. }
  65. componentWillUnmount() {
  66. this.isUnmounted = true;
  67. }
  68. async componentDidUpdate(prevProps: Props) {
  69. if (!this.hasPropsChanged(prevProps)) {
  70. return;
  71. }
  72. this.issueQueries();
  73. }
  74. hasPropsChanged(prevProps: Props) {
  75. return this.props.refreshCounter !== prevProps.refreshCounter;
  76. }
  77. private issueQueries = async () => {
  78. const {
  79. isVisible,
  80. queries,
  81. datasource,
  82. panelId,
  83. dashboardId,
  84. timeRange,
  85. widthPixels,
  86. maxDataPoints,
  87. onDataResponse,
  88. } = this.props;
  89. if (!isVisible) {
  90. return;
  91. }
  92. if (!queries.length) {
  93. this.setState({ loading: LoadingState.Done });
  94. return;
  95. }
  96. this.setState({ loading: LoadingState.Loading, errorMessage: '' });
  97. try {
  98. const ds = await this.dataSourceSrv.get(datasource);
  99. // TODO interpolate variables
  100. const minInterval = this.props.minInterval || ds.interval;
  101. const intervalRes = kbn.calculateInterval(timeRange, widthPixels, minInterval);
  102. const queryOptions: DataQueryOptions = {
  103. timezone: 'browser',
  104. panelId: panelId,
  105. dashboardId: dashboardId,
  106. range: timeRange,
  107. rangeRaw: timeRange.raw,
  108. interval: intervalRes.interval,
  109. intervalMs: intervalRes.intervalMs,
  110. targets: queries,
  111. maxDataPoints: maxDataPoints || widthPixels,
  112. scopedVars: {},
  113. cacheTimeout: null,
  114. };
  115. const resp = await ds.query(queryOptions);
  116. if (this.isUnmounted) {
  117. return;
  118. }
  119. if (onDataResponse) {
  120. onDataResponse(resp);
  121. }
  122. this.setState({
  123. loading: LoadingState.Done,
  124. response: resp,
  125. isFirstLoad: false,
  126. });
  127. } catch (err) {
  128. console.log('Loading error', err);
  129. this.onError('Request Error');
  130. }
  131. };
  132. onError = (errorMessage: string) => {
  133. if (this.state.loading !== LoadingState.Error || this.state.errorMessage !== errorMessage) {
  134. this.setState({
  135. loading: LoadingState.Error,
  136. isFirstLoad: false,
  137. errorMessage: errorMessage,
  138. });
  139. }
  140. };
  141. getPanelData = () => {
  142. const { response } = this.state;
  143. if (response.data.length > 0 && (response.data[0] as TableData).type === 'table') {
  144. return {
  145. tableData: response.data[0] as TableData,
  146. timeSeries: null,
  147. };
  148. }
  149. return {
  150. timeSeries: response.data as TimeSeries[],
  151. tableData: null,
  152. };
  153. };
  154. render() {
  155. const { queries } = this.props;
  156. const { loading, isFirstLoad } = this.state;
  157. const panelData = this.getPanelData();
  158. if (isFirstLoad && loading === LoadingState.Loading) {
  159. return this.renderLoadingStates();
  160. }
  161. if (!queries.length) {
  162. return (
  163. <div className="panel-empty">
  164. <p>Add a query to get some data!</p>
  165. </div>
  166. );
  167. }
  168. return (
  169. <>
  170. {this.renderLoadingStates()}
  171. <ErrorBoundary>
  172. {({ error, errorInfo }) => {
  173. if (errorInfo) {
  174. this.onError(error.message || DEFAULT_PLUGIN_ERROR);
  175. return null;
  176. }
  177. return (
  178. <>
  179. {this.props.children({
  180. loading,
  181. panelData,
  182. })}
  183. </>
  184. );
  185. }}
  186. </ErrorBoundary>
  187. </>
  188. );
  189. }
  190. private renderLoadingStates(): JSX.Element {
  191. const { loading, errorMessage } = this.state;
  192. if (loading === LoadingState.Loading) {
  193. return (
  194. <div className="panel-loading">
  195. <i className="fa fa-spinner fa-spin" />
  196. </div>
  197. );
  198. } else if (loading === LoadingState.Error) {
  199. return (
  200. <Tooltip content={errorMessage} placement="bottom-start" theme="error">
  201. <div className="panel-info-corner panel-info-corner--error">
  202. <i className="fa" />
  203. <span className="panel-info-corner-inner" />
  204. </div>
  205. </Tooltip>
  206. );
  207. }
  208. return null;
  209. }
  210. }