PanelQueryRunner.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Libraries
  2. import { cloneDeep } from 'lodash';
  3. import { ReplaySubject, Unsubscribable, Observable } from 'rxjs';
  4. import { map } from 'rxjs/operators';
  5. // Services & Utils
  6. import { config } from 'app/core/config';
  7. import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  8. import kbn from 'app/core/utils/kbn';
  9. import templateSrv from 'app/features/templating/template_srv';
  10. import { runRequest, preProcessPanelData } from './runRequest';
  11. import { runSharedRequest, isSharedDashboardQuery } from '../../../plugins/datasource/dashboard';
  12. // Types
  13. import { PanelData, DataQuery, DataQueryRequest, DataSourceApi, DataSourceJsonData } from '@grafana/ui';
  14. import { TimeRange, DataTransformerConfig, transformDataFrame, ScopedVars } from '@grafana/data';
  15. export interface QueryRunnerOptions<
  16. TQuery extends DataQuery = DataQuery,
  17. TOptions extends DataSourceJsonData = DataSourceJsonData
  18. > {
  19. datasource: string | DataSourceApi<TQuery, TOptions>;
  20. queries: TQuery[];
  21. panelId: number;
  22. dashboardId?: number;
  23. timezone?: string;
  24. timeRange: TimeRange;
  25. timeInfo?: string; // String description of time range for display
  26. widthPixels: number;
  27. maxDataPoints: number | undefined | null;
  28. minInterval: string | undefined | null;
  29. scopedVars?: ScopedVars;
  30. cacheTimeout?: string;
  31. delayStateNotification?: number; // default 100ms.
  32. transformations?: DataTransformerConfig[];
  33. }
  34. let counter = 100;
  35. function getNextRequestId() {
  36. return 'Q' + counter++;
  37. }
  38. export class PanelQueryRunner {
  39. private subject?: ReplaySubject<PanelData>;
  40. private subscription?: Unsubscribable;
  41. private transformations?: DataTransformerConfig[];
  42. private lastResult?: PanelData;
  43. constructor() {
  44. this.subject = new ReplaySubject(1);
  45. }
  46. /**
  47. * Returns an observable that subscribes to the shared multi-cast subject (that reply last result).
  48. */
  49. getData(transform = true): Observable<PanelData> {
  50. if (transform) {
  51. return this.subject.pipe(
  52. map((data: PanelData) => {
  53. if (this.hasTransformations()) {
  54. const newSeries = transformDataFrame(this.transformations, data.series);
  55. return { ...data, series: newSeries };
  56. }
  57. return data;
  58. })
  59. );
  60. }
  61. // Just pass it directly
  62. return this.subject.pipe();
  63. }
  64. hasTransformations() {
  65. return config.featureToggles.transformations && this.transformations && this.transformations.length > 0;
  66. }
  67. async run(options: QueryRunnerOptions) {
  68. const {
  69. queries,
  70. timezone,
  71. datasource,
  72. panelId,
  73. dashboardId,
  74. timeRange,
  75. timeInfo,
  76. cacheTimeout,
  77. widthPixels,
  78. maxDataPoints,
  79. scopedVars,
  80. minInterval,
  81. // delayStateNotification,
  82. } = options;
  83. if (isSharedDashboardQuery(datasource)) {
  84. this.pipeToSubject(runSharedRequest(options));
  85. return;
  86. }
  87. const request: DataQueryRequest = {
  88. requestId: getNextRequestId(),
  89. timezone,
  90. panelId,
  91. dashboardId,
  92. range: timeRange,
  93. timeInfo,
  94. interval: '',
  95. intervalMs: 0,
  96. targets: cloneDeep(queries),
  97. maxDataPoints: maxDataPoints || widthPixels,
  98. scopedVars: scopedVars || {},
  99. cacheTimeout,
  100. startTime: Date.now(),
  101. };
  102. // Add deprecated property
  103. (request as any).rangeRaw = timeRange.raw;
  104. try {
  105. const ds = await getDataSource(datasource, request.scopedVars);
  106. if (ds.meta && !ds.meta.hiddenQueries) {
  107. request.targets = request.targets.filter(q => !q.hide);
  108. }
  109. // Attach the datasource name to each query
  110. request.targets = request.targets.map(query => {
  111. if (!query.datasource) {
  112. query.datasource = ds.name;
  113. }
  114. return query;
  115. });
  116. const lowerIntervalLimit = minInterval ? templateSrv.replace(minInterval, request.scopedVars) : ds.interval;
  117. const norm = kbn.calculateInterval(timeRange, widthPixels, lowerIntervalLimit);
  118. // make shallow copy of scoped vars,
  119. // and add built in variables interval and interval_ms
  120. request.scopedVars = Object.assign({}, request.scopedVars, {
  121. __interval: { text: norm.interval, value: norm.interval },
  122. __interval_ms: { text: norm.intervalMs.toString(), value: norm.intervalMs },
  123. });
  124. request.interval = norm.interval;
  125. request.intervalMs = norm.intervalMs;
  126. this.pipeToSubject(runRequest(ds, request));
  127. } catch (err) {
  128. console.log('PanelQueryRunner Error', err);
  129. }
  130. }
  131. private pipeToSubject(observable: Observable<PanelData>) {
  132. if (this.subscription) {
  133. this.subscription.unsubscribe();
  134. }
  135. this.subscription = observable.subscribe({
  136. next: (data: PanelData) => {
  137. this.lastResult = preProcessPanelData(data, this.lastResult);
  138. this.subject.next(this.lastResult);
  139. },
  140. });
  141. }
  142. setTransformations(transformations?: DataTransformerConfig[]) {
  143. this.transformations = transformations;
  144. }
  145. /**
  146. * Called when the panel is closed
  147. */
  148. destroy() {
  149. // Tell anyone listening that we are done
  150. if (this.subject) {
  151. this.subject.complete();
  152. }
  153. if (this.subscription) {
  154. this.subscription.unsubscribe();
  155. }
  156. }
  157. }
  158. async function getDataSource(
  159. datasource: string | DataSourceApi | null,
  160. scopedVars: ScopedVars
  161. ): Promise<DataSourceApi> {
  162. if (datasource && (datasource as any).query) {
  163. return datasource as DataSourceApi;
  164. }
  165. return await getDatasourceSrv().get(datasource as string, scopedVars);
  166. }