diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 6215c1610ac..62648e24c7b 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -12,6 +12,7 @@ import { MetricFindValue, AnnotationQueryRequest, AnnotationEvent, + DataQueryError, } from '@grafana/data'; import { v4 as uuidv4 } from 'uuid'; import InfluxSeries from './influx_series'; @@ -21,8 +22,9 @@ import { InfluxQueryBuilder } from './query_builder'; import { InfluxQuery, InfluxOptions, InfluxVersion } from './types'; import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; import { getBackendSrv, DataSourceWithBackend, frameToMetricFindValue } from '@grafana/runtime'; -import { Observable, from } from 'rxjs'; +import { Observable, throwError, of } from 'rxjs'; import { FluxQueryEditor } from './components/FluxQueryEditor'; +import { catchError, map } from 'rxjs/operators'; export default class InfluxDatasource extends DataSourceWithBackend { type: string; @@ -75,7 +77,7 @@ export default class InfluxDatasource extends DataSourceWithBackend { + classicQuery(options: any): Observable { let timeFilter = this.getTimeFilter(options); const scopedVars = options.scopedVars; const targets = _.cloneDeep(options.targets); @@ -135,7 +137,7 @@ export default class InfluxDatasource extends DataSourceWithBackend { - if (!data || !data.results) { - return []; - } - - const seriesList = []; - for (i = 0; i < data.results.length; i++) { - const result = data.results[i]; - if (!result || !result.series) { - continue; + return this._seriesQuery(allQueries, options).pipe( + map((data: any) => { + if (!data || !data.results) { + return { data: [] }; } - const target = queryTargets[i]; - let alias = target.alias; - if (alias) { - alias = this.templateSrv.replace(target.alias, options.scopedVars); - } - - const meta: QueryResultMeta = { - executedQueryString: data.executedQueryString, - }; - - const influxSeries = new InfluxSeries({ - refId: target.refId, - series: data.results[i].series, - alias: alias, - meta, - }); - - switch (target.resultFormat) { - case 'logs': - meta.preferredVisualisationType = 'logs'; - case 'table': { - seriesList.push(influxSeries.getTable()); - break; + const seriesList = []; + for (i = 0; i < data.results.length; i++) { + const result = data.results[i]; + if (!result || !result.series) { + continue; } - default: { - const timeSeries = influxSeries.getTimeSeries(); - for (y = 0; y < timeSeries.length; y++) { - seriesList.push(timeSeries[y]); + + const target = queryTargets[i]; + let alias = target.alias; + if (alias) { + alias = this.templateSrv.replace(target.alias, options.scopedVars); + } + + const meta: QueryResultMeta = { + executedQueryString: data.executedQueryString, + }; + + const influxSeries = new InfluxSeries({ + refId: target.refId, + series: data.results[i].series, + alias: alias, + meta, + }); + + switch (target.resultFormat) { + case 'logs': + meta.preferredVisualisationType = 'logs'; + case 'table': { + seriesList.push(influxSeries.getTable()); + break; + } + default: { + const timeSeries = influxSeries.getTimeSeries(); + for (y = 0; y < timeSeries.length; y++) { + seriesList.push(timeSeries[y]); + } + break; } - break; } } - } - return { data: seriesList }; - }); + return { data: seriesList }; + }) + ); } async annotationQuery(options: AnnotationQueryRequest): Promise { @@ -219,15 +223,17 @@ export default class InfluxDatasource extends DataSourceWithBackend { - if (!data || !data.results || !data.results[0]) { - throw { message: 'No results in response from InfluxDB' }; - } - return new InfluxSeries({ - series: data.results[0].series, - annotation: options.annotation, - }).getAnnotations(); - }); + return this._seriesQuery(query, options) + .toPromise() + .then((data: any) => { + if (!data || !data.results || !data.results[0]) { + throw { message: 'No results in response from InfluxDB' }; + } + return new InfluxSeries({ + series: data.results[0].series, + annotation: options.annotation, + }).getAnnotations(); + }); } targetContainsTemplate(target: any) { @@ -268,14 +274,12 @@ export default class InfluxDatasource extends DataSourceWithBackend { - const expandedTag = { + expandedQuery.tags = query.tags.map(tag => { + return { ...tag, value: this.templateSrv.replace(tag.value, undefined, 'regex'), }; - return expandedTag; }); - expandedQuery.tags = expandedTags; } return expandedQuery; }); @@ -305,9 +309,11 @@ export default class InfluxDatasource extends DataSourceWithBackend { - return this.responseParser.parse(query, resp); - }); + return this._seriesQuery(interpolated, options) + .toPromise() + .then(resp => { + return this.responseParser.parse(query, resp); + }); } getTagKeys(options: any = {}) { @@ -324,7 +330,7 @@ export default class InfluxDatasource extends DataSourceWithBackend { const error = _.get(res, 'results[0].error'); if (error) { @@ -459,14 +466,15 @@ export default class InfluxDatasource extends DataSourceWithBackend { + .fetch(req) + .pipe( + map((result: any) => { const { data } = result; if (data) { data.executedQueryString = q; if (data.results) { const errors = result.data.results.filter((elem: any) => elem.error); + if (errors.length > 0) { throw { message: 'InfluxDB Error: ' + errors[0].error, @@ -476,29 +484,42 @@ export default class InfluxDatasource extends DataSourceWithBackend { - if ((Number.isInteger(err.status) && err.status !== 0) || err.status >= 300) { - if (err.data && err.data.error) { - throw { - message: 'InfluxDB Error: ' + err.data.error, - data: err.data, - config: err.config, - }; - } else { - throw { - message: 'Network Error: ' + err.statusText + '(' + err.status + ')', - data: err.data, - config: err.config, - }; - } - } else { - throw err; + }), + catchError(err => { + if (err.cancelled) { + return of(err); } - } + + return throwError(this.handleErrors(err)); + }) ); } + handleErrors(err: any) { + const error: DataQueryError = { + message: + (err && err.status) || + (err && err.message) || + 'Unknown error during query transaction. Please check JS console logs.', + }; + + if ((Number.isInteger(err.status) && err.status !== 0) || err.status >= 300) { + if (err.data && err.data.error) { + error.message = 'InfluxDB Error: ' + err.data.error; + error.data = err.data; + // @ts-ignore + error.config = err.config; + } else { + error.message = 'Network Error: ' + err.statusText + '(' + err.status + ')'; + error.data = err.data; + // @ts-ignore + error.config = err.config; + } + } + + return error; + } + getTimeFilter(options: any) { const from = this.getInfluxTime(options.rangeRaw.from, false, options.timezone); const until = this.getInfluxTime(options.rangeRaw.to, true, options.timezone); diff --git a/public/app/plugins/datasource/influxdb/specs/datasource.test.ts b/public/app/plugins/datasource/influxdb/specs/datasource.test.ts index 2ae22a67eeb..d42c54b0421 100644 --- a/public/app/plugins/datasource/influxdb/specs/datasource.test.ts +++ b/public/app/plugins/datasource/influxdb/specs/datasource.test.ts @@ -2,6 +2,8 @@ import InfluxDatasource from '../datasource'; import { TemplateSrvStub } from 'test/specs/helpers'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ +import { of } from 'rxjs'; +import { FetchResponse } from '@grafana/runtime'; //@ts-ignore const templateSrv = new TemplateSrvStub(); @@ -16,7 +18,7 @@ describe('InfluxDataSource', () => { instanceSettings: { url: 'url', name: 'influxDb', jsonData: { httpMode: 'GET' } }, }; - const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest'); + const fetchMock = jest.spyOn(backendSrv, 'fetch'); beforeEach(() => { jest.clearAllMocks(); @@ -35,12 +37,13 @@ describe('InfluxDataSource', () => { let requestQuery: any, requestMethod: any, requestData: any, response: any; beforeEach(async () => { - datasourceRequestMock.mockImplementation((req: any) => { + fetchMock.mockImplementation((req: any) => { requestMethod = req.method; requestQuery = req.params.q; requestData = req.data; - return Promise.resolve({ + return of({ data: { + status: 'success', results: [ { series: [ @@ -53,7 +56,7 @@ describe('InfluxDataSource', () => { }, ], }, - }); + } as FetchResponse); }); response = await ctx.ds.metricFindQuery(query, queryOptions); @@ -96,8 +99,8 @@ describe('InfluxDataSource', () => { }; it('throws an error', async () => { - datasourceRequestMock.mockImplementation((req: any) => { - return Promise.resolve({ + fetchMock.mockImplementation((req: any) => { + return of({ data: { results: [ { @@ -105,7 +108,7 @@ describe('InfluxDataSource', () => { }, ], }, - }); + } as FetchResponse); }); try { @@ -132,23 +135,25 @@ describe('InfluxDataSource', () => { let requestMethod: any, requestQueryParameter: any, queryEncoded: any, requestQuery: any; beforeEach(async () => { - datasourceRequestMock.mockImplementation((req: any) => { + fetchMock.mockImplementation((req: any) => { requestMethod = req.method; requestQueryParameter = req.params; requestQuery = req.data; - return Promise.resolve({ - results: [ - { - series: [ - { - name: 'measurement', - columns: ['max'], - values: [[1]], - }, - ], - }, - ], - }); + return of({ + data: { + results: [ + { + series: [ + { + name: 'measurement', + columns: ['max'], + values: [[1]], + }, + ], + }, + ], + }, + } as FetchResponse); }); queryEncoded = await ctx.ds.serializeParams({ q: query });