Cloudwatch: Dynamic labels frontend migration (#48579)

* migrate metric queries

* restructure migrations

* self review

* cleanup tests

* ensure alias is not changed

* apply pr feedback
This commit is contained in:
Erik Sundell
2022-05-03 13:52:17 +02:00
committed by GitHub
parent bc58e2edab
commit 929d3134d1
12 changed files with 345 additions and 201 deletions

View File

@@ -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';

View File

@@ -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';

View File

@@ -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<CloudWatchMetricsQuery, 'refId'> = {
@@ -21,10 +22,11 @@ export const DEFAULT_QUERY: Omit<CloudWatchMetricsQuery, 'refId'> = {
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;
};
/**

View File

@@ -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,27 +267,35 @@ export class CloudWatchDatasource
throw new Error('invalid metric editor mode');
}
replaceMetricQueryVars(
query: CloudWatchMetricsQuery,
options: DataQueryRequest<CloudWatchQuery>
): 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<CloudWatchQuery>
): Observable<DataQueryResponse> => {
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,
...migratedAndIterpolatedQuery,
type: 'timeSeriesQuery',
datasource: this.getRef(),
};

View File

@@ -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'] });
});
});
});

View File

@@ -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<LegacyAnnotationQuery>
): Array<AnnotationQuery<DataQuery>> {
const newAnnotations: Array<AnnotationQuery<LegacyAnnotationQuery>> = [];
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;
}
}
}

View File

@@ -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);
});
});
});
});

View File

@@ -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<string, string> = {
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;
}

View File

@@ -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'] });
});
});
});

View File

@@ -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<LegacyAnnotationQuery>
): Array<AnnotationQuery<DataQuery>> {
const newAnnotations: Array<AnnotationQuery<LegacyAnnotationQuery>> = [];
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';

View File

@@ -46,7 +46,9 @@ export interface CloudWatchMetricsQuery extends MetricStat, DataQuery {
//common props
id: string;
alias?: string;
label?: string;
// Math expression query
expression?: string;

View File

@@ -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<CloudWatchDatasource, VariableQuery> {