diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx index e236b34121e..bbfb73fea13 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx @@ -1,1063 +1,133 @@ -import { interval, of, throwError } from 'rxjs'; -import { - DataFrame, - DataQueryErrorType, - DataSourceInstanceSettings, - dateMath, - getFrameDisplayName, -} from '@grafana/data'; - -import * as redux from 'app/store/store'; -import { CloudWatchDatasource, MAX_ATTEMPTS } from '../datasource'; +import React from 'react'; +import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { DataSourceInstanceSettings } from '@grafana/data'; import { TemplateSrv } from 'app/features/templating/template_srv'; -import { - CloudWatchJsonData, - CloudWatchLogsQuery, - CloudWatchLogsQueryStatus, - CloudWatchMetricsQuery, - LogAction, -} 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 { convertToStoreState } from '../../../../../test/helpers/convertToStoreState'; -import { getTemplateSrvDependencies } from 'test/helpers/getTemplateSrvDependencies'; -import { CustomVariableModel, initialVariableModelState, VariableHide } from '../../../../features/variables/types'; - -import * as rxjsUtils from '../utils/rxjs/increasingInterval'; -import { createFetchResponse } from 'test/helpers/createFetchResponse'; - -jest.mock('@grafana/runtime', () => ({ - ...((jest.requireActual('@grafana/runtime') as unknown) as object), - getBackendSrv: () => backendSrv, -})); - -type Args = { response?: any; throws?: boolean; templateSrv?: TemplateSrv }; - -function getTestContext({ response = {}, throws = false, templateSrv = new TemplateSrv() }: Args = {}) { - jest.clearAllMocks(); - - const fetchMock = jest.spyOn(backendSrv, 'fetch'); - - throws - ? fetchMock.mockImplementation(() => throwError(response)) - : fetchMock.mockImplementation(() => of(createFetchResponse(response))); +import { MetricsQueryEditor, normalizeQuery, Props } from './MetricsQueryEditor'; +import { CloudWatchDatasource } from '../datasource'; +import { CustomVariableModel, initialVariableModelState } from '../../../../features/variables/types'; +import { CloudWatchJsonData } from '../types'; +const setup = () => { const instanceSettings = { jsonData: { defaultRegion: 'us-east-1' }, - name: 'TestDatasource', } as DataSourceInstanceSettings; - const timeSrv = { - time: { from: '2016-12-31 15:00:00Z', to: '2016-12-31 16:00:00Z' }, - timeRange: () => { - return { - from: dateMath.parse(timeSrv.time.from, false), - to: dateMath.parse(timeSrv.time.to, true), - }; + const templateSrv = new TemplateSrv(); + const variable: CustomVariableModel = { + ...initialVariableModelState, + id: 'var3', + index: 0, + name: 'var3', + options: [ + { selected: true, value: 'var3-foo', text: 'var3-foo' }, + { selected: false, value: 'var3-bar', text: 'var3-bar' }, + { selected: true, value: 'var3-baz', text: 'var3-baz' }, + ], + current: { selected: true, value: ['var3-foo', 'var3-baz'], text: 'var3-foo + var3-baz' }, + multi: true, + includeAll: false, + query: '', + type: 'custom', + }; + templateSrv.init([variable]); + + const datasource = new CloudWatchDatasource(instanceSettings, templateSrv as any, {} as any); + datasource.metricFindQuery = async () => [{ value: 'test', label: 'test', text: 'test' }]; + + const props: Props = { + query: { + queryMode: 'Metrics', + refId: '', + id: '', + region: 'us-east-1', + namespace: 'ec2', + metricName: 'CPUUtilization', + dimensions: { somekey: 'somevalue' }, + statistic: '', + period: '', + expression: '', + alias: '', + matchExact: true, }, - } as TimeSrv; + datasource, + history: [], + onChange: jest.fn(), + onRunQuery: jest.fn(), + }; - const ds = new CloudWatchDatasource(instanceSettings, templateSrv, timeSrv); + return props; +}; - return { ds, fetchMock, instanceSettings }; -} - -describe('CloudWatchDatasource', () => { - const start = 1483196400 * 1000; - const defaultTimeRange = { from: new Date(start), to: new Date(start + 3600 * 1000) }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('When getting log groups', () => { - it('should return log groups as an array of strings', async () => { - const response = { - results: { - A: { - frames: [ - { - schema: { - name: 'logGroups', - refId: 'A', - fields: [{ name: 'logGroupName', type: 'string', typeInfo: { frame: 'string', nullable: true } }], - }, - data: { - values: [ - [ - '/aws/containerinsights/dev303-workshop/application', - '/aws/containerinsights/dev303-workshop/dataplane', - '/aws/containerinsights/dev303-workshop/flowlogs', - '/aws/containerinsights/dev303-workshop/host', - '/aws/containerinsights/dev303-workshop/performance', - '/aws/containerinsights/dev303-workshop/prometheus', - '/aws/containerinsights/ecommerce-sockshop/application', - '/aws/containerinsights/ecommerce-sockshop/dataplane', - '/aws/containerinsights/ecommerce-sockshop/host', - '/aws/containerinsights/ecommerce-sockshop/performance', - '/aws/containerinsights/watchdemo-perf/application', - '/aws/containerinsights/watchdemo-perf/dataplane', - '/aws/containerinsights/watchdemo-perf/host', - '/aws/containerinsights/watchdemo-perf/performance', - '/aws/containerinsights/watchdemo-perf/prometheus', - '/aws/containerinsights/watchdemo-prod-us-east-1/performance', - '/aws/containerinsights/watchdemo-staging/application', - '/aws/containerinsights/watchdemo-staging/dataplane', - '/aws/containerinsights/watchdemo-staging/host', - '/aws/containerinsights/watchdemo-staging/performance', - '/aws/ecs/containerinsights/bugbash-ec2/performance', - '/aws/ecs/containerinsights/ecs-demoworkshop/performance', - '/aws/ecs/containerinsights/ecs-workshop-dev/performance', - '/aws/eks/dev303-workshop/cluster', - '/aws/events/cloudtrail', - '/aws/events/ecs', - '/aws/lambda/cwsyn-mycanary-fac97ded-f134-499a-9d71-4c3be1f63182', - '/aws/lambda/cwsyn-watch-linkchecks-ef7ef273-5da2-4663-af54-d2f52d55b060', - '/ecs/ecs-cwagent-daemon-service', - '/ecs/ecs-demo-limitTask', - 'CloudTrail/DefaultLogGroup', - 'container-insights-prometheus-beta', - 'container-insights-prometheus-demo', - ], - ], - }, - }, - ], - }, - }, - }; - - const { ds } = getTestContext({ response }); - const expectedLogGroups = [ - '/aws/containerinsights/dev303-workshop/application', - '/aws/containerinsights/dev303-workshop/dataplane', - '/aws/containerinsights/dev303-workshop/flowlogs', - '/aws/containerinsights/dev303-workshop/host', - '/aws/containerinsights/dev303-workshop/performance', - '/aws/containerinsights/dev303-workshop/prometheus', - '/aws/containerinsights/ecommerce-sockshop/application', - '/aws/containerinsights/ecommerce-sockshop/dataplane', - '/aws/containerinsights/ecommerce-sockshop/host', - '/aws/containerinsights/ecommerce-sockshop/performance', - '/aws/containerinsights/watchdemo-perf/application', - '/aws/containerinsights/watchdemo-perf/dataplane', - '/aws/containerinsights/watchdemo-perf/host', - '/aws/containerinsights/watchdemo-perf/performance', - '/aws/containerinsights/watchdemo-perf/prometheus', - '/aws/containerinsights/watchdemo-prod-us-east-1/performance', - '/aws/containerinsights/watchdemo-staging/application', - '/aws/containerinsights/watchdemo-staging/dataplane', - '/aws/containerinsights/watchdemo-staging/host', - '/aws/containerinsights/watchdemo-staging/performance', - '/aws/ecs/containerinsights/bugbash-ec2/performance', - '/aws/ecs/containerinsights/ecs-demoworkshop/performance', - '/aws/ecs/containerinsights/ecs-workshop-dev/performance', - '/aws/eks/dev303-workshop/cluster', - '/aws/events/cloudtrail', - '/aws/events/ecs', - '/aws/lambda/cwsyn-mycanary-fac97ded-f134-499a-9d71-4c3be1f63182', - '/aws/lambda/cwsyn-watch-linkchecks-ef7ef273-5da2-4663-af54-d2f52d55b060', - '/ecs/ecs-cwagent-daemon-service', - '/ecs/ecs-demo-limitTask', - 'CloudTrail/DefaultLogGroup', - 'container-insights-prometheus-beta', - 'container-insights-prometheus-demo', - ]; - - const logGroups = await ds.describeLogGroups({}); - - expect(logGroups).toEqual(expectedLogGroups); +describe('QueryEditor', () => { + it('should render component', async () => { + const { act } = renderer; + await act(async () => { + const props = setup(); + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); }); }); - describe('When performing CloudWatch logs query', () => { - beforeEach(() => { - jest.spyOn(rxjsUtils, 'increasingInterval').mockImplementation(() => interval(100)); + it('normalizes query on mount', async () => { + const { act } = renderer; + const props = setup(); + // This does not actually even conform to the prop type but this happens on initialisation somehow + props.query = { + queryMode: 'Metrics', + apiMode: 'Metrics', + refId: '', + expression: '', + matchExact: true, + } as any; + await act(async () => { + renderer.create(); }); - - it('should stop querying when no more data received a number of times in a row', async () => { - const { ds } = getTestContext(); - const fakeFrames = genMockFrames(20); - const initialRecordsMatched = fakeFrames[0].meta!.stats!.find((stat) => stat.displayName === 'Records scanned')! - .value!; - for (let i = 1; i < 4; i++) { - fakeFrames[i].meta!.stats = [ - { - displayName: 'Records scanned', - value: initialRecordsMatched, - }, - ]; - } - - const finalRecordsMatched = fakeFrames[9].meta!.stats!.find((stat) => stat.displayName === 'Records scanned')! - .value!; - for (let i = 10; i < fakeFrames.length; i++) { - fakeFrames[i].meta!.stats = [ - { - displayName: 'Records scanned', - value: finalRecordsMatched, - }, - ]; - } - - let i = 0; - jest.spyOn(ds, 'makeLogActionRequest').mockImplementation((subtype: LogAction) => { - if (subtype === 'GetQueryResults') { - const mockObservable = of([fakeFrames[i]]); - i++; - return mockObservable; - } else { - return of([]); - } - }); - - const myResponse = await ds.logsQuery([{ queryId: 'fake-query-id', region: 'default', refId: 'A' }]).toPromise(); - - const expectedData = [ - { - ...fakeFrames[14], - meta: { - custom: { - Status: 'Cancelled', - }, - stats: fakeFrames[14].meta!.stats, - }, - }, - ]; - - expect(myResponse).toEqual({ - data: expectedData, - key: 'test-key', - state: 'Done', - error: { - type: DataQueryErrorType.Timeout, - message: `error: query timed out after ${MAX_ATTEMPTS} attempts`, - }, - }); - expect(i).toBe(15); - }); - - it('should continue querying as long as new data is being received', async () => { - const { ds } = getTestContext(); - const fakeFrames = genMockFrames(15); - - let i = 0; - jest.spyOn(ds, 'makeLogActionRequest').mockImplementation((subtype: LogAction) => { - if (subtype === 'GetQueryResults') { - const mockObservable = of([fakeFrames[i]]); - i++; - return mockObservable; - } else { - return of([]); - } - }); - - const myResponse = await ds.logsQuery([{ queryId: 'fake-query-id', region: 'default', refId: 'A' }]).toPromise(); - expect(myResponse).toEqual({ - data: [fakeFrames[fakeFrames.length - 1]], - key: 'test-key', - state: 'Done', - }); - expect(i).toBe(15); - }); - - it('should stop querying when results come back with status "Complete"', async () => { - const { ds } = getTestContext(); - const fakeFrames = genMockFrames(3); - let i = 0; - jest.spyOn(ds, 'makeLogActionRequest').mockImplementation((subtype: LogAction) => { - if (subtype === 'GetQueryResults') { - const mockObservable = of([fakeFrames[i]]); - i++; - return mockObservable; - } else { - return of([]); - } - }); - - const myResponse = await ds.logsQuery([{ queryId: 'fake-query-id', region: 'default', refId: 'A' }]).toPromise(); - - expect(myResponse).toEqual({ - data: [fakeFrames[2]], - key: 'test-key', - state: 'Done', - }); - expect(i).toBe(3); - }); - - it('should call the replace method on provided log groups', () => { - const { ds } = getTestContext(); - const replaceSpy = jest.spyOn(ds, 'replace').mockImplementation((target?: string) => target ?? ''); - ds.makeLogActionRequest('StartQuery', [ - { - queryString: 'test query string', - region: 'default', - logGroupNames: ['log-group', '${my_var}Variable', 'Cool${other_var}'], - }, - ]); - - expect(replaceSpy).toBeCalledWith('log-group', undefined, true, 'log groups'); - expect(replaceSpy).toBeCalledWith('${my_var}Variable', undefined, true, 'log groups'); - expect(replaceSpy).toBeCalledWith('Cool${other_var}', undefined, true, 'log groups'); + expect((props.onChange as jest.Mock).mock.calls[0][0]).toEqual({ + namespace: '', + metricName: '', + expression: '', + dimensions: {}, + region: 'default', + id: '', + alias: '', + statistic: 'Average', + period: '', + queryMode: 'Metrics', + apiMode: 'Metrics', + refId: '', + matchExact: true, }); }); - describe('When performing CloudWatch metrics query', () => { - const query: any = { - range: defaultTimeRange, - rangeRaw: { from: 1483228800, to: 1483232400 }, - targets: [ - { - type: 'Metrics', - expression: '', - refId: 'A', - region: 'us-east-1', - namespace: 'AWS/EC2', - metricName: 'CPUUtilization', - dimensions: { - InstanceId: 'i-12345678', - }, - statistic: 'Average', - period: '300', - }, - ], - }; - - const response: any = { - timings: [null], - results: { - A: { - type: 'Metrics', - error: '', - refId: 'A', - meta: {}, - series: [ - { - name: 'CPUUtilization_Average', - points: [ - [1, 1483228800000], - [2, 1483229100000], - [5, 1483229700000], - ], - tags: { - InstanceId: 'i-12345678', - }, - }, - ], - }, - }, - }; - - it('should generate the correct query', async () => { - const { ds, fetchMock } = getTestContext({ response }); - - await expect(ds.query(query)).toEmitValuesWith(() => { - expect(fetchMock.mock.calls[0][0].data.queries).toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - namespace: query.targets[0].namespace, - metricName: query.targets[0].metricName, - dimensions: { InstanceId: ['i-12345678'] }, - statistic: query.targets[0].statistic, - period: query.targets[0].period, - }), - ]) - ); + describe('should use correct default values', () => { + it('when region is null is display default in the label', async () => { + // @ts-ignore strict null error TS2345: Argument of type '() => Promise' is not assignable to parameter of type '() => void | undefined'. + await act(async () => { + const props = setup(); + props.query.region = (null as unknown) as string; + const wrapper = mount(); + expect( + wrapper.find('.gf-form-inline').first().find('Segment').find('InlineLabel').find('label').text() + ).toEqual('default'); }); }); - it('should generate the correct query with interval variable', async () => { - const period: CustomVariableModel = { - ...initialVariableModelState, - id: 'period', - name: 'period', - index: 0, - current: { value: '10m', text: '10m', selected: true }, - options: [{ value: '10m', text: '10m', selected: true }], - multi: false, - includeAll: false, - query: '', - hide: VariableHide.dontHide, - type: 'custom', - }; - const templateSrv = new TemplateSrv(); - templateSrv.init([period]); - - const query: any = { - range: defaultTimeRange, - rangeRaw: { from: 1483228800, to: 1483232400 }, - targets: [ - { - type: 'Metrics', - refId: 'A', - region: 'us-east-1', - namespace: 'AWS/EC2', - metricName: 'CPUUtilization', - dimensions: { - InstanceId: 'i-12345678', - }, - statistic: 'Average', - period: '[[period]]', - }, - ], - }; - - const { ds, fetchMock } = getTestContext({ response, templateSrv }); - - await expect(ds.query(query)).toEmitValuesWith(() => { - expect(fetchMock.mock.calls[0][0].data.queries[0].period).toEqual('600'); - }); - }); - - it('should return series list', async () => { - const { ds } = getTestContext({ response }); - - await expect(ds.query(query)).toEmitValuesWith((received) => { - const result = received[0]; - expect(getFrameDisplayName(result.data[0])).toBe(response.results.A.series[0].name); - expect(result.data[0].fields[1].values.buffer[0]).toBe(response.results.A.series[0].points[0][0]); - }); - }); - - describe('and throttling exception is thrown', () => { - const partialQuery = { - type: 'Metrics', - namespace: 'AWS/EC2', - metricName: 'CPUUtilization', - dimensions: { - InstanceId: 'i-12345678', - }, - statistic: 'Average', - period: '300', + it('should normalize query with default values', () => { + expect(normalizeQuery({ refId: '42' } as any)).toEqual({ + namespace: '', + metricName: '', expression: '', - }; - - const query: any = { - range: defaultTimeRange, - rangeRaw: { from: 1483228800, to: 1483232400 }, - targets: [ - { ...partialQuery, refId: 'A', region: 'us-east-1' }, - { ...partialQuery, refId: 'B', region: 'us-east-2' }, - { ...partialQuery, refId: 'C', region: 'us-east-1' }, - { ...partialQuery, refId: 'D', region: 'us-east-2' }, - { ...partialQuery, refId: 'E', region: 'eu-north-1' }, - ], - }; - - const backendErrorResponse = { - data: { - message: 'Throttling: exception', - results: { - A: { - error: 'Throttling: exception', - refId: 'A', - meta: {}, - }, - B: { - error: 'Throttling: exception', - refId: 'B', - meta: {}, - }, - C: { - error: 'Throttling: exception', - refId: 'C', - meta: {}, - }, - D: { - error: 'Throttling: exception', - refId: 'D', - meta: {}, - }, - E: { - error: 'Throttling: exception', - refId: 'E', - meta: {}, - }, - }, - }, - }; - - beforeEach(() => { - redux.setStore({ - dispatch: jest.fn(), - } as any); - }); - - it('should display one alert error message per region+datasource combination', async () => { - const { ds } = getTestContext({ response: backendErrorResponse, throws: true }); - const memoizedDebounceSpy = jest.spyOn(ds, 'debouncedAlert'); - - await expect(ds.query(query)).toEmitValuesWith((received) => { - expect(memoizedDebounceSpy).toHaveBeenCalledWith('TestDatasource', 'us-east-1'); - expect(memoizedDebounceSpy).toHaveBeenCalledWith('TestDatasource', 'us-east-2'); - expect(memoizedDebounceSpy).toHaveBeenCalledWith('TestDatasource', 'eu-north-1'); - expect(memoizedDebounceSpy).toBeCalledTimes(3); - }); - }); - }); - - describe('when regions query is used', () => { - describe('and region param is left out', () => { - it('should use the default region', async () => { - const { ds, instanceSettings } = getTestContext(); - ds.doMetricQueryRequest = jest.fn().mockResolvedValue([]); - - await ds.metricFindQuery('metrics(testNamespace)'); - - expect(ds.doMetricQueryRequest).toHaveBeenCalledWith('metrics', { - namespace: 'testNamespace', - region: instanceSettings.jsonData.defaultRegion, - }); - }); - }); - - describe('and region param is defined by user', () => { - it('should use the user defined region', async () => { - const { ds } = getTestContext(); - ds.doMetricQueryRequest = jest.fn().mockResolvedValue([]); - - await ds.metricFindQuery('metrics(testNamespace2, custom-region)'); - - expect(ds.doMetricQueryRequest).toHaveBeenCalledWith('metrics', { - namespace: 'testNamespace2', - region: 'custom-region', - }); - }); + dimensions: {}, + region: 'default', + id: '', + alias: '', + statistic: 'Average', + matchExact: true, + period: '', + refId: '42', }); }); }); - - describe('When query region is "default"', () => { - it('should return the datasource region if empty or "default"', () => { - const { ds, instanceSettings } = getTestContext(); - const defaultRegion = instanceSettings.jsonData.defaultRegion; - - expect(ds.getActualRegion()).toBe(defaultRegion); - expect(ds.getActualRegion('')).toBe(defaultRegion); - expect(ds.getActualRegion('default')).toBe(defaultRegion); - }); - - it('should return the specified region if specified', () => { - const { ds } = getTestContext(); - - expect(ds.getActualRegion('some-fake-region-1')).toBe('some-fake-region-1'); - }); - - it('should query for the datasource region if empty or "default"', async () => { - const { ds, instanceSettings } = getTestContext(); - const performTimeSeriesQueryMock = jest.spyOn(ds, 'performTimeSeriesQuery').mockReturnValue(of({})); - - const query: any = { - range: defaultTimeRange, - rangeRaw: { from: 1483228800, to: 1483232400 }, - targets: [ - { - type: 'Metrics', - refId: 'A', - region: 'default', - namespace: 'AWS/EC2', - metricName: 'CPUUtilization', - dimensions: { - InstanceId: 'i-12345678', - }, - statistic: 'Average', - period: '300s', - }, - ], - }; - - await expect(ds.query(query)).toEmitValuesWith(() => { - expect(performTimeSeriesQueryMock.mock.calls[0][0].queries[0].region).toBe( - instanceSettings.jsonData.defaultRegion - ); - }); - }); - }); - - describe('When interpolating variables', () => { - it('should return an empty array if no queries are provided', () => { - const templateSrv: any = { replace: jest.fn() }; - const { ds } = getTestContext({ templateSrv }); - - expect(ds.interpolateVariablesInQueries([], {})).toHaveLength(0); - }); - - it('should replace correct variables in CloudWatchLogsQuery', () => { - const templateSrv: any = { replace: jest.fn() }; - const { ds } = getTestContext({ templateSrv }); - const variableName = 'someVar'; - const logQuery: CloudWatchLogsQuery = { - id: 'someId', - refId: 'someRefId', - queryMode: 'Logs', - expression: `$${variableName}`, - region: `$${variableName}`, - }; - - ds.interpolateVariablesInQueries([logQuery], {}); - - // We interpolate `expression` and `region` in CloudWatchLogsQuery - expect(templateSrv.replace).toHaveBeenCalledWith(`$${variableName}`, {}); - expect(templateSrv.replace).toHaveBeenCalledTimes(2); - }); - - it('should replace correct variables in CloudWatchMetricsQuery', () => { - const templateSrv: any = { replace: jest.fn() }; - const { ds } = getTestContext({ templateSrv }); - const variableName = 'someVar'; - const logQuery: CloudWatchMetricsQuery = { - id: 'someId', - refId: 'someRefId', - queryMode: 'Metrics', - expression: `$${variableName}`, - region: `$${variableName}`, - period: `$${variableName}`, - alias: `$${variableName}`, - metricName: `$${variableName}`, - namespace: `$${variableName}`, - dimensions: { - [`$${variableName}`]: `$${variableName}`, - }, - matchExact: false, - statistic: '', - }; - - ds.interpolateVariablesInQueries([logQuery], {}); - - // We interpolate `expression`, `region`, `period`, `alias`, `metricName`, `nameSpace` and `dimensions` in CloudWatchMetricsQuery - expect(templateSrv.replace).toHaveBeenCalledWith(`$${variableName}`, {}); - expect(templateSrv.replace).toHaveBeenCalledTimes(8); - }); - }); - - describe('When performing CloudWatch query for extended statistic', () => { - const query: any = { - range: defaultTimeRange, - rangeRaw: { from: 1483228800, to: 1483232400 }, - targets: [ - { - type: 'Metrics', - refId: 'A', - region: 'us-east-1', - namespace: 'AWS/ApplicationELB', - metricName: 'TargetResponseTime', - dimensions: { - LoadBalancer: 'lb', - TargetGroup: 'tg', - }, - statistic: 'p90.00', - period: '300s', - }, - ], - }; - - const response: any = { - timings: [null], - results: { - A: { - error: '', - refId: 'A', - meta: {}, - series: [ - { - name: 'TargetResponseTime_p90.00', - points: [ - [1, 1483228800000], - [2, 1483229100000], - [5, 1483229700000], - ], - tags: { - LoadBalancer: 'lb', - TargetGroup: 'tg', - }, - }, - ], - }, - }, - }; - - it('should return series list', async () => { - const { ds } = getTestContext({ response }); - - await expect(ds.query(query)).toEmitValuesWith((received) => { - const result = received[0]; - expect(getFrameDisplayName(result.data[0])).toBe(response.results.A.series[0].name); - expect(result.data[0].fields[1].values.buffer[0]).toBe(response.results.A.series[0].points[0][0]); - }); - }); - }); - - describe('When performing CloudWatch query with template variables', () => { - let templateSrv: TemplateSrv; - beforeEach(() => { - const var1: CustomVariableModel = { - ...initialVariableModelState, - id: 'var1', - name: 'var1', - index: 0, - current: { value: 'var1-foo', text: 'var1-foo', selected: true }, - options: [{ value: 'var1-foo', text: 'var1-foo', selected: true }], - multi: false, - includeAll: false, - query: '', - hide: VariableHide.dontHide, - type: 'custom', - }; - const var2: CustomVariableModel = { - ...initialVariableModelState, - id: 'var2', - name: 'var2', - index: 1, - current: { value: 'var2-foo', text: 'var2-foo', selected: true }, - options: [{ value: 'var2-foo', text: 'var2-foo', selected: true }], - multi: false, - includeAll: false, - query: '', - hide: VariableHide.dontHide, - type: 'custom', - }; - const var3: CustomVariableModel = { - ...initialVariableModelState, - id: 'var3', - name: 'var3', - index: 2, - current: { value: ['var3-foo', 'var3-baz'], text: 'var3-foo + var3-baz', selected: true }, - options: [ - { selected: true, value: 'var3-foo', text: 'var3-foo' }, - { selected: false, value: 'var3-bar', text: 'var3-bar' }, - { selected: true, value: 'var3-baz', text: 'var3-baz' }, - ], - multi: true, - includeAll: false, - query: '', - hide: VariableHide.dontHide, - type: 'custom', - }; - const var4: CustomVariableModel = { - ...initialVariableModelState, - id: 'var4', - name: 'var4', - index: 3, - options: [ - { selected: true, value: 'var4-foo', text: 'var4-foo' }, - { selected: false, value: 'var4-bar', text: 'var4-bar' }, - { selected: true, value: 'var4-baz', text: 'var4-baz' }, - ], - current: { value: ['var4-foo', 'var4-baz'], text: 'var4-foo + var4-baz', selected: true }, - multi: true, - includeAll: false, - query: '', - hide: VariableHide.dontHide, - type: 'custom', - }; - const variables = [var1, var2, var3, var4]; - const state = convertToStoreState(variables); - templateSrv = new TemplateSrv(getTemplateSrvDependencies(state)); - templateSrv.init(variables); - }); - - it('should generate the correct query for single template variable', async () => { - const { ds, fetchMock } = getTestContext({ templateSrv }); - const query: any = { - range: defaultTimeRange, - rangeRaw: { from: 1483228800, to: 1483232400 }, - targets: [ - { - type: 'Metrics', - refId: 'A', - region: 'us-east-1', - namespace: 'TestNamespace', - metricName: 'TestMetricName', - dimensions: { - dim2: '[[var2]]', - }, - statistic: 'Average', - period: '300s', - }, - ], - }; - - await expect(ds.query(query)).toEmitValuesWith(() => { - expect(fetchMock.mock.calls[0][0].data.queries[0].dimensions['dim2']).toStrictEqual(['var2-foo']); - }); - }); - - it('should generate the correct query in the case of one multilple template variables', async () => { - const { ds, fetchMock } = getTestContext({ templateSrv }); - const query: any = { - range: defaultTimeRange, - rangeRaw: { from: 1483228800, to: 1483232400 }, - targets: [ - { - type: 'Metrics', - refId: 'A', - region: 'us-east-1', - namespace: 'TestNamespace', - metricName: 'TestMetricName', - dimensions: { - dim1: '[[var1]]', - dim2: '[[var2]]', - dim3: '[[var3]]', - }, - statistic: 'Average', - period: '300s', - }, - ], - scopedVars: { - var1: { selected: true, value: 'var1-foo' }, - var2: { selected: true, value: 'var2-foo' }, - }, - }; - - await expect(ds.query(query)).toEmitValuesWith(() => { - expect(fetchMock.mock.calls[0][0].data.queries[0].dimensions['dim1']).toStrictEqual(['var1-foo']); - expect(fetchMock.mock.calls[0][0].data.queries[0].dimensions['dim2']).toStrictEqual(['var2-foo']); - expect(fetchMock.mock.calls[0][0].data.queries[0].dimensions['dim3']).toStrictEqual(['var3-foo', 'var3-baz']); - }); - }); - - it('should generate the correct query in the case of multilple multi template variables', async () => { - const { ds, fetchMock } = getTestContext({ templateSrv }); - const query: any = { - range: defaultTimeRange, - rangeRaw: { from: 1483228800, to: 1483232400 }, - targets: [ - { - type: 'Metrics', - refId: 'A', - region: 'us-east-1', - namespace: 'TestNamespace', - metricName: 'TestMetricName', - dimensions: { - dim1: '[[var1]]', - dim3: '[[var3]]', - dim4: '[[var4]]', - }, - statistic: 'Average', - period: '300s', - }, - ], - }; - - await expect(ds.query(query)).toEmitValuesWith(() => { - expect(fetchMock.mock.calls[0][0].data.queries[0].dimensions['dim1']).toStrictEqual(['var1-foo']); - expect(fetchMock.mock.calls[0][0].data.queries[0].dimensions['dim3']).toStrictEqual(['var3-foo', 'var3-baz']); - expect(fetchMock.mock.calls[0][0].data.queries[0].dimensions['dim4']).toStrictEqual(['var4-foo', 'var4-baz']); - }); - }); - - it('should generate the correct query for multilple template variables, lack scopedVars', async () => { - const { ds, fetchMock } = getTestContext({ templateSrv }); - const query: any = { - range: defaultTimeRange, - rangeRaw: { from: 1483228800, to: 1483232400 }, - targets: [ - { - type: 'Metrics', - refId: 'A', - region: 'us-east-1', - namespace: 'TestNamespace', - metricName: 'TestMetricName', - dimensions: { - dim1: '[[var1]]', - dim2: '[[var2]]', - dim3: '[[var3]]', - }, - statistic: 'Average', - period: '300', - }, - ], - scopedVars: { - var1: { selected: true, value: 'var1-foo' }, - }, - }; - - await expect(ds.query(query)).toEmitValuesWith(() => { - expect(fetchMock.mock.calls[0][0].data.queries[0].dimensions['dim1']).toStrictEqual(['var1-foo']); - expect(fetchMock.mock.calls[0][0].data.queries[0].dimensions['dim2']).toStrictEqual(['var2-foo']); - expect(fetchMock.mock.calls[0][0].data.queries[0].dimensions['dim3']).toStrictEqual(['var3-foo', 'var3-baz']); - }); - }); - }); - - function describeMetricFindQuery(query: any, func: any) { - describe('metricFindQuery ' + query, () => { - const scenario: any = {}; - scenario.setup = async (setupCallback: any) => { - beforeEach(async () => { - await setupCallback(); - const { ds } = getTestContext({ response: scenario.requestResponse }); - ds.metricFindQuery(query).then((args: any) => { - scenario.result = args; - }); - }); - }; - - func(scenario); - }); - } - - describeMetricFindQuery('regions()', async (scenario: any) => { - await scenario.setup(() => { - scenario.requestResponse = { - results: { - metricFindQuery: { - tables: [{ rows: [['us-east-1', 'us-east-1']] }], - }, - }, - }; - }); - - it('should call __GetRegions and return result', () => { - expect(scenario.result[0].text).toContain('us-east-1'); - expect(scenario.request.queries[0].type).toBe('metricFindQuery'); - expect(scenario.request.queries[0].subtype).toBe('regions'); - }); - }); - - describeMetricFindQuery('namespaces()', async (scenario: any) => { - await scenario.setup(() => { - scenario.requestResponse = { - results: { - metricFindQuery: { - tables: [{ rows: [['AWS/EC2', 'AWS/EC2']] }], - }, - }, - }; - }); - - it('should call __GetNamespaces and return result', () => { - expect(scenario.result[0].text).toContain('AWS/EC2'); - expect(scenario.request.queries[0].type).toBe('metricFindQuery'); - expect(scenario.request.queries[0].subtype).toBe('namespaces'); - }); - }); - - describeMetricFindQuery('metrics(AWS/EC2, us-east-2)', async (scenario: any) => { - await scenario.setup(() => { - scenario.requestResponse = { - results: { - metricFindQuery: { - tables: [{ rows: [['CPUUtilization', 'CPUUtilization']] }], - }, - }, - }; - }); - - it('should call __GetMetrics and return result', () => { - expect(scenario.result[0].text).toBe('CPUUtilization'); - expect(scenario.request.queries[0].type).toBe('metricFindQuery'); - expect(scenario.request.queries[0].subtype).toBe('metrics'); - }); - }); - - describeMetricFindQuery('dimension_keys(AWS/EC2)', async (scenario: any) => { - await scenario.setup(() => { - scenario.requestResponse = { - results: { - metricFindQuery: { - tables: [{ rows: [['InstanceId', 'InstanceId']] }], - }, - }, - }; - }); - - it('should call __GetDimensions and return result', () => { - expect(scenario.result[0].text).toBe('InstanceId'); - expect(scenario.request.queries[0].type).toBe('metricFindQuery'); - expect(scenario.request.queries[0].subtype).toBe('dimension_keys'); - }); - }); - - describeMetricFindQuery('dimension_values(us-east-1,AWS/EC2,CPUUtilization,InstanceId)', async (scenario: any) => { - await scenario.setup(() => { - scenario.requestResponse = { - results: { - metricFindQuery: { - tables: [{ rows: [['i-12345678', 'i-12345678']] }], - }, - }, - }; - }); - - it('should call __ListMetrics and return result', () => { - expect(scenario.result[0].text).toContain('i-12345678'); - expect(scenario.request.queries[0].type).toBe('metricFindQuery'); - expect(scenario.request.queries[0].subtype).toBe('dimension_values'); - }); - }); - - describeMetricFindQuery('dimension_values(default,AWS/EC2,CPUUtilization,InstanceId)', async (scenario: any) => { - await scenario.setup(() => { - scenario.requestResponse = { - results: { - metricFindQuery: { - tables: [{ rows: [['i-12345678', 'i-12345678']] }], - }, - }, - }; - }); - - it('should call __ListMetrics and return result', () => { - expect(scenario.result[0].text).toContain('i-12345678'); - expect(scenario.request.queries[0].type).toBe('metricFindQuery'); - expect(scenario.request.queries[0].subtype).toBe('dimension_values'); - }); - }); - - describeMetricFindQuery( - 'resource_arns(default,ec2:instance,{"environment":["production"]})', - async (scenario: any) => { - await scenario.setup(() => { - scenario.requestResponse = { - results: { - metricFindQuery: { - tables: [ - { - rows: [ - [ - 'arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567', - 'arn:aws:ec2:us-east-1:123456789012:instance/i-76543210987654321', - ], - ], - }, - ], - }, - }, - }; - }); - - it('should call __ListMetrics and return result', () => { - expect(scenario.result[0].text).toContain('arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567'); - expect(scenario.request.queries[0].type).toBe('metricFindQuery'); - expect(scenario.request.queries[0].subtype).toBe('resource_arns'); - }); - } - ); }); - -function genMockFrames(numResponses: number): DataFrame[] { - const recordIncrement = 50; - const mockFrames: DataFrame[] = []; - - for (let i = 0; i < numResponses; i++) { - mockFrames.push({ - fields: [], - meta: { - custom: { - Status: i === numResponses - 1 ? CloudWatchLogsQueryStatus.Complete : CloudWatchLogsQueryStatus.Running, - }, - stats: [ - { - displayName: 'Records scanned', - value: (i + 1) * recordIncrement, - }, - ], - }, - refId: 'A', - length: 0, - }); - } - - return mockFrames; -} diff --git a/public/app/plugins/datasource/cloudwatch/components/__snapshots__/MetricsQueryEditor.test.tsx.snap b/public/app/plugins/datasource/cloudwatch/components/__snapshots__/MetricsQueryEditor.test.tsx.snap new file mode 100644 index 00000000000..d977a61df6d --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/components/__snapshots__/MetricsQueryEditor.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`QueryEditor should render component 1`] = `null`;