diff --git a/public/app/plugins/datasource/cloud-monitoring/api.test.ts b/public/app/plugins/datasource/cloud-monitoring/api.test.ts index 3e1372592bd..6b9dfe18546 100644 --- a/public/app/plugins/datasource/cloud-monitoring/api.test.ts +++ b/public/app/plugins/datasource/cloud-monitoring/api.test.ts @@ -1,6 +1,8 @@ +import { of } from 'rxjs'; + import Api from './api'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ -import { SelectableValue } from '@grafana/data'; +import { createFetchResponse } from 'test/helpers/createFetchResponse'; jest.mock('@grafana/runtime', () => ({ ...((jest.requireActual('@grafana/runtime') as unknown) as object), @@ -12,58 +14,60 @@ const response = [ { label: 'test2', value: 'test2' }, ]; -describe('api', () => { - const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest'); - beforeEach(() => { - datasourceRequestMock.mockImplementation((options: any) => { - const data = { [options.url.match(/([^\/]*)\/*$/)[1]]: response }; - return Promise.resolve({ data, status: 200 }); - }); +type Args = { path?: string; options?: any; response?: any; cache?: any }; + +async function getTestContext({ path = 'some-resource', options = {}, response = {}, cache }: Args = {}) { + jest.clearAllMocks(); + + const fetchMock = jest.spyOn(backendSrv, 'fetch'); + + fetchMock.mockImplementation((options: any) => { + const data = { [options.url.match(/([^\/]*)\/*$/)[1]]: response }; + return of(createFetchResponse(data)); }); - describe('when resource was cached', () => { - let api: Api; - let res: Array>; - beforeEach(async () => { - api = new Api('/cloudmonitoring/'); - api.cache['some-resource'] = response; - res = await api.get('some-resource'); - }); + const api = new Api('/cloudmonitoring/'); + + if (cache) { + api.cache[path] = cache; + } + + const res = await api.get(path, options); + + return { res, api, fetchMock }; +} + +describe('api', () => { + describe('when resource was cached', () => { + it('should return cached value and not load from source', async () => { + const path = 'some-resource'; + const { res, api, fetchMock } = await getTestContext({ path, cache: response }); - it('should return cached value and not load from source', () => { expect(res).toEqual(response); - expect(api.cache['some-resource']).toEqual(response); - expect(datasourceRequestMock).not.toHaveBeenCalled(); + expect(api.cache[path]).toEqual(response); + expect(fetchMock).not.toHaveBeenCalled(); }); }); describe('when resource was not cached', () => { - let api: Api; - let res: Array>; - beforeEach(async () => { - api = new Api('/cloudmonitoring/'); - res = await api.get('some-resource'); - }); + it('should return from source and not from cache', async () => { + const path = 'some-resource'; + const { res, api, fetchMock } = await getTestContext({ path, response }); - it('should return cached value and not load from source', () => { expect(res).toEqual(response); - expect(api.cache['some-resource']).toEqual(response); - expect(datasourceRequestMock).toHaveBeenCalled(); + expect(api.cache[path]).toEqual(response); + expect(fetchMock).toHaveBeenCalled(); }); }); describe('when cache should be bypassed', () => { - let api: Api; - let res: Array>; - beforeEach(async () => { - api = new Api('/cloudmonitoring/'); - api.cache['some-resource'] = response; - res = await api.get('some-resource', { useCache: false }); - }); + it('should return from source and not from cache', async () => { + const options = { useCache: false }; + const path = 'some-resource'; + const { res, fetchMock } = await getTestContext({ path, response, cache: response, options }); - it('should return cached value and not load from source', () => { expect(res).toEqual(response); - expect(datasourceRequestMock).toHaveBeenCalled(); + expect(fetchMock).toHaveBeenCalled(); }); }); }); diff --git a/public/app/plugins/datasource/cloud-monitoring/api.ts b/public/app/plugins/datasource/cloud-monitoring/api.ts index d9dece79b4f..1be022054c6 100644 --- a/public/app/plugins/datasource/cloud-monitoring/api.ts +++ b/public/app/plugins/datasource/cloud-monitoring/api.ts @@ -1,11 +1,17 @@ +import { Observable, of } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; +import { SelectableValue } from '@grafana/data'; +import { FetchResponse, getBackendSrv } from '@grafana/runtime'; + import appEvents from 'app/core/app_events'; import { CoreEvents } from 'app/types'; -import { SelectableValue } from '@grafana/data'; -import { getBackendSrv } from '@grafana/runtime'; - import { formatCloudMonitoringError } from './functions'; import { MetricDescriptor } from './types'; +export interface PostResponse { + results: Record; +} + interface Options { responseMap?: (res: any) => SelectableValue | MetricDescriptor; baseUrl?: string; @@ -25,48 +31,56 @@ export default class Api { }; } - async get(path: string, options?: Options): Promise> | MetricDescriptor[]> { - try { - const { useCache, responseMap, baseUrl } = { ...this.defaultOptions, ...options }; + get(path: string, options?: Options): Promise> | MetricDescriptor[]> { + const { useCache, responseMap, baseUrl } = { ...this.defaultOptions, ...options }; - if (useCache && this.cache[path]) { - return this.cache[path]; - } + if (useCache && this.cache[path]) { + return Promise.resolve(this.cache[path]); + } - const response = await getBackendSrv().datasourceRequest({ + return getBackendSrv() + .fetch>({ url: baseUrl + path, method: 'GET', - }); + }) + .pipe( + map(response => { + const responsePropName = path.match(/([^\/]*)\/*$/)![1]; + let res = []; + if (response && response.data && response.data[responsePropName]) { + res = response.data[responsePropName].map(responseMap); + } - const responsePropName = path.match(/([^\/]*)\/*$/)![1]; - let res = []; - if (response && response.data && response.data[responsePropName]) { - res = response.data[responsePropName].map(responseMap); - } + if (useCache) { + this.cache[path] = res; + } - if (useCache) { - this.cache[path] = res; - } - - return res; - } catch (error) { - appEvents.emit(CoreEvents.dsRequestError, { error: { data: { error: formatCloudMonitoringError(error) } } }); - return []; - } + return res; + }), + catchError(error => { + appEvents.emit(CoreEvents.dsRequestError, { + error: { data: { error: formatCloudMonitoringError(error) } }, + }); + return of([]); + }) + ) + .toPromise(); } - async post(data: { [key: string]: any }) { - return getBackendSrv().datasourceRequest({ + post(data: Record): Observable> { + return getBackendSrv().fetch({ url: '/api/tsdb/query', method: 'POST', data, }); } - async test(projectName: string) { - return getBackendSrv().datasourceRequest({ - url: `${this.baseUrl}${projectName}/metricDescriptors`, - method: 'GET', - }); + test(projectName: string) { + return getBackendSrv() + .fetch({ + url: `${this.baseUrl}${projectName}/metricDescriptors`, + method: 'GET', + }) + .toPromise(); } } diff --git a/public/app/plugins/datasource/cloud-monitoring/datasource.ts b/public/app/plugins/datasource/cloud-monitoring/datasource.ts index b955c51473b..aae7107f21b 100644 --- a/public/app/plugins/datasource/cloud-monitoring/datasource.ts +++ b/public/app/plugins/datasource/cloud-monitoring/datasource.ts @@ -14,8 +14,10 @@ import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { CloudMonitoringOptions, CloudMonitoringQuery, Filter, MetricDescriptor, QueryType } from './types'; import { cloudMonitoringUnitMappings } from './constants'; -import API from './api'; +import API, { PostResponse } from './api'; import { CloudMonitoringVariableSupport } from './variables'; +import { catchError, map, mergeMap } from 'rxjs/operators'; +import { from, Observable, of, throwError } from 'rxjs'; export default class CloudMonitoringDatasource extends DataSourceApi { api: API; @@ -37,45 +39,52 @@ export default class CloudMonitoringDatasource extends DataSourceApi `$${v.name}`); } - async query(options: DataQueryRequest): Promise { - const result: DataQueryResponseData[] = []; - const data = await this.getTimeSeries(options); - if (data.results) { - Object.values(data.results).forEach((queryRes: any) => { - if (!queryRes.series) { - return; + query(options: DataQueryRequest): Observable { + return this.getTimeSeries(options).pipe( + map(data => { + if (!data.results) { + return { data: [] }; } - const unit = this.resolvePanelUnitFromTargets(options.targets); - queryRes.series.forEach((series: any) => { - let timeSerie: any = { - target: series.name, - datapoints: series.points, - refId: queryRes.refId, - meta: queryRes.meta, - }; - if (unit) { - timeSerie = { ...timeSerie, unit }; - } - const df = toDataFrame(timeSerie); - for (const field of df.fields) { - if (queryRes.meta?.deepLink && queryRes.meta?.deepLink.length > 0) { - field.config.links = [ - { - url: queryRes.meta?.deepLink, - title: 'View in Metrics Explorer', - targetBlank: true, - }, - ]; - } + const result: DataQueryResponseData[] = []; + const values = Object.values(data.results); + for (const queryRes of values) { + if (!queryRes.series) { + continue; } - result.push(df); - }); - }); - return { data: result }; - } else { - return { data: [] }; - } + + const unit = this.resolvePanelUnitFromTargets(options.targets); + + for (const series of queryRes.series) { + let timeSerie: any = { + target: series.name, + datapoints: series.points, + refId: queryRes.refId, + meta: queryRes.meta, + }; + if (unit) { + timeSerie = { ...timeSerie, unit }; + } + const df = toDataFrame(timeSerie); + + for (const field of df.fields) { + if (queryRes.meta?.deepLink && queryRes.meta?.deepLink.length > 0) { + field.config.links = [ + { + url: queryRes.meta?.deepLink, + title: 'View in Metrics Explorer', + targetBlank: true, + }, + ]; + } + } + result.push(df); + } + } + + return { data: result }; + }) + ); } async annotationQuery(options: any) { @@ -101,47 +110,57 @@ export default class CloudMonitoringDatasource extends DataSourceApi { + const results = data.results['annotationQuery'].tables[0].rows.map((v: any) => { + return { + annotation: annotation, + time: Date.parse(v[0]), + title: v[1], + tags: [], + text: v[3], + } as any; + }); - const results = data.results['annotationQuery'].tables[0].rows.map((v: any) => { - return { - annotation: annotation, - time: Date.parse(v[0]), - title: v[1], - tags: [], - text: v[3], - } as any; - }); - - return results; + return results; + }) + ) + .toPromise(); } - async getTimeSeries(options: DataQueryRequest) { - await this.ensureGCEDefaultProject(); + getTimeSeries(options: DataQueryRequest): Observable { const queries = options.targets .map(this.migrateQuery) .filter(this.shouldRunQuery) .map(q => this.prepareTimeSeriesQuery(q, options.scopedVars)) .map(q => ({ ...q, intervalMs: options.intervalMs, type: 'timeSeriesQuery' })); - if (queries.length > 0) { - const { data } = await this.api.post({ - from: options.range.from.valueOf().toString(), - to: options.range.to.valueOf().toString(), - queries, - }); - return data; - } else { - return { results: [] }; + if (!queries.length) { + return of({ results: [] }); } + + return from(this.ensureGCEDefaultProject()).pipe( + mergeMap(() => { + return this.api.post({ + from: options.range.from.valueOf().toString(), + to: options.range.to.valueOf().toString(), + queries, + }); + }), + map(({ data }) => { + return data; + }) + ); } async getLabels(metricType: string, refId: string, projectName: string, groupBys?: string[]) { - const response = await this.getTimeSeries({ + return this.getTimeSeries({ targets: [ { refId, @@ -157,9 +176,14 @@ export default class CloudMonitoringDatasource extends DataSourceApi); - const result = response.results[refId]; - return result && result.meta ? result.meta.labels : {}; + } as DataQueryRequest) + .pipe( + map(response => { + const result = response.results[refId]; + return result && result.meta ? result.meta.labels : {}; + }) + ) + .toPromise(); } async testDatasource() { @@ -205,14 +229,17 @@ export default class CloudMonitoringDatasource extends DataSourceApi { - return data && data.results && data.results.getGCEDefaultProject && data.results.getGCEDefaultProject.meta - ? data.results.getGCEDefaultProject.meta.defaultProject - : ''; - }) - .catch(err => { - throw err.data.error; - }); + .pipe( + map(({ data }) => { + return data && data.results && data.results.getGCEDefaultProject && data.results.getGCEDefaultProject.meta + ? data.results.getGCEDefaultProject.meta.defaultProject + : ''; + }), + catchError(err => { + return throwError(err.data.error); + }) + ) + .toPromise(); } getDefaultProject(): string { @@ -272,7 +299,7 @@ export default class CloudMonitoringDatasource extends DataSourceApi ({ value: projectId, diff --git a/public/app/plugins/datasource/cloud-monitoring/specs/datasource.test.ts b/public/app/plugins/datasource/cloud-monitoring/specs/datasource.test.ts index b8a02a0fa81..46e13f05bde 100644 --- a/public/app/plugins/datasource/cloud-monitoring/specs/datasource.test.ts +++ b/public/app/plugins/datasource/cloud-monitoring/specs/datasource.test.ts @@ -1,86 +1,79 @@ +import { of, throwError } from 'rxjs'; +import { DataSourceInstanceSettings, observableTester, toUtc } from '@grafana/data'; + import CloudMonitoringDataSource from '../datasource'; import { metricDescriptors } from './testData'; import { TemplateSrv } from 'app/features/templating/template_srv'; -import { DataSourceInstanceSettings, toUtc } from '@grafana/data'; import { CloudMonitoringOptions } from '../types'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { CustomVariableModel } from '../../../../features/variables/types'; import { initialCustomVariableModelState } from '../../../../features/variables/custom/reducer'; +import { createFetchResponse } from 'test/helpers/createFetchResponse'; jest.mock('@grafana/runtime', () => ({ ...((jest.requireActual('@grafana/runtime') as unknown) as object), getBackendSrv: () => backendSrv, })); -interface Result { - status: any; - message?: any; -} +type Args = { response?: any; throws?: boolean; templateSrv?: TemplateSrv }; + +function getTestcontext({ response = {}, throws = false, templateSrv = new TemplateSrv() }: Args = {}) { + jest.clearAllMocks(); -describe('CloudMonitoringDataSource', () => { const instanceSettings = ({ jsonData: { defaultProject: 'testproject', }, } as unknown) as DataSourceInstanceSettings; - const templateSrv = new TemplateSrv(); + const timeSrv = {} as TimeSrv; - const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest'); - beforeEach(() => { - jest.clearAllMocks(); - datasourceRequestMock.mockImplementation(jest.fn()); - }); + const fetchMock = jest.spyOn(backendSrv, 'fetch'); + throws + ? fetchMock.mockImplementation(() => throwError(response)) + : fetchMock.mockImplementation(() => of(createFetchResponse(response))); + + const ds = new CloudMonitoringDataSource(instanceSettings, templateSrv, timeSrv); + + return { ds }; +} + +describe('CloudMonitoringDataSource', () => { describe('when performing testDataSource', () => { describe('and call to cloud monitoring api succeeds', () => { - let ds; - let result: Result; - beforeEach(async () => { - datasourceRequestMock.mockImplementation(() => Promise.resolve({ status: 200 })); - ds = new CloudMonitoringDataSource(instanceSettings, templateSrv, timeSrv); - result = await ds.testDatasource(); - }); + it('should return successfully', async () => { + const { ds } = getTestcontext(); + + const result = await ds.testDatasource(); - it('should return successfully', () => { expect(result.status).toBe('success'); }); }); describe('and a list of metricDescriptors are returned', () => { - let ds; - let result: Result; - beforeEach(async () => { - datasourceRequestMock.mockImplementation(() => Promise.resolve({ status: 200, data: metricDescriptors })); + it('should return status success', async () => { + const { ds } = getTestcontext({ response: metricDescriptors }); - ds = new CloudMonitoringDataSource(instanceSettings, templateSrv, timeSrv); - result = await ds.testDatasource(); - }); + const result = await ds.testDatasource(); - it('should return status success', () => { expect(result.status).toBe('success'); }); }); describe('and call to cloud monitoring api fails with 400 error', () => { - let ds; - let result: Result; - beforeEach(async () => { - datasourceRequestMock.mockImplementation(() => - Promise.reject({ - statusText: 'Bad Request', - data: { - error: { code: 400, message: 'Field interval.endTime had an invalid value' }, - }, - }) - ); + it('should return error status and a detailed error message', async () => { + const response = { + statusText: 'Bad Request', + data: { + error: { code: 400, message: 'Field interval.endTime had an invalid value' }, + }, + }; + const { ds } = getTestcontext({ response, throws: true }); - ds = new CloudMonitoringDataSource(instanceSettings, templateSrv, timeSrv); - result = await ds.testDatasource(); - }); + const result = await ds.testDatasource(); - it('should return error status and a detailed error message', () => { expect(result.status).toEqual('error'); expect(result.message).toBe( 'Google Cloud Monitoring: Bad Request: 400. Field interval.endTime had an invalid value' @@ -90,154 +83,133 @@ describe('CloudMonitoringDataSource', () => { }); describe('When performing query', () => { - const options = { - range: { - from: toUtc('2017-08-22T20:00:00Z'), - to: toUtc('2017-08-22T23:59:00Z'), - }, - rangeRaw: { - from: 'now-4h', - to: 'now', - }, - targets: [ - { - refId: 'A', - }, - ], - }; - describe('and no time series data is returned', () => { - let ds: CloudMonitoringDataSource; - const response: any = { - results: { - A: { - refId: 'A', - meta: { - rawQuery: 'arawquerystring', - }, - series: null, - tables: null, + it('should return a list of datapoints', done => { + const options = { + range: { + from: toUtc('2017-08-22T20:00:00Z'), + to: toUtc('2017-08-22T23:59:00Z'), }, - }, - }; + rangeRaw: { + from: 'now-4h', + to: 'now', + }, + targets: [ + { + refId: 'A', + }, + ], + }; - beforeEach(() => { - datasourceRequestMock.mockImplementation(() => Promise.resolve({ status: 200, data: response })); - ds = new CloudMonitoringDataSource(instanceSettings, templateSrv, timeSrv); - }); + const response: any = { + results: { + A: { + refId: 'A', + meta: { + rawQuery: 'arawquerystring', + }, + series: null, + tables: null, + }, + }, + }; - it('should return a list of datapoints', () => { - return ds.query(options as any).then(results => { - expect(results.data.length).toBe(0); + const { ds } = getTestcontext({ response }); + + observableTester().subscribeAndExpectOnNext({ + expect: results => { + expect(results.data.length).toBe(0); + }, + observable: ds.query(options as any), + done, }); }); }); }); describe('when performing getMetricTypes', () => { - describe('and call to cloud monitoring api succeeds', () => {}); - let ds; - let result: any; - beforeEach(async () => { - datasourceRequestMock.mockImplementation(() => - Promise.resolve({ - data: { - metricDescriptors: [ - { - displayName: 'test metric name 1', - type: 'compute.googleapis.com/instance/cpu/test-metric-type-1', - description: 'A description', - }, - { - type: 'logging.googleapis.com/user/logbased-metric-with-no-display-name', - }, - ], - }, - }) - ); + describe('and call to cloud monitoring api succeeds', () => { + it('should return successfully', async () => { + const response = { + metricDescriptors: [ + { + displayName: 'test metric name 1', + type: 'compute.googleapis.com/instance/cpu/test-metric-type-1', + description: 'A description', + }, + { + type: 'logging.googleapis.com/user/logbased-metric-with-no-display-name', + }, + ], + }; + const { ds } = getTestcontext({ response }); - ds = new CloudMonitoringDataSource(instanceSettings, templateSrv, timeSrv); - // @ts-ignore - result = await ds.getMetricTypes('proj'); - }); + const result = await ds.getMetricTypes('proj'); - it('should return successfully', () => { - expect(result.length).toBe(2); - expect(result[0].service).toBe('compute.googleapis.com'); - expect(result[0].serviceShortName).toBe('compute'); - expect(result[0].type).toBe('compute.googleapis.com/instance/cpu/test-metric-type-1'); - expect(result[0].displayName).toBe('test metric name 1'); - expect(result[0].description).toBe('A description'); - expect(result[1].type).toBe('logging.googleapis.com/user/logbased-metric-with-no-display-name'); - expect(result[1].displayName).toBe('logging.googleapis.com/user/logbased-metric-with-no-display-name'); + expect(result.length).toBe(2); + expect(result[0].service).toBe('compute.googleapis.com'); + expect(result[0].serviceShortName).toBe('compute'); + expect(result[0].type).toBe('compute.googleapis.com/instance/cpu/test-metric-type-1'); + expect(result[0].displayName).toBe('test metric name 1'); + expect(result[0].description).toBe('A description'); + expect(result[1].type).toBe('logging.googleapis.com/user/logbased-metric-with-no-display-name'); + expect(result[1].displayName).toBe('logging.googleapis.com/user/logbased-metric-with-no-display-name'); + }); }); }); describe('when interpolating a template variable for the filter', () => { - let interpolated: any[]; describe('and is single value variable', () => { - beforeEach(() => { - const filterTemplateSrv = initTemplateSrv('filtervalue1'); - const ds = new CloudMonitoringDataSource(instanceSettings, filterTemplateSrv, timeSrv); - interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '${test}'], {}); - }); - it('should replace the variable with the value', () => { + const templateSrv = initTemplateSrv('filtervalue1'); + const { ds } = getTestcontext({ templateSrv }); + const interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '${test}'], {}); + expect(interpolated.length).toBe(3); expect(interpolated[2]).toBe('filtervalue1'); }); }); describe('and is single value variable for the label part', () => { - beforeEach(() => { - const filterTemplateSrv = initTemplateSrv('resource.label.zone'); - const ds = new CloudMonitoringDataSource(instanceSettings, filterTemplateSrv, timeSrv); - interpolated = ds.interpolateFilters(['${test}', '=~', 'europe-north-1a'], {}); - }); - it('should replace the variable with the value and not with regex formatting', () => { + const templateSrv = initTemplateSrv('resource.label.zone'); + const { ds } = getTestcontext({ templateSrv }); + const interpolated = ds.interpolateFilters(['${test}', '=~', 'europe-north-1a'], {}); + expect(interpolated.length).toBe(3); expect(interpolated[0]).toBe('resource.label.zone'); }); }); describe('and is multi value variable', () => { - beforeEach(() => { - const filterTemplateSrv = initTemplateSrv(['filtervalue1', 'filtervalue2'], true); - const ds = new CloudMonitoringDataSource(instanceSettings, filterTemplateSrv, timeSrv); - interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '[[test]]'], {}); - }); - it('should replace the variable with a regex expression', () => { + const templateSrv = initTemplateSrv(['filtervalue1', 'filtervalue2'], true); + const { ds } = getTestcontext({ templateSrv }); + const interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '[[test]]'], {}); + expect(interpolated[2]).toBe('(filtervalue1|filtervalue2)'); }); }); }); describe('when interpolating a template variable for group bys', () => { - let interpolated: any[]; - describe('and is single value variable', () => { - beforeEach(() => { - const groupByTemplateSrv = initTemplateSrv('groupby1'); - const ds = new CloudMonitoringDataSource(instanceSettings, groupByTemplateSrv, timeSrv); - interpolated = ds.interpolateGroupBys(['[[test]]'], {}); - }); - it('should replace the variable with the value', () => { + const templateSrv = initTemplateSrv('groupby1'); + const { ds } = getTestcontext({ templateSrv }); + const interpolated = ds.interpolateGroupBys(['[[test]]'], {}); + expect(interpolated.length).toBe(1); expect(interpolated[0]).toBe('groupby1'); }); }); describe('and is multi value variable', () => { - beforeEach(() => { - const groupByTemplateSrv = initTemplateSrv(['groupby1', 'groupby2'], true); - const ds = new CloudMonitoringDataSource(instanceSettings, groupByTemplateSrv, timeSrv); - interpolated = ds.interpolateGroupBys(['[[test]]'], {}); - }); - it('should replace the variable with an array of group bys', () => { + const templateSrv = initTemplateSrv(['groupby1', 'groupby2'], true); + const { ds } = getTestcontext({ templateSrv }); + const interpolated = ds.interpolateGroupBys(['[[test]]'], {}); + expect(interpolated.length).toBe(2); expect(interpolated[0]).toBe('groupby1'); expect(interpolated[1]).toBe('groupby2'); @@ -246,24 +218,20 @@ describe('CloudMonitoringDataSource', () => { }); describe('unit parsing', () => { - let ds: CloudMonitoringDataSource, res: any; - beforeEach(() => { - ds = new CloudMonitoringDataSource(instanceSettings, templateSrv, timeSrv); - }); + const { ds } = getTestcontext(); + describe('when theres only one target', () => { describe('and the cloud monitoring unit does nott have a corresponding grafana unit', () => { - beforeEach(() => { - res = ds.resolvePanelUnitFromTargets([{ unit: 'megaseconds' }]); - }); it('should return undefined', () => { + const res = ds.resolvePanelUnitFromTargets([{ unit: 'megaseconds' }]); + expect(res).toBeUndefined(); }); }); describe('and the cloud monitoring unit has a corresponding grafana unit', () => { - beforeEach(() => { - res = ds.resolvePanelUnitFromTargets([{ unit: 'bit' }]); - }); it('should return bits', () => { + const res = ds.resolvePanelUnitFromTargets([{ unit: 'bit' }]); + expect(res).toEqual('bits'); }); }); @@ -271,32 +239,30 @@ describe('CloudMonitoringDataSource', () => { describe('when theres more than one target', () => { describe('and all target units are the same', () => { - beforeEach(() => { - res = ds.resolvePanelUnitFromTargets([{ unit: 'bit' }, { unit: 'bit' }]); - }); it('should return bits', () => { + const res = ds.resolvePanelUnitFromTargets([{ unit: 'bit' }, { unit: 'bit' }]); + expect(res).toEqual('bits'); }); }); describe('and all target units are the same but does not have grafana mappings', () => { - beforeEach(() => { - res = ds.resolvePanelUnitFromTargets([{ unit: 'megaseconds' }, { unit: 'megaseconds' }]); - }); it('should return the default value of undefined', () => { + const res = ds.resolvePanelUnitFromTargets([{ unit: 'megaseconds' }, { unit: 'megaseconds' }]); + expect(res).toBeUndefined(); }); }); describe('and all target units are not the same', () => { - beforeEach(() => { - res = ds.resolvePanelUnitFromTargets([{ unit: 'bit' }, { unit: 'min' }]); - }); it('should return the default value of undefined', () => { + const res = ds.resolvePanelUnitFromTargets([{ unit: 'bit' }, { unit: 'min' }]); + expect(res).toBeUndefined(); }); }); }); }); }); + function initTemplateSrv(values: any, multi = false) { const templateSrv = new TemplateSrv(); const test: CustomVariableModel = { diff --git a/public/test/helpers/createFetchResponse.ts b/public/test/helpers/createFetchResponse.ts new file mode 100644 index 00000000000..0838236e8d3 --- /dev/null +++ b/public/test/helpers/createFetchResponse.ts @@ -0,0 +1,15 @@ +import { FetchResponse } from '@grafana/runtime'; + +export function createFetchResponse(data: T): FetchResponse { + return { + data, + status: 200, + url: 'http://localhost:3000/api/query', + config: { url: 'http://localhost:3000/api/query' }, + type: 'basic', + statusText: 'Ok', + redirected: false, + headers: ({} as unknown) as Headers, + ok: true, + }; +}