diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts index f846d4e254c..38587ea8cf6 100644 --- a/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts @@ -9,7 +9,11 @@ import { CustomVariableModel } from 'app/features/variables/types'; import { TemplateSrvMock } from '../../../../features/templating/template_srv.mock'; import { CloudWatchDatasource } from '../datasource'; -export function setupMockedDataSource({ data = [], variables }: { data?: any; variables?: any } = {}) { +export function setupMockedDataSource({ + data = [], + variables, + mockGetVariableName = true, +}: { data?: any; variables?: any; mockGetVariableName?: boolean } = {}) { let templateService = new TemplateSrvMock({ region: 'templatedRegion', fields: 'templatedField', @@ -19,7 +23,9 @@ export function setupMockedDataSource({ data = [], variables }: { data?: any; va templateService = new TemplateSrv(); templateService.init(variables); templateService.getVariables = jest.fn().mockReturnValue(variables); - templateService.getVariableName = (name: string) => name; + if (mockGetVariableName) { + templateService.getVariableName = (name: string) => name; + } } const datasource = new CloudWatchDatasource( @@ -125,3 +131,19 @@ export const aggregationvariable: CustomVariableModel = { ], multi: false, }; + +export const dimensionVariable: CustomVariableModel = { + ...initialCustomVariableModelState, + id: 'dimension', + name: 'dimension', + current: { + value: 'env', + text: 'env', + selected: true, + }, + options: [ + { value: 'env', text: 'env', selected: false }, + { value: 'tag', text: 'tag', selected: false }, + ], + multi: false, +}; diff --git a/public/app/plugins/datasource/cloudwatch/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/datasource.test.ts index c4baf75a911..e8f8619fd95 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.test.ts @@ -5,6 +5,7 @@ import { ArrayVector, DataFrame, dataFrameToJSON, dateTime, Field, MutableDataFr import { setDataSourceSrv } from '@grafana/runtime'; import { + dimensionVariable, labelsVariable, limitVariable, metricVariable, @@ -398,6 +399,19 @@ describe('datasource', () => { }); }); + describe('convertMultiFiltersFormat', () => { + const ds = setupMockedDataSource({ variables: [labelsVariable, dimensionVariable], mockGetVariableName: false }); + it('converts keys and values correctly', () => { + // the json in this line doesn't matter, but it makes sure that old queries will be parsed + const filters = { $dimension: ['b'], a: ['${labels:json}', 'bar'] }; + const result = ds.datasource.convertMultiFilterFormat(filters); + expect(result).toStrictEqual({ + env: ['b'], + a: ['InstanceId', 'InstanceType', 'bar'], + }); + }); + }); + describe('getLogGroupFields', () => { it('passes region correctly', async () => { const { datasource, fetchMock } = setupMockedDataSource(); diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index 6d7cdebe903..133d5b6ce50 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -54,6 +54,7 @@ import { MetricQuery, MetricQueryType, MetricRequest, + MultiFilters, StartQueryRequest, TSDBResponse, } from './types'; @@ -720,7 +721,7 @@ export class CloudWatchDatasource return this.doMetricResourceRequest('ec2-instance-attribute', { region: this.templateSrv.replace(this.getActualRegion(region)), attributeName: this.templateSrv.replace(attributeName), - filters: JSON.stringify(filters), + filters: JSON.stringify(this.convertMultiFilterFormat(filters, 'filter key')), }); } @@ -728,7 +729,7 @@ export class CloudWatchDatasource return this.doMetricResourceRequest('resource-arns', { region: this.templateSrv.replace(this.getActualRegion(region)), resourceType: this.templateSrv.replace(resourceType), - tags: JSON.stringify(tags), + tags: JSON.stringify(this.convertMultiFilterFormat(tags, 'tag name')), }); } @@ -836,18 +837,40 @@ export class CloudWatchDatasource return { ...result, [key]: null }; } - const valueVar = this.templateSrv - .getVariables() - .find(({ name }) => name === this.templateSrv.getVariableName(value)); - if (valueVar) { - if ((valueVar as unknown as VariableWithMultiSupport).multi) { - const values = this.templateSrv.replace(value, scopedVars, 'pipe').split('|'); - return { ...result, [key]: values }; - } - return { ...result, [key]: [this.templateSrv.replace(value, scopedVars)] }; - } + const newValues = this.getVariableValue(value, scopedVars); + return { ...result, [key]: newValues }; + }, {}); + } - return { ...result, [key]: [value] }; + // get the value for a given template variable + getVariableValue(value: string, scopedVars: ScopedVars): string[] { + const variableName = this.templateSrv.getVariableName(value); + const valueVar = this.templateSrv.getVariables().find(({ name }) => { + return name === variableName; + }); + if (variableName && valueVar) { + if ((valueVar as unknown as VariableWithMultiSupport).multi) { + // rebuild the variable name to handle old migrated queries + const values = this.templateSrv.replace('$' + variableName, scopedVars, 'pipe').split('|'); + return values; + } + return [this.templateSrv.replace(value, scopedVars)]; + } + return [value]; + } + + convertMultiFilterFormat(multiFilters: MultiFilters, fieldName?: string) { + return Object.entries(multiFilters).reduce((result, [key, values]) => { + key = this.replace(key, {}, true, fieldName); + if (!values) { + return { ...result, [key]: null }; + } + const initialVal: string[] = []; + const newValues = values.reduce((result, value) => { + const vals = this.getVariableValue(value, {}); + return [...result, ...vals]; + }, initialVal); + return { ...result, [key]: newValues }; }, {}); }