DataPanel.tsx 4.9 KB

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