DataPanel.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. // Library
  2. import React, { Component } from 'react';
  3. // Services
  4. import { DatasourceSrv, getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  5. // Utils
  6. import kbn from 'app/core/utils/kbn';
  7. // Types
  8. import {
  9. DataQueryOptions,
  10. DataQueryError,
  11. LoadingState,
  12. SeriesData,
  13. TimeRange,
  14. ScopedVars,
  15. toSeriesData,
  16. guessFieldTypes,
  17. } from '@grafana/ui';
  18. interface RenderProps {
  19. loading: LoadingState;
  20. data: SeriesData[];
  21. }
  22. export interface Props {
  23. datasource: string | null;
  24. queries: any[];
  25. panelId: number;
  26. dashboardId?: number;
  27. isVisible?: boolean;
  28. timeRange?: TimeRange;
  29. widthPixels: number;
  30. refreshCounter: number;
  31. minInterval?: string;
  32. maxDataPoints?: number;
  33. scopedVars?: ScopedVars;
  34. children: (r: RenderProps) => JSX.Element;
  35. onDataResponse?: (data?: SeriesData[]) => void;
  36. onError: (message: string, error: DataQueryError) => void;
  37. }
  38. export interface State {
  39. isFirstLoad: boolean;
  40. loading: LoadingState;
  41. data?: SeriesData[];
  42. }
  43. /**
  44. * All panels will be passed tables that have our best guess at colum type set
  45. *
  46. * This is also used by PanelChrome for snapshot support
  47. */
  48. export function getProcessedSeriesData(results?: any[]): SeriesData[] {
  49. if (!results) {
  50. return [];
  51. }
  52. const series: SeriesData[] = [];
  53. for (const r of results) {
  54. if (r) {
  55. series.push(guessFieldTypes(toSeriesData(r)));
  56. }
  57. }
  58. return series;
  59. }
  60. export class DataPanel extends Component<Props, State> {
  61. static defaultProps = {
  62. isVisible: true,
  63. dashboardId: 1,
  64. };
  65. dataSourceSrv: DatasourceSrv = getDatasourceSrv();
  66. isUnmounted = false;
  67. constructor(props: Props) {
  68. super(props);
  69. this.state = {
  70. loading: LoadingState.NotStarted,
  71. isFirstLoad: true,
  72. };
  73. }
  74. componentDidMount() {
  75. this.issueQueries();
  76. }
  77. componentWillUnmount() {
  78. this.isUnmounted = true;
  79. }
  80. async componentDidUpdate(prevProps: Props) {
  81. if (!this.hasPropsChanged(prevProps)) {
  82. return;
  83. }
  84. this.issueQueries();
  85. }
  86. hasPropsChanged(prevProps: Props) {
  87. return this.props.refreshCounter !== prevProps.refreshCounter;
  88. }
  89. private issueQueries = async () => {
  90. const {
  91. isVisible,
  92. queries,
  93. datasource,
  94. panelId,
  95. dashboardId,
  96. timeRange,
  97. widthPixels,
  98. maxDataPoints,
  99. scopedVars,
  100. onDataResponse,
  101. onError,
  102. } = this.props;
  103. if (!isVisible) {
  104. return;
  105. }
  106. if (!queries.length) {
  107. this.setState({ loading: LoadingState.Done });
  108. return;
  109. }
  110. this.setState({ loading: LoadingState.Loading });
  111. try {
  112. const ds = await this.dataSourceSrv.get(datasource, scopedVars);
  113. // TODO interpolate variables
  114. const minInterval = this.props.minInterval || ds.interval;
  115. const intervalRes = kbn.calculateInterval(timeRange, widthPixels, minInterval);
  116. const queryOptions: DataQueryOptions = {
  117. timezone: 'browser',
  118. panelId: panelId,
  119. dashboardId: dashboardId,
  120. range: timeRange,
  121. rangeRaw: timeRange.raw,
  122. interval: intervalRes.interval,
  123. intervalMs: intervalRes.intervalMs,
  124. targets: queries,
  125. maxDataPoints: maxDataPoints || widthPixels,
  126. scopedVars: scopedVars || {},
  127. cacheTimeout: null,
  128. };
  129. const resp = await ds.query(queryOptions);
  130. if (this.isUnmounted) {
  131. return;
  132. }
  133. // Make sure the data is SeriesData[]
  134. const data = getProcessedSeriesData(resp.data);
  135. if (onDataResponse) {
  136. onDataResponse(data);
  137. }
  138. this.setState({
  139. data,
  140. loading: LoadingState.Done,
  141. isFirstLoad: false,
  142. });
  143. } catch (err) {
  144. console.log('DataPanel error', err);
  145. let message = 'Query error';
  146. if (err.message) {
  147. message = err.message;
  148. } else if (err.data && err.data.message) {
  149. message = err.data.message;
  150. } else if (err.data && err.data.error) {
  151. message = err.data.error;
  152. } else if (err.status) {
  153. message = `Query error: ${err.status} ${err.statusText}`;
  154. }
  155. onError(message, err);
  156. this.setState({ isFirstLoad: false, loading: LoadingState.Error });
  157. }
  158. };
  159. render() {
  160. const { queries } = this.props;
  161. const { loading, isFirstLoad, data } = this.state;
  162. // do not render component until we have first data
  163. if (isFirstLoad && (loading === LoadingState.Loading || loading === LoadingState.NotStarted)) {
  164. return this.renderLoadingState();
  165. }
  166. if (!queries.length) {
  167. return (
  168. <div className="panel-empty">
  169. <p>Add a query to get some data!</p>
  170. </div>
  171. );
  172. }
  173. return (
  174. <>
  175. {loading === LoadingState.Loading && this.renderLoadingState()}
  176. {this.props.children({ loading, data })}
  177. </>
  178. );
  179. }
  180. private renderLoadingState(): JSX.Element {
  181. return (
  182. <div className="panel-loading">
  183. <i className="fa fa-spinner fa-spin" />
  184. </div>
  185. );
  186. }
  187. }