From f9611250eaada546b9b24429dc010c4969d48c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 3 Oct 2019 05:15:59 -0700 Subject: [PATCH] Prometheus: Fixes so results in Panel always are sorted by query order (#19597) Fixes #19529 --- .../datasource/prometheus/datasource.test.ts | 21 ++++++- .../datasource/prometheus/datasource.ts | 59 ++++++++++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.test.ts b/public/app/plugins/datasource/prometheus/datasource.test.ts index a381eb8c1e7..36db7a754f7 100644 --- a/public/app/plugins/datasource/prometheus/datasource.test.ts +++ b/public/app/plugins/datasource/prometheus/datasource.test.ts @@ -1,6 +1,6 @@ import { PrometheusDatasource } from './datasource'; import { DataSourceInstanceSettings } from '@grafana/ui'; -import { PromOptions } from './types'; +import { PromContext, PromOptions } from './types'; import { dateTime, LoadingState } from '@grafana/data'; const defaultInstanceSettings: DataSourceInstanceSettings = { @@ -67,11 +67,11 @@ describe('datasource', () => { }); }); - it('with 2 queries, waits for all to finish until sending Done status', done => { + it('with 2 queries and used from Explore, sends results as they arrive', done => { expect.assertions(4); backendSrvMock.datasourceRequest.mockReturnValue(Promise.resolve(makePromResponse())); const responseStatus = [LoadingState.Loading, LoadingState.Done]; - ds.query(makeQuery([{}, {}])).subscribe({ + ds.query(makeQuery([{ context: PromContext.Explore }, { context: PromContext.Explore }])).subscribe({ next(next) { expect(next.data.length).not.toBe(0); expect(next.state).toBe(responseStatus.shift()); @@ -81,6 +81,20 @@ describe('datasource', () => { }, }); }); + + it('with 2 queries and used from Panel, waits for all to finish until sending Done status', done => { + expect.assertions(2); + backendSrvMock.datasourceRequest.mockReturnValue(Promise.resolve(makePromResponse())); + ds.query(makeQuery([{ context: PromContext.Panel }, { context: PromContext.Panel }])).subscribe({ + next(next) { + expect(next.data.length).not.toBe(0); + expect(next.state).toBe(LoadingState.Done); + }, + complete() { + done(); + }, + }); + }); }); }); @@ -92,6 +106,7 @@ function makeQuery(targets: any[]): any { start: dateTime().subtract(5, 'minutes'), end: dateTime(), expr: 'test', + showingGraph: true, ...t, }; }), diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index bc98a80a16d..86dbe75abc8 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -3,8 +3,8 @@ import _ from 'lodash'; import $ from 'jquery'; // Services & Utils import kbn from 'app/core/utils/kbn'; -import { AnnotationEvent, dateMath, DateTime, LoadingState, TimeRange } from '@grafana/data'; -import { from, merge, Observable, of } from 'rxjs'; +import { AnnotationEvent, dateMath, DateTime, LoadingState, TimeRange, TimeSeries } from '@grafana/data'; +import { from, merge, Observable, of, forkJoin } from 'rxjs'; import { filter, map, tap } from 'rxjs/operators'; import PrometheusMetricFindQuery from './metric_find_query'; @@ -28,6 +28,7 @@ import { safeStringifyValue } from 'app/core/utils/explore'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { ExploreUrlState } from 'app/types'; +import TableModel from 'app/core/table_model'; export interface PromDataQueryResponse { data: { @@ -221,6 +222,18 @@ export class PrometheusDatasource extends DataSourceApi }; }; + calledFromExplore = (options: DataQueryRequest): boolean => { + let exploreTargets = 0; + for (let index = 0; index < options.targets.length; index++) { + const target = options.targets[index]; + if (target.context === PromContext.Explore) { + exploreTargets++; + } + } + + return exploreTargets === options.targets.length; + }; + query(options: DataQueryRequest): Observable { const start = this.getPrometheusTime(options.range.from, false); const end = this.getPrometheusTime(options.range.to, true); @@ -234,6 +247,14 @@ export class PrometheusDatasource extends DataSourceApi }); } + if (this.calledFromExplore(options)) { + return this.exploreQuery(queries, activeTargets, end); + } + + return this.panelsQuery(queries, activeTargets, end, options.requestId); + } + + private exploreQuery(queries: PromQueryRequest[], activeTargets: PromQuery[], end: number) { let runningQueriesCount = queries.length; const subQueries = queries.map((query, index) => { const target = activeTargets[index]; @@ -264,6 +285,40 @@ export class PrometheusDatasource extends DataSourceApi return merge(...subQueries); } + private panelsQuery(queries: PromQueryRequest[], activeTargets: PromQuery[], end: number, requestId: string) { + const observables: Array>> = queries.map((query, index) => { + const target = activeTargets[index]; + let observable: Observable = null; + + if (query.instant) { + observable = from(this.performInstantQuery(query, end)); + } else { + observable = from(this.performTimeSeriesQuery(query, query.start, query.end)); + } + + return observable.pipe( + filter((response: any) => (response.cancelled ? false : true)), + map((response: any) => { + const data = this.processResult(response, query, target, queries.length); + return data; + }) + ); + }); + + return forkJoin(observables).pipe( + map((results: Array>) => { + const data = results.reduce((result, current) => { + return [...result, ...current]; + }, []); + return { + data, + key: requestId, + state: LoadingState.Done, + }; + }) + ); + } + createQuery(target: PromQuery, options: DataQueryRequest, start: number, end: number) { const query: PromQueryRequest = { hinting: target.hinting,