CloudWatch: filter alert queries (#47222)

This commit is contained in:
Isabella Siu 2022-04-04 12:06:31 -04:00 committed by GitHub
parent 1c34cc8b91
commit b1c68b5a12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 129 additions and 120 deletions

View File

@ -10,8 +10,13 @@ import {
namespaceVariable, namespaceVariable,
setupMockedDataSource, setupMockedDataSource,
} from './__mocks__/CloudWatchDataSource'; } from './__mocks__/CloudWatchDataSource';
import { CloudWatchDatasource } from './datasource'; import {
import { CloudWatchLogsQueryStatus, CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from './types'; CloudWatchLogsQuery,
CloudWatchLogsQueryStatus,
CloudWatchMetricsQuery,
MetricEditorMode,
MetricQueryType,
} from './types';
describe('datasource', () => { describe('datasource', () => {
describe('query', () => { describe('query', () => {
@ -144,111 +149,124 @@ describe('datasource', () => {
}); });
}); });
describe('filterMetricQuery', () => { describe('filterQuery', () => {
let baseQuery: CloudWatchMetricsQuery; const datasource = setupMockedDataSource().datasource;
let datasource: CloudWatchDatasource; describe('CloudWatchLogsQuery', () => {
const baseQuery: CloudWatchLogsQuery = {
beforeEach(() => { queryMode: 'Logs',
datasource = setupMockedDataSource().datasource;
baseQuery = {
id: '', id: '',
region: 'us-east-2', region: '',
namespace: '',
period: '',
alias: '',
metricName: '',
dimensions: {},
matchExact: true,
statistic: '',
expression: '',
refId: '', refId: '',
logGroupNames: ['foo', 'bar'],
}; };
it('should return false if empty logGroupNames', () => {
expect(datasource.filterQuery({ ...baseQuery, logGroupNames: undefined })).toBeFalsy();
});
it('should return true if has logGroupNames', () => {
expect(datasource.filterQuery(baseQuery)).toBeTruthy();
});
}); });
describe('CloudWatchMetricsQuery', () => {
it('should error if invalid mode', async () => { let baseQuery: CloudWatchMetricsQuery;
expect(() => datasource.filterMetricQuery(baseQuery)).toThrowError('invalid metric editor mode');
});
describe('metric search queries', () => {
beforeEach(() => { beforeEach(() => {
datasource = setupMockedDataSource().datasource;
baseQuery = { baseQuery = {
...baseQuery, id: '',
namespace: 'AWS/EC2', region: 'us-east-2',
metricName: 'CPUUtilization', namespace: '',
statistic: 'Average', period: '',
metricQueryType: MetricQueryType.Search, alias: '',
metricEditorMode: MetricEditorMode.Builder, metricName: '',
dimensions: {},
matchExact: true,
statistic: '',
expression: '',
refId: '',
}; };
}); });
it('should not allow builder queries that dont have namespace, metric or statistic', async () => { it('should error if invalid mode', async () => {
expect(datasource.filterMetricQuery({ ...baseQuery, statistic: undefined })).toBeFalsy(); expect(() => datasource.filterQuery(baseQuery)).toThrowError('invalid metric editor mode');
expect(datasource.filterMetricQuery({ ...baseQuery, metricName: undefined })).toBeFalsy();
expect(datasource.filterMetricQuery({ ...baseQuery, namespace: '' })).toBeFalsy();
}); });
it('should allow builder queries that have namespace, metric or statistic', async () => { describe('metric search queries', () => {
expect(datasource.filterMetricQuery(baseQuery)).toBeTruthy(); beforeEach(() => {
baseQuery = {
...baseQuery,
namespace: 'AWS/EC2',
metricName: 'CPUUtilization',
statistic: 'Average',
metricQueryType: MetricQueryType.Search,
metricEditorMode: MetricEditorMode.Builder,
};
});
it('should not allow builder queries that dont have namespace, metric or statistic', async () => {
expect(datasource.filterQuery({ ...baseQuery, statistic: undefined })).toBeFalsy();
expect(datasource.filterQuery({ ...baseQuery, metricName: undefined })).toBeFalsy();
expect(datasource.filterQuery({ ...baseQuery, namespace: '' })).toBeFalsy();
});
it('should allow builder queries that have namespace, metric or statistic', async () => {
expect(datasource.filterQuery(baseQuery)).toBeTruthy();
});
it('should not allow code queries that dont have an expression', async () => {
expect(
datasource.filterQuery({ ...baseQuery, expression: undefined, metricEditorMode: MetricEditorMode.Code })
).toBeFalsy();
});
it('should allow code queries that have an expression', async () => {
expect(
datasource.filterQuery({ ...baseQuery, expression: 'x * 2', metricEditorMode: MetricEditorMode.Code })
).toBeTruthy();
});
}); });
it('should not allow code queries that dont have an expression', async () => { describe('metric search expression queries', () => {
expect( beforeEach(() => {
datasource.filterMetricQuery({ ...baseQuery, expression: undefined, metricEditorMode: MetricEditorMode.Code }) baseQuery = {
).toBeFalsy(); ...baseQuery,
metricQueryType: MetricQueryType.Search,
metricEditorMode: MetricEditorMode.Code,
};
});
it('should not allow queries that dont have an expression', async () => {
const valid = datasource.filterQuery(baseQuery);
expect(valid).toBeFalsy();
});
it('should allow queries that have an expression', async () => {
baseQuery.expression = 'SUM([a,x])';
const valid = datasource.filterQuery(baseQuery);
expect(valid).toBeTruthy();
});
}); });
it('should allow code queries that have an expression', async () => { describe('metric query queries', () => {
expect( beforeEach(() => {
datasource.filterMetricQuery({ ...baseQuery, expression: 'x * 2', metricEditorMode: MetricEditorMode.Code }) baseQuery = {
).toBeTruthy(); ...baseQuery,
}); metricQueryType: MetricQueryType.Query,
}); metricEditorMode: MetricEditorMode.Code,
};
});
describe('metric search expression queries', () => { it('should not allow queries that dont have a sql expresssion', async () => {
beforeEach(() => { const valid = datasource.filterQuery(baseQuery);
datasource = setupMockedDataSource().datasource; expect(valid).toBeFalsy();
baseQuery = { });
...baseQuery,
metricQueryType: MetricQueryType.Search,
metricEditorMode: MetricEditorMode.Code,
};
});
it('should not allow queries that dont have an expresssion', async () => { it('should allow queries that have a sql expresssion', async () => {
const valid = datasource.filterMetricQuery(baseQuery); baseQuery.sqlExpression = 'select SUM(CPUUtilization) from "AWS/EC2"';
expect(valid).toBeFalsy(); const valid = datasource.filterQuery(baseQuery);
}); expect(valid).toBeTruthy();
});
it('should allow queries that have an expresssion', async () => {
baseQuery.expression = 'SUM([a,x])';
const valid = datasource.filterMetricQuery(baseQuery);
expect(valid).toBeTruthy();
});
});
describe('metric query queries', () => {
beforeEach(() => {
datasource = setupMockedDataSource().datasource;
baseQuery = {
...baseQuery,
metricQueryType: MetricQueryType.Query,
metricEditorMode: MetricEditorMode.Code,
};
});
it('should not allow queries that dont have a sql expresssion', async () => {
const valid = datasource.filterMetricQuery(baseQuery);
expect(valid).toBeFalsy();
});
it('should allow queries that have a sql expresssion', async () => {
baseQuery.sqlExpression = 'select SUM(CPUUtilization) from "AWS/EC2"';
const valid = datasource.filterMetricQuery(baseQuery);
expect(valid).toBeTruthy();
}); });
}); });
}); });
describe('resource requests', () => { describe('resource requests', () => {
it('should map resource response to metric response', async () => { it('should map resource response to metric response', async () => {
const datasource = setupMockedDataSource().datasource; const datasource = setupMockedDataSource().datasource;

View File

@ -234,22 +234,15 @@ export class CloudWatchDatasource
); );
}; };
filterMetricQuery({ filterQuery(query: CloudWatchQuery): boolean {
region, if (query.queryMode === 'Logs') {
metricQueryType, return !!query.logGroupNames?.length;
metricEditorMode, }
expression, const { region, metricQueryType, metricEditorMode, expression, metricName, namespace, sqlExpression, statistic } =
metricName, query;
namespace,
sqlExpression,
statistic,
dimensions,
...rest
}: CloudWatchMetricsQuery): boolean {
if (!region) { if (!region) {
return false; return false;
} }
if (metricQueryType === MetricQueryType.Search && metricEditorMode === MetricEditorMode.Builder) { if (metricQueryType === MetricQueryType.Search && metricEditorMode === MetricEditorMode.Builder) {
return !!namespace && !!metricName && !!statistic; return !!namespace && !!metricName && !!statistic;
} else if (metricQueryType === MetricQueryType.Search && metricEditorMode === MetricEditorMode.Code) { } else if (metricQueryType === MetricQueryType.Search && metricEditorMode === MetricEditorMode.Code) {
@ -266,27 +259,25 @@ export class CloudWatchDatasource
metricQueries: CloudWatchMetricsQuery[], metricQueries: CloudWatchMetricsQuery[],
options: DataQueryRequest<CloudWatchQuery> options: DataQueryRequest<CloudWatchQuery>
): Observable<DataQueryResponse> => { ): Observable<DataQueryResponse> => {
const validMetricsQueries = metricQueries const validMetricsQueries = metricQueries.map((item: CloudWatchMetricsQuery): MetricQuery => {
.filter(this.filterMetricQuery) item.region = this.templateSrv.replace(this.getActualRegion(item.region), options.scopedVars);
.map((item: CloudWatchMetricsQuery): MetricQuery => { item.namespace = this.replace(item.namespace, options.scopedVars, true, 'namespace');
item.region = this.templateSrv.replace(this.getActualRegion(item.region), options.scopedVars); item.metricName = this.replace(item.metricName, options.scopedVars, true, 'metric name');
item.namespace = this.replace(item.namespace, options.scopedVars, true, 'namespace'); item.dimensions = this.convertDimensionFormat(item.dimensions ?? {}, options.scopedVars);
item.metricName = this.replace(item.metricName, options.scopedVars, true, 'metric name'); item.statistic = this.templateSrv.replace(item.statistic, options.scopedVars);
item.dimensions = this.convertDimensionFormat(item.dimensions ?? {}, options.scopedVars); item.period = String(this.getPeriod(item, options)); // use string format for period in graph query, and alerting
item.statistic = this.templateSrv.replace(item.statistic, options.scopedVars); item.id = this.templateSrv.replace(item.id, options.scopedVars);
item.period = String(this.getPeriod(item, options)); // use string format for period in graph query, and alerting item.expression = this.templateSrv.replace(item.expression, options.scopedVars);
item.id = this.templateSrv.replace(item.id, options.scopedVars); item.sqlExpression = this.templateSrv.replace(item.sqlExpression, options.scopedVars, 'raw');
item.expression = this.templateSrv.replace(item.expression, options.scopedVars);
item.sqlExpression = this.templateSrv.replace(item.sqlExpression, options.scopedVars, 'raw');
return { return {
intervalMs: options.intervalMs, intervalMs: options.intervalMs,
maxDataPoints: options.maxDataPoints, maxDataPoints: options.maxDataPoints,
...item, ...item,
type: 'timeSeriesQuery', type: 'timeSeriesQuery',
datasource: this.getRef(), datasource: this.getRef(),
}; };
}); });
// No valid targets, return the empty result to save a round trip. // No valid targets, return the empty result to save a round trip.
if (isEmpty(validMetricsQueries)) { if (isEmpty(validMetricsQueries)) {