DataPanel.tsx 5.0 KB

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