diff --git a/public/app/plugins/datasource/graphite/specs/datasource.test.ts b/public/app/plugins/datasource/graphite/datasource.test.ts similarity index 89% rename from public/app/plugins/datasource/graphite/specs/datasource.test.ts rename to public/app/plugins/datasource/graphite/datasource.test.ts index 5652aeb2b29..d3d98337e7b 100644 --- a/public/app/plugins/datasource/graphite/specs/datasource.test.ts +++ b/public/app/plugins/datasource/graphite/datasource.test.ts @@ -1,10 +1,12 @@ -import { GraphiteDatasource } from '../datasource'; +import { GraphiteDatasource } from './datasource'; import _ from 'lodash'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { dateTime, getFrameDisplayName } from '@grafana/data'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ -import { DEFAULT_GRAPHITE_VERSION } from '../versions'; +import { of } from 'rxjs'; +import { createFetchResponse } from 'test/helpers/createFetchResponse'; +import { DEFAULT_GRAPHITE_VERSION } from './versions'; jest.mock('@grafana/runtime', () => ({ ...((jest.requireActual('@grafana/runtime') as unknown) as object), @@ -17,7 +19,7 @@ interface Context { } describe('graphiteDatasource', () => { - const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest'); + const fetchMock = jest.spyOn(backendSrv, 'fetch'); let ctx = {} as Context; @@ -114,14 +116,14 @@ describe('graphiteDatasource', () => { maxDataPoints: 500, }; - let results: any; + let response: any; let requestOptions: any; - beforeEach(async () => { - datasourceRequestMock.mockImplementation((options: any) => { + beforeEach(() => { + fetchMock.mockImplementation((options: any) => { requestOptions = options; - return Promise.resolve({ - data: [ + return of( + createFetchResponse([ { target: 'prod1.count', datapoints: [ @@ -129,13 +131,11 @@ describe('graphiteDatasource', () => { [12, 1], ], }, - ], - }); + ]) + ); }); - await ctx.ds.query(query as any).then((data: any) => { - results = data; - }); + response = ctx.ds.query(query as any); }); it('X-Dashboard and X-Panel headers to be set!', () => { @@ -164,13 +164,19 @@ describe('graphiteDatasource', () => { expect(params).not.toContain('cacheTimeout=undefined'); }); - it('should return series list', () => { - expect(results.data.length).toBe(1); - expect(results.data[0].name).toBe('prod1.count'); + it('should return series list', async () => { + await expect(response).toEmitValuesWith((values: any) => { + const results = values[0]; + expect(results.data.length).toBe(1); + expect(results.data[0].name).toBe('prod1.count'); + }); }); - it('should convert to millisecond resolution', () => { - expect(results.data[0].fields[1].values.get(0)).toBe(10); + it('should convert to millisecond resolution', async () => { + await expect(response).toEmitValuesWith((values: any) => { + const results = values[0]; + expect(results.data[0].fields[1].values.get(0)).toBe(10); + }); }); }); @@ -189,21 +195,19 @@ describe('graphiteDatasource', () => { }; describe('and tags are returned as string', () => { - const response = { - data: [ - { - when: 1507222850, - tags: 'tag1 tag2', - data: 'some text', - id: 2, - what: 'Event - deploy', - }, - ], - }; + const response = [ + { + when: 1507222850, + tags: 'tag1 tag2', + data: 'some text', + id: 2, + what: 'Event - deploy', + }, + ]; beforeEach(async () => { - datasourceRequestMock.mockImplementation((options: any) => { - return Promise.resolve(response); + fetchMock.mockImplementation((options: any) => { + return of(createFetchResponse(response)); }); await ctx.ds.annotationQuery(options).then((data: any) => { results = data; @@ -219,20 +223,19 @@ describe('graphiteDatasource', () => { }); describe('and tags are returned as an array', () => { - const response = { - data: [ - { - when: 1507222850, - tags: ['tag1', 'tag2'], - data: 'some text', - id: 2, - what: 'Event - deploy', - }, - ], - }; + const response = [ + { + when: 1507222850, + tags: ['tag1', 'tag2'], + data: 'some text', + id: 2, + what: 'Event - deploy', + }, + ]; + beforeEach(() => { - datasourceRequestMock.mockImplementation((options: any) => { - return Promise.resolve(response); + fetchMock.mockImplementation((options: any) => { + return of(createFetchResponse(response)); }); ctx.ds.annotationQuery(options).then((data: any) => { @@ -350,11 +353,9 @@ describe('graphiteDatasource', () => { let requestOptions: any; beforeEach(() => { - datasourceRequestMock.mockImplementation((options: any) => { + fetchMock.mockImplementation((options: any) => { requestOptions = options; - return Promise.resolve({ - data: ['backend_01', 'backend_02'], - }); + return of(createFetchResponse(['backend_01', 'backend_02'])); }); }); diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index 87d53afea42..8153fdd7280 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -9,6 +9,7 @@ import { ScopedVars, toDataFrame, TimeRange, + MetricFindValue, } from '@grafana/data'; import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version'; import gfunc from './gfunc'; @@ -18,6 +19,8 @@ import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_sr import { GraphiteOptions, GraphiteQuery, GraphiteType, MetricTankRequestMeta } from './types'; import { getRollupNotice, getRuntimeConsolidationNotice } from 'app/plugins/datasource/graphite/meta'; import { getSearchFilterScopedVar } from '../../../features/variables/utils'; +import { Observable, of, OperatorFunction, pipe } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; import { DEFAULT_GRAPHITE_VERSION } from './versions'; export class GraphiteDatasource extends DataSourceApi { @@ -65,7 +68,7 @@ export class GraphiteDatasource extends DataSourceApi): Promise { + query(options: DataQueryRequest): Observable { const graphOptions = { from: this.translateTime(options.range.raw.from, false, options.timezone), until: this.translateTime(options.range.raw.to, true, options.timezone), @@ -77,7 +80,7 @@ export class GraphiteDatasource extends DataSourceApi; - return this.query(graphiteQuery).then((result) => { - const list = []; + return this.query(graphiteQuery) + .pipe( + map((result: any) => { + const list = []; - for (let i = 0; i < result.data.length; i++) { - const target = result.data[i]; + for (let i = 0; i < result.data.length; i++) { + const target = result.data[i]; - for (let y = 0; y < target.length; y++) { - const time = target.fields[0].values.get(y); - const value = target.fields[1].values.get(y); + for (let y = 0; y < target.length; y++) { + const time = target.fields[0].values.get(y); + const value = target.fields[1].values.get(y); - if (!value) { - continue; + if (!value) { + continue; + } + + list.push({ + annotation: options.annotation, + time, + title: target.name, + }); + } } - list.push({ - annotation: options.annotation, - time, - title: target.name, - }); - } - } - - return list; - }); + return list; + }) + ) + .toPromise(); } else { // Graphite event as annotation const tags = this.templateSrv.replace(options.annotation.tags); @@ -290,7 +297,7 @@ export class GraphiteDatasource extends DataSourceApi { - return ''; - }); + .pipe( + map((results: any) => { + if (results.data) { + const semver = new SemVersion(results.data); + return semver.isValid() ? results.data : ''; + } + return ''; + }), + catchError(() => { + return of(''); + }) + ) + .toPromise(); } createFuncInstance(funcDef: any, options?: any) { @@ -573,22 +577,23 @@ export class GraphiteDatasource extends DataSourceApi { - if (results.status !== 200 || typeof results.data !== 'object') { + return this.doGraphiteRequest(httpOptions) + .pipe( + map((results: any) => { + if (results.status !== 200 || typeof results.data !== 'object') { + this.funcDefs = gfunc.getFuncDefs(this.graphiteVersion); + } else { + this.funcDefs = gfunc.parseFuncDefs(results.data); + } + return this.funcDefs; + }), + catchError((error: any) => { + console.error('Fetching graphite functions error', error); this.funcDefs = gfunc.getFuncDefs(this.graphiteVersion); - } else { - this.funcDefs = gfunc.parseFuncDefs(results.data); - } - return this.funcDefs; - }) - .catch((err: any) => { - console.error('Fetching graphite functions error', err); - this.funcDefs = gfunc.getFuncDefs(this.graphiteVersion); - return this.funcDefs; - }); - - return this.funcDefsPromise; + return of(this.funcDefs); + }) + ) + .toPromise(); } testDatasource() { @@ -601,9 +606,10 @@ export class GraphiteDatasource extends DataSourceApi; - return this.query(query).then(() => { - return { status: 'success', message: 'Data source is working' }; - }); + + return this.query(query) + .toPromise() + .then(() => ({ status: 'success', message: 'Data source is working' })); } doGraphiteRequest(options: { @@ -625,7 +631,7 @@ export class GraphiteDatasource extends DataSourceApi> { + return pipe( + map((results: any) => { + if (results.data) { + return _.map(results.data, (value) => { + return { text: value }; + }); + } else { + return []; + } + }) + ); +}