mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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'] });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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'] });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
@@ -46,7 +46,9 @@ export interface CloudWatchMetricsQuery extends MetricStat, DataQuery {
|
||||
|
||||
//common props
|
||||
id: string;
|
||||
|
||||
alias?: string;
|
||||
label?: string;
|
||||
|
||||
// Math expression query
|
||||
expression?: string;
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user