diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index 20a4b7265aa..57279169b29 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -40,17 +40,17 @@ import kbn from 'app/core/utils/kbn'; import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; import { isConstant, isMulti } from 'app/features/variables/guard'; import { alignCurrentWithMulti } from 'app/features/variables/shared/multiOptions'; -import { - migrateCloudWatchQuery, - migrateMultipleStatsAnnotationQuery, - migrateMultipleStatsMetricsQuery, -} from 'app/plugins/datasource/cloudwatch/migrations'; import { CloudWatchMetricsQuery, LegacyAnnotationQuery } from 'app/plugins/datasource/cloudwatch/types'; import { plugin as gaugePanelPlugin } from 'app/plugins/panel/gauge/module'; import { plugin as statPanelPlugin } from 'app/plugins/panel/stat/module'; import { labelsToFieldsTransformer } from '../../../../../packages/grafana-data/src/transformations/transformers/labelsToFields'; import { mergeTransformer } from '../../../../../packages/grafana-data/src/transformations/transformers/merge'; +import { + migrateCloudWatchQuery, + migrateMultipleStatsAnnotationQuery, + migrateMultipleStatsMetricsQuery, +} from '../../../plugins/datasource/cloudwatch/migrations/dashboardMigrations'; import { VariableHide } from '../../variables/types'; import { DashboardModel } from './DashboardModel'; diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx index 0ccc64b7781..292ad4dd4f7 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx @@ -6,7 +6,7 @@ import { InlineField } from '@grafana/ui'; import { Dimensions } from '..'; import { CloudWatchDatasource } from '../../datasource'; import { useDimensionKeys, useMetrics, useNamespaces, useRegions } from '../../hooks'; -import { migrateVariableQuery } from '../../migrations'; +import { migrateVariableQuery } from '../../migrations/variableQueryMigrations'; import { CloudWatchJsonData, CloudWatchQuery, VariableQuery, VariableQueryType } from '../../types'; import { MultiFilter } from './MultiFilter'; diff --git a/public/app/plugins/datasource/cloudwatch/components/usePreparedMetricsQuery.ts b/public/app/plugins/datasource/cloudwatch/components/usePreparedMetricsQuery.ts index a52d5e277e9..15375c58664 100644 --- a/public/app/plugins/datasource/cloudwatch/components/usePreparedMetricsQuery.ts +++ b/public/app/plugins/datasource/cloudwatch/components/usePreparedMetricsQuery.ts @@ -1,6 +1,7 @@ import deepEqual from 'fast-deep-equal'; import { useEffect, useMemo } from 'react'; +import { migrateMetricQuery } from '../migrations/metricQueryMigrations'; import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../types'; export const DEFAULT_QUERY: Omit = { @@ -21,10 +22,11 @@ export const DEFAULT_QUERY: Omit = { const prepareQuery = (query: CloudWatchMetricsQuery) => { const withDefaults = { ...DEFAULT_QUERY, ...query }; + const migratedQuery = migrateMetricQuery(withDefaults); // If we didn't make any changes to the object, then return the original object to keep the // identity the same, and not trigger any other useEffects or anything. - return deepEqual(withDefaults, query) ? query : withDefaults; + return deepEqual(migratedQuery, query) ? query : migratedQuery; }; /** diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index 38eb2226efc..8b87e9a95a1 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -36,6 +36,7 @@ import { isCloudWatchAnnotationQuery, isCloudWatchLogsQuery, isCloudWatchMetrics import { CloudWatchLanguageProvider } from './language_provider'; import memoizedDebounce from './memoizedDebounce'; import { MetricMathCompletionItemProvider } from './metric-math/completion/CompletionItemProvider'; +import { migrateMetricQuery } from './migrations/metricQueryMigrations'; import { CloudWatchAnnotationQuery, CloudWatchJsonData, @@ -266,31 +267,39 @@ export class CloudWatchDatasource throw new Error('invalid metric editor mode'); } + replaceMetricQueryVars( + query: CloudWatchMetricsQuery, + options: DataQueryRequest + ): CloudWatchMetricsQuery { + query.region = this.templateSrv.replace(this.getActualRegion(query.region), options.scopedVars); + query.namespace = this.replace(query.namespace, options.scopedVars, true, 'namespace'); + query.metricName = this.replace(query.metricName, options.scopedVars, true, 'metric name'); + query.dimensions = this.convertDimensionFormat(query.dimensions ?? {}, options.scopedVars); + query.statistic = this.templateSrv.replace(query.statistic, options.scopedVars); + query.period = String(this.getPeriod(query, options)); // use string format for period in graph query, and alerting + query.id = this.templateSrv.replace(query.id, options.scopedVars); + query.expression = this.templateSrv.replace(query.expression, options.scopedVars); + query.sqlExpression = this.templateSrv.replace(query.sqlExpression, options.scopedVars, 'raw'); + + return query; + } + handleMetricQueries = ( metricQueries: CloudWatchMetricsQuery[], options: DataQueryRequest ): Observable => { - const validMetricsQueries = metricQueries - .filter(this.filterQuery) - .map((item: CloudWatchMetricsQuery): MetricQuery => { - item.region = this.templateSrv.replace(this.getActualRegion(item.region), options.scopedVars); - item.namespace = this.replace(item.namespace, options.scopedVars, true, 'namespace'); - item.metricName = this.replace(item.metricName, options.scopedVars, true, 'metric name'); - item.dimensions = this.convertDimensionFormat(item.dimensions ?? {}, options.scopedVars); - item.statistic = this.templateSrv.replace(item.statistic, options.scopedVars); - item.period = String(this.getPeriod(item, options)); // use string format for period in graph query, and alerting - item.id = this.templateSrv.replace(item.id, options.scopedVars); - item.expression = this.templateSrv.replace(item.expression, options.scopedVars); - item.sqlExpression = this.templateSrv.replace(item.sqlExpression, options.scopedVars, 'raw'); + const validMetricsQueries = metricQueries.filter(this.filterQuery).map((q: CloudWatchMetricsQuery): MetricQuery => { + const migratedQuery = migrateMetricQuery(q); + const migratedAndIterpolatedQuery = this.replaceMetricQueryVars(migratedQuery, options); - return { - intervalMs: options.intervalMs, - maxDataPoints: options.maxDataPoints, - ...item, - type: 'timeSeriesQuery', - datasource: this.getRef(), - }; - }); + return { + intervalMs: options.intervalMs, + maxDataPoints: options.maxDataPoints, + ...migratedAndIterpolatedQuery, + type: 'timeSeriesQuery', + datasource: this.getRef(), + }; + }); // No valid targets, return the empty result to save a round trip. if (isEmpty(validMetricsQueries)) { diff --git a/public/app/plugins/datasource/cloudwatch/migration.test.ts b/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.test.ts similarity index 55% rename from public/app/plugins/datasource/cloudwatch/migration.test.ts rename to public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.test.ts index 271fc54ca90..848dda3d857 100644 --- a/public/app/plugins/datasource/cloudwatch/migration.test.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.test.ts @@ -1,21 +1,14 @@ import { AnnotationQuery, DataQuery } from '@grafana/data'; +import { CloudWatchMetricsQuery, LegacyAnnotationQuery, MetricEditorMode, MetricQueryType } from '../types'; + import { migrateCloudWatchQuery, migrateMultipleStatsAnnotationQuery, migrateMultipleStatsMetricsQuery, - migrateVariableQuery, -} from './migrations'; -import { - CloudWatchMetricsQuery, - LegacyAnnotationQuery, - MetricEditorMode, - MetricQueryType, - VariableQueryType, - OldVariableQuery, -} from './types'; +} from './dashboardMigrations'; -describe('migration', () => { +describe('dashboardMigrations', () => { describe('migrateMultipleStatsMetricsQuery', () => { const queryToMigrate = { statistics: ['Average', 'Sum', 'Maximum'], @@ -183,94 +176,4 @@ describe('migration', () => { }); }); }); - describe('migrateVariableQuery', () => { - describe('when metrics query is used', () => { - describe('and region param is left out', () => { - it('should leave an empty region', () => { - const query = migrateVariableQuery('metrics(testNamespace)'); - expect(query.queryType).toBe(VariableQueryType.Metrics); - expect(query.namespace).toBe('testNamespace'); - expect(query.region).toBe(''); - }); - }); - - describe('and region param is defined by user', () => { - it('should use the user defined region', () => { - const query = migrateVariableQuery('metrics(testNamespace2, custom-region)'); - expect(query.queryType).toBe(VariableQueryType.Metrics); - expect(query.namespace).toBe('testNamespace2'); - expect(query.region).toBe('custom-region'); - }); - }); - }); - describe('when dimension_values query is used', () => { - describe('and filter param is left out', () => { - it('should leave an empty filter', () => { - const query = migrateVariableQuery('dimension_values(us-east-1,AWS/RDS,CPUUtilization,DBInstanceIdentifier)'); - expect(query.queryType).toBe(VariableQueryType.DimensionValues); - expect(query.region).toBe('us-east-1'); - expect(query.namespace).toBe('AWS/RDS'); - expect(query.metricName).toBe('CPUUtilization'); - expect(query.dimensionKey).toBe('DBInstanceIdentifier'); - expect(query.dimensionFilters).toStrictEqual({}); - }); - }); - describe('and filter param is defined by user', () => { - it('should use the user defined filter', () => { - const query = migrateVariableQuery( - 'dimension_values(us-east-1,AWS/RDS,CPUUtilization,DBInstanceIdentifier,{"InstanceId":"$instance_id"})' - ); - expect(query.queryType).toBe(VariableQueryType.DimensionValues); - expect(query.region).toBe('us-east-1'); - expect(query.namespace).toBe('AWS/RDS'); - expect(query.metricName).toBe('CPUUtilization'); - expect(query.dimensionKey).toBe('DBInstanceIdentifier'); - expect(query.dimensionFilters).toStrictEqual({ InstanceId: '$instance_id' }); - }); - }); - }); - }); - describe('when resource_arns query is used', () => { - it('should parse the query', () => { - const query = migrateVariableQuery( - 'resource_arns(eu-west-1,elasticloadbalancing:loadbalancer,{"elasticbeanstalk:environment-name":["myApp-dev","myApp-prod"]})' - ); - expect(query.queryType).toBe(VariableQueryType.ResourceArns); - expect(query.region).toBe('eu-west-1'); - expect(query.resourceType).toBe('elasticloadbalancing:loadbalancer'); - expect(query.tags).toStrictEqual({ 'elasticbeanstalk:environment-name': ['myApp-dev', 'myApp-prod'] }); - }); - }); - describe('when ec2_instance_attribute query is used', () => { - it('should parse the query', () => { - const query = migrateVariableQuery('ec2_instance_attribute(us-east-1,rds:db,{"environment":["$environment"]})'); - expect(query.queryType).toBe(VariableQueryType.EC2InstanceAttributes); - expect(query.region).toBe('us-east-1'); - expect(query.attributeName).toBe('rds:db'); - expect(query.ec2Filters).toStrictEqual({ environment: ['$environment'] }); - }); - }); - describe('when OldVariableQuery is used', () => { - it('should parse the query', () => { - const oldQuery: OldVariableQuery = { - queryType: VariableQueryType.EC2InstanceAttributes, - namespace: '', - region: 'us-east-1', - metricName: '', - dimensionKey: '', - ec2Filters: '{"environment":["$environment"]}', - instanceID: '', - attributeName: 'rds:db', - resourceType: 'elasticloadbalancing:loadbalancer', - tags: '{"elasticbeanstalk:environment-name":["myApp-dev","myApp-prod"]}', - refId: '', - }; - const query = migrateVariableQuery(oldQuery); - expect(query.region).toBe('us-east-1'); - expect(query.attributeName).toBe('rds:db'); - expect(query.ec2Filters).toStrictEqual({ environment: ['$environment'] }); - expect(query.resourceType).toBe('elasticloadbalancing:loadbalancer'); - expect(query.tags).toStrictEqual({ 'elasticbeanstalk:environment-name': ['myApp-dev', 'myApp-prod'] }); - }); - }); }); diff --git a/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.ts b/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.ts new file mode 100644 index 00000000000..01c532ab939 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.ts @@ -0,0 +1,67 @@ +// Functions in this file are called from the app/features/dashboard/state/DashboardMigrator. +// Migrations applied by the DashboardMigrator are performed before the plugin is loaded. +// DashboardMigrator migrations are tied to a certain minimum version of a dashboard which means they will only be ran once. + +import { DataQuery, AnnotationQuery } from '@grafana/data'; +import { getNextRefIdChar } from 'app/core/utils/query'; + +import { CloudWatchMetricsQuery, LegacyAnnotationQuery, MetricQueryType, MetricEditorMode } from '../types'; + +// E.g query.statistics = ['Max', 'Min'] will be migrated to two queries - query1.statistic = 'Max' and query2.statistic = 'Min' +export function migrateMultipleStatsMetricsQuery( + query: CloudWatchMetricsQuery, + panelQueries: DataQuery[] +): DataQuery[] { + const newQueries = []; + if (query?.statistics && query?.statistics.length) { + query.statistic = query.statistics[0]; + for (const stat of query.statistics.splice(1)) { + newQueries.push({ ...query, statistic: stat }); + } + } + for (const newTarget of newQueries) { + newTarget.refId = getNextRefIdChar(panelQueries); + delete newTarget.statistics; + panelQueries.push(newTarget); + } + delete query.statistics; + + return newQueries; +} + +// Migrates an annotation query that use more than one statistic into multiple queries +// E.g query.statistics = ['Max', 'Min'] will be migrated to two queries - query1.statistic = 'Max' and query2.statistic = 'Min' +export function migrateMultipleStatsAnnotationQuery( + annotationQuery: AnnotationQuery +): Array> { + const newAnnotations: Array> = []; + + if (annotationQuery && 'statistics' in annotationQuery && annotationQuery?.statistics?.length) { + for (const stat of annotationQuery.statistics.splice(1)) { + const { statistics, name, ...newAnnotation } = annotationQuery; + newAnnotations.push({ ...newAnnotation, statistic: stat, name: `${name} - ${stat}` }); + } + annotationQuery.statistic = annotationQuery.statistics[0]; + // Only change the name of the original if new annotations have been created + if (newAnnotations.length !== 0) { + annotationQuery.name = `${annotationQuery.name} - ${annotationQuery.statistic}`; + } + delete annotationQuery.statistics; + } + + return newAnnotations; +} + +export function migrateCloudWatchQuery(query: CloudWatchMetricsQuery) { + if (!query.hasOwnProperty('metricQueryType')) { + query.metricQueryType = MetricQueryType.Search; + } + + if (!query.hasOwnProperty('metricEditorMode')) { + if (query.metricQueryType === MetricQueryType.Query) { + query.metricEditorMode = MetricEditorMode.Code; + } else { + query.metricEditorMode = query.expression ? MetricEditorMode.Code : MetricEditorMode.Builder; + } + } +} diff --git a/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.test.ts b/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.test.ts new file mode 100644 index 00000000000..41122a8e66d --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.test.ts @@ -0,0 +1,101 @@ +import { config } from '@grafana/runtime'; + +import { CloudWatchMetricsQuery } from '../types'; + +import { migrateAliasPatterns } from './metricQueryMigrations'; + +describe('metricQueryMigrations', () => { + interface TestScenario { + description?: string; + alias: string; + label?: string; + } + + describe('migrateAliasPatterns', () => { + const baseQuery: CloudWatchMetricsQuery = { + statistic: 'Average', + refId: 'A', + id: '', + region: 'us-east-2', + namespace: 'AWS/EC2', + period: '300', + alias: '', + metricName: 'CPUUtilization', + dimensions: {}, + matchExact: false, + expression: '', + }; + describe('when feature is enabled', () => { + describe('and label was not previously migrated', () => { + const cases: TestScenario[] = [ + { description: 'Metric name', alias: '{{metric}}', label: "${PROP('MetricName')}" }, + { description: 'Trim pattern', alias: '{{ metric }}', label: "${PROP('MetricName')}" }, + { description: 'Namespace', alias: '{{namespace}}', label: "${PROP('Namespace')}" }, + { description: 'Period', alias: '{{period}}', label: "${PROP('Period')}" }, + { description: 'Region', alias: '{{region}}', label: "${PROP('Region')}" }, + { description: 'Statistic', alias: '{{stat}}', label: "${PROP('Stat')}" }, + { description: 'Label', alias: '{{label}}', label: '${LABEL}' }, + { + description: 'Non-existing alias pattern', + alias: '{{anything_else}}', + label: "${PROP('Dim.anything_else')}", + }, + { + description: 'Combined patterns', + alias: 'some {{combination}} of {{label}} and {{metric}}', + label: "some ${PROP('Dim.combination')} of ${LABEL} and ${PROP('MetricName')}", + }, + { + description: 'Combined patterns not trimmed', + alias: 'some {{combination }}{{ label}} and {{metric}}', + label: "some ${PROP('Dim.combination')}${LABEL} and ${PROP('MetricName')}", + }, + ]; + test.each(cases)('given old alias %p, it should be migrated to label: %p', ({ alias, label }) => { + config.featureToggles.cloudWatchDynamicLabels = true; + const testQuery = { ...baseQuery, alias }; + const result = migrateAliasPatterns(testQuery); + expect(result.label).toBe(label); + expect(result.alias).toBe(alias); + }); + }); + + describe('and label was previously migrated', () => { + const cases: TestScenario[] = [ + { + alias: '', + label: "${PROP('MetricName')}", + }, + { alias: '{{metric}}', label: "${PROP('Period')}" }, + { alias: '{{namespace}}', label: '' }, + ]; + test.each(cases)('it should not be migrated once again', ({ alias, label }) => { + config.featureToggles.cloudWatchDynamicLabels = true; + const testQuery = { ...baseQuery, alias, label }; + const result = migrateAliasPatterns(testQuery); + expect(result.label).toBe(label); + expect(result.alias).toBe(alias); + }); + }); + }); + + describe('when feature is disabled', () => { + const cases: TestScenario[] = [ + { alias: '{{period}}', label: "${PROP('MetricName')}" }, + { alias: '{{metric}}', label: '' }, + { alias: '', label: "${PROP('MetricName')}" }, + { alias: '', label: undefined }, + ]; + test.each(cases)('should not migrate alias', ({ alias, label }) => { + config.featureToggles.cloudWatchDynamicLabels = false; + const testQuery = { ...baseQuery, alias: `${alias}` }; + if (label !== undefined) { + testQuery.label = label; + } + const result = migrateAliasPatterns(testQuery); + expect(result.label).toBe(label); + expect(result.alias).toBe(alias); + }); + }); + }); +}); diff --git a/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.ts b/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.ts new file mode 100644 index 00000000000..446b44c7710 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.ts @@ -0,0 +1,35 @@ +import { config } from '@grafana/runtime'; + +import { CloudWatchMetricsQuery } from '../types'; + +// Call this function to migrate queries from within the plugin. +export function migrateMetricQuery(query: CloudWatchMetricsQuery): CloudWatchMetricsQuery { + //add metric query migrations here + const migratedQuery = migrateAliasPatterns(query); + return migratedQuery; +} + +const aliasPatterns: Record = { + metric: `PROP('MetricName')`, + namespace: `PROP('Namespace')`, + period: `PROP('Period')`, + region: `PROP('Region')`, + stat: `PROP('Stat')`, + label: `LABEL`, +}; + +export function migrateAliasPatterns(query: CloudWatchMetricsQuery): CloudWatchMetricsQuery { + if (config.featureToggles.cloudWatchDynamicLabels && !query.hasOwnProperty('label')) { + const regex = /{{\s*(.+?)\s*}}/g; + query.label = + query.alias?.replace(regex, (_, value) => { + if (aliasPatterns.hasOwnProperty(value)) { + return `\${${aliasPatterns[value]}}`; + } + + return `\${PROP('Dim.${value}')}`; + }) ?? ''; + } + + return query; +} diff --git a/public/app/plugins/datasource/cloudwatch/migrations/variableQueryMigrations.test.ts b/public/app/plugins/datasource/cloudwatch/migrations/variableQueryMigrations.test.ts new file mode 100644 index 00000000000..5f6534f5c6a --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/migrations/variableQueryMigrations.test.ts @@ -0,0 +1,96 @@ +import { VariableQueryType, OldVariableQuery } from '../types'; + +import { migrateVariableQuery } from './variableQueryMigrations'; + +describe('variableQueryMigrations', () => { + describe('migrateVariableQuery', () => { + describe('when metrics query is used', () => { + describe('and region param is left out', () => { + it('should leave an empty region', () => { + const query = migrateVariableQuery('metrics(testNamespace)'); + expect(query.queryType).toBe(VariableQueryType.Metrics); + expect(query.namespace).toBe('testNamespace'); + expect(query.region).toBe(''); + }); + }); + + describe('and region param is defined by user', () => { + it('should use the user defined region', () => { + const query = migrateVariableQuery('metrics(testNamespace2, custom-region)'); + expect(query.queryType).toBe(VariableQueryType.Metrics); + expect(query.namespace).toBe('testNamespace2'); + expect(query.region).toBe('custom-region'); + }); + }); + }); + describe('when dimension_values query is used', () => { + describe('and filter param is left out', () => { + it('should leave an empty filter', () => { + const query = migrateVariableQuery('dimension_values(us-east-1,AWS/RDS,CPUUtilization,DBInstanceIdentifier)'); + expect(query.queryType).toBe(VariableQueryType.DimensionValues); + expect(query.region).toBe('us-east-1'); + expect(query.namespace).toBe('AWS/RDS'); + expect(query.metricName).toBe('CPUUtilization'); + expect(query.dimensionKey).toBe('DBInstanceIdentifier'); + expect(query.dimensionFilters).toStrictEqual({}); + }); + }); + describe('and filter param is defined by user', () => { + it('should use the user defined filter', () => { + const query = migrateVariableQuery( + 'dimension_values(us-east-1,AWS/RDS,CPUUtilization,DBInstanceIdentifier,{"InstanceId":"$instance_id"})' + ); + expect(query.queryType).toBe(VariableQueryType.DimensionValues); + expect(query.region).toBe('us-east-1'); + expect(query.namespace).toBe('AWS/RDS'); + expect(query.metricName).toBe('CPUUtilization'); + expect(query.dimensionKey).toBe('DBInstanceIdentifier'); + expect(query.dimensionFilters).toStrictEqual({ InstanceId: '$instance_id' }); + }); + }); + }); + }); + describe('when resource_arns query is used', () => { + it('should parse the query', () => { + const query = migrateVariableQuery( + 'resource_arns(eu-west-1,elasticloadbalancing:loadbalancer,{"elasticbeanstalk:environment-name":["myApp-dev","myApp-prod"]})' + ); + expect(query.queryType).toBe(VariableQueryType.ResourceArns); + expect(query.region).toBe('eu-west-1'); + expect(query.resourceType).toBe('elasticloadbalancing:loadbalancer'); + expect(query.tags).toStrictEqual({ 'elasticbeanstalk:environment-name': ['myApp-dev', 'myApp-prod'] }); + }); + }); + describe('when ec2_instance_attribute query is used', () => { + it('should parse the query', () => { + const query = migrateVariableQuery('ec2_instance_attribute(us-east-1,rds:db,{"environment":["$environment"]})'); + expect(query.queryType).toBe(VariableQueryType.EC2InstanceAttributes); + expect(query.region).toBe('us-east-1'); + expect(query.attributeName).toBe('rds:db'); + expect(query.ec2Filters).toStrictEqual({ environment: ['$environment'] }); + }); + }); + describe('when OldVariableQuery is used', () => { + it('should parse the query', () => { + const oldQuery: OldVariableQuery = { + queryType: VariableQueryType.EC2InstanceAttributes, + namespace: '', + region: 'us-east-1', + metricName: '', + dimensionKey: '', + ec2Filters: '{"environment":["$environment"]}', + instanceID: '', + attributeName: 'rds:db', + resourceType: 'elasticloadbalancing:loadbalancer', + tags: '{"elasticbeanstalk:environment-name":["myApp-dev","myApp-prod"]}', + refId: '', + }; + const query = migrateVariableQuery(oldQuery); + expect(query.region).toBe('us-east-1'); + expect(query.attributeName).toBe('rds:db'); + expect(query.ec2Filters).toStrictEqual({ environment: ['$environment'] }); + expect(query.resourceType).toBe('elasticloadbalancing:loadbalancer'); + expect(query.tags).toStrictEqual({ 'elasticbeanstalk:environment-name': ['myApp-dev', 'myApp-prod'] }); + }); + }); +}); diff --git a/public/app/plugins/datasource/cloudwatch/migrations.ts b/public/app/plugins/datasource/cloudwatch/migrations/variableQueryMigrations.ts similarity index 63% rename from public/app/plugins/datasource/cloudwatch/migrations.ts rename to public/app/plugins/datasource/cloudwatch/migrations/variableQueryMigrations.ts index 88650a2246d..25fae8c167a 100644 --- a/public/app/plugins/datasource/cloudwatch/migrations.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations/variableQueryMigrations.ts @@ -1,77 +1,6 @@ import { omit } from 'lodash'; -import { AnnotationQuery, DataQuery } from '@grafana/data'; -import { getNextRefIdChar } from 'app/core/utils/query'; - -import { - CloudWatchMetricsQuery, - LegacyAnnotationQuery, - MetricEditorMode, - MetricQueryType, - VariableQuery, - VariableQueryType, - OldVariableQuery, -} from './types'; - -// Migrates a metric query that use more than one statistic into multiple queries -// E.g query.statistics = ['Max', 'Min'] will be migrated to two queries - query1.statistic = 'Max' and query2.statistic = 'Min' -export function migrateMultipleStatsMetricsQuery( - query: CloudWatchMetricsQuery, - panelQueries: DataQuery[] -): DataQuery[] { - const newQueries = []; - if (query?.statistics && query?.statistics.length) { - query.statistic = query.statistics[0]; - for (const stat of query.statistics.splice(1)) { - newQueries.push({ ...query, statistic: stat }); - } - } - for (const newTarget of newQueries) { - newTarget.refId = getNextRefIdChar(panelQueries); - delete newTarget.statistics; - panelQueries.push(newTarget); - } - delete query.statistics; - - return newQueries; -} - -// Migrates an annotation query that use more than one statistic into multiple queries -// E.g query.statistics = ['Max', 'Min'] will be migrated to two queries - query1.statistic = 'Max' and query2.statistic = 'Min' -export function migrateMultipleStatsAnnotationQuery( - annotationQuery: AnnotationQuery -): Array> { - const newAnnotations: Array> = []; - - if (annotationQuery && 'statistics' in annotationQuery && annotationQuery?.statistics?.length) { - for (const stat of annotationQuery.statistics.splice(1)) { - const { statistics, name, ...newAnnotation } = annotationQuery; - newAnnotations.push({ ...newAnnotation, statistic: stat, name: `${name} - ${stat}` }); - } - annotationQuery.statistic = annotationQuery.statistics[0]; - // Only change the name of the original if new annotations have been created - if (newAnnotations.length !== 0) { - annotationQuery.name = `${annotationQuery.name} - ${annotationQuery.statistic}`; - } - delete annotationQuery.statistics; - } - - return newAnnotations; -} - -export function migrateCloudWatchQuery(query: CloudWatchMetricsQuery) { - if (!query.hasOwnProperty('metricQueryType')) { - query.metricQueryType = MetricQueryType.Search; - } - - if (!query.hasOwnProperty('metricEditorMode')) { - if (query.metricQueryType === MetricQueryType.Query) { - query.metricEditorMode = MetricEditorMode.Code; - } else { - query.metricEditorMode = query.expression ? MetricEditorMode.Code : MetricEditorMode.Builder; - } - } -} +import { VariableQuery, VariableQueryType, OldVariableQuery } from '../types'; function isVariableQuery(rawQuery: string | VariableQuery | OldVariableQuery): rawQuery is VariableQuery { return typeof rawQuery !== 'string' && typeof rawQuery.ec2Filters !== 'string' && typeof rawQuery.tags !== 'string'; diff --git a/public/app/plugins/datasource/cloudwatch/types.ts b/public/app/plugins/datasource/cloudwatch/types.ts index e169e3fb329..9a5545b64fa 100644 --- a/public/app/plugins/datasource/cloudwatch/types.ts +++ b/public/app/plugins/datasource/cloudwatch/types.ts @@ -46,7 +46,9 @@ export interface CloudWatchMetricsQuery extends MetricStat, DataQuery { //common props id: string; + alias?: string; + label?: string; // Math expression query expression?: string; diff --git a/public/app/plugins/datasource/cloudwatch/variables.ts b/public/app/plugins/datasource/cloudwatch/variables.ts index 82733bb0861..20c58214aba 100644 --- a/public/app/plugins/datasource/cloudwatch/variables.ts +++ b/public/app/plugins/datasource/cloudwatch/variables.ts @@ -5,7 +5,7 @@ import { CustomVariableSupport, DataQueryRequest, DataQueryResponse } from '@gra import { VariableQueryEditor } from './components/VariableQueryEditor/VariableQueryEditor'; import { CloudWatchDatasource } from './datasource'; -import { migrateVariableQuery } from './migrations'; +import { migrateVariableQuery } from './migrations/variableQueryMigrations'; import { VariableQuery, VariableQueryType } from './types'; export class CloudWatchVariableSupport extends CustomVariableSupport {