CloudWatch: Allow hidden queries to be executed in case an ID is provided (#50987)

* allow hidden queries in case an id is provided

* cleanup test

* name tests properly
This commit is contained in:
Erik Sundell 2022-06-17 10:24:38 +02:00 committed by GitHub
parent 1f0c951bce
commit fcbe0059c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 144 additions and 116 deletions

View File

@ -0,0 +1,28 @@
import { CloudWatchMetricsQuery, MetricQueryType, MetricEditorMode, CloudWatchLogsQuery } from '../types';
export const validMetricsQuery: CloudWatchMetricsQuery = {
id: '',
queryMode: 'Metrics',
region: 'us-east-2',
namespace: 'AWS/EC2',
period: '3000',
alias: '',
metricName: 'CPUUtilization',
dimensions: { InstanceId: 'i-123' },
matchExact: true,
statistic: 'Average',
expression: '',
refId: 'A',
metricQueryType: MetricQueryType.Search,
metricEditorMode: MetricEditorMode.Code,
hide: false,
};
export const validLogsQuery: CloudWatchLogsQuery = {
queryMode: 'Logs',
logGroupNames: ['group-A', 'group-B'],
hide: false,
id: '',
region: 'us-east-2',
refId: 'A',
};

View File

@ -15,10 +15,11 @@ import {
setupMockedDataSource, setupMockedDataSource,
regionVariable, regionVariable,
} from './__mocks__/CloudWatchDataSource'; } from './__mocks__/CloudWatchDataSource';
import { validLogsQuery, validMetricsQuery } from './__mocks__/queries';
import { import {
CloudWatchLogsQuery,
CloudWatchLogsQueryStatus, CloudWatchLogsQueryStatus,
CloudWatchMetricsQuery, CloudWatchMetricsQuery,
CloudWatchQuery,
MetricEditorMode, MetricEditorMode,
MetricQueryType, MetricQueryType,
} from './types'; } from './types';
@ -45,6 +46,19 @@ describe('datasource', () => {
}); });
}); });
const testTable: Array<{ query: CloudWatchQuery; valid: boolean }> = [
{ query: { ...validLogsQuery, hide: true }, valid: false },
{ query: { ...validLogsQuery, hide: false }, valid: true },
{ query: { ...validMetricsQuery, hide: true }, valid: false },
{ query: { ...validMetricsQuery, hide: true, id: 'queryA' }, valid: true },
{ query: { ...validMetricsQuery, hide: false }, valid: true },
];
test.each(testTable)('should filter out hidden queries unless id is provided', ({ query, valid }) => {
const { datasource } = setupMockedDataSource();
expect(datasource.filterQuery(query)).toEqual(valid);
});
it('should interpolate variables in the query', async () => { it('should interpolate variables in the query', async () => {
const { datasource, fetchMock } = setupMockedDataSource(); const { datasource, fetchMock } = setupMockedDataSource();
await lastValueFrom( await lastValueFrom(
@ -180,120 +194,107 @@ describe('datasource', () => {
}); });
}); });
describe('filterQuery', () => { describe('filterMetricsQuery', () => {
const datasource = setupMockedDataSource().datasource; const datasource = setupMockedDataSource().datasource;
describe('CloudWatchLogsQuery', () => { let baseQuery: CloudWatchMetricsQuery;
const baseQuery: CloudWatchLogsQuery = { beforeEach(() => {
queryMode: 'Logs', baseQuery = {
id: '', id: '',
region: '', region: 'us-east-2',
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', () => {
let baseQuery: CloudWatchMetricsQuery; it('should error if invalid mode', async () => {
expect(() => datasource.filterMetricQuery(baseQuery)).toThrowError('invalid metric editor mode');
});
describe('metric search queries', () => {
beforeEach(() => { beforeEach(() => {
baseQuery = { baseQuery = {
id: '', ...baseQuery,
region: 'us-east-2', namespace: 'AWS/EC2',
namespace: '', metricName: 'CPUUtilization',
period: '', statistic: 'Average',
alias: '', metricQueryType: MetricQueryType.Search,
metricName: '', metricEditorMode: MetricEditorMode.Builder,
dimensions: {},
matchExact: true,
statistic: '',
expression: '',
refId: '',
}; };
}); });
it('should error if invalid mode', async () => { it('should not allow builder queries that dont have namespace, metric or statistic', async () => {
expect(() => datasource.filterQuery(baseQuery)).toThrowError('invalid metric editor mode'); expect(datasource.filterMetricQuery({ ...baseQuery, statistic: undefined })).toBeFalsy();
expect(datasource.filterMetricQuery({ ...baseQuery, metricName: undefined })).toBeFalsy();
expect(datasource.filterMetricQuery({ ...baseQuery, namespace: '' })).toBeFalsy();
}); });
describe('metric search queries', () => { it('should allow builder queries that have namespace, metric or statistic', async () => {
beforeEach(() => { expect(datasource.filterMetricQuery(baseQuery)).toBeTruthy();
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();
});
}); });
describe('metric search expression queries', () => { it('should not allow code queries that dont have an expression', async () => {
beforeEach(() => { expect(
baseQuery = { datasource.filterMetricQuery({
...baseQuery, ...baseQuery,
metricQueryType: MetricQueryType.Search, expression: undefined,
metricEditorMode: MetricEditorMode.Code, metricEditorMode: MetricEditorMode.Code,
}; })
}); ).toBeFalsy();
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();
});
}); });
describe('metric query queries', () => { it('should allow code queries that have an expression', async () => {
beforeEach(() => { expect(
baseQuery = { datasource.filterMetricQuery({ ...baseQuery, expression: 'x * 2', metricEditorMode: MetricEditorMode.Code })
...baseQuery, ).toBeTruthy();
metricQueryType: MetricQueryType.Query, });
metricEditorMode: MetricEditorMode.Code, });
};
});
it('should not allow queries that dont have a sql expresssion', async () => { describe('metric search expression queries', () => {
const valid = datasource.filterQuery(baseQuery); beforeEach(() => {
expect(valid).toBeFalsy(); baseQuery = {
}); ...baseQuery,
metricQueryType: MetricQueryType.Search,
metricEditorMode: MetricEditorMode.Code,
};
});
it('should allow queries that have a sql expresssion', async () => { it('should not allow queries that dont have an expression', async () => {
baseQuery.sqlExpression = 'select SUM(CPUUtilization) from "AWS/EC2"'; const valid = datasource.filterMetricQuery(baseQuery);
const valid = datasource.filterQuery(baseQuery); expect(valid).toBeFalsy();
expect(valid).toBeTruthy(); });
});
it('should allow queries that have an expression', async () => {
baseQuery.expression = 'SUM([a,x])';
const valid = datasource.filterMetricQuery(baseQuery);
expect(valid).toBeTruthy();
});
});
describe('metric query queries', () => {
beforeEach(() => {
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();
}); });
}); });
}); });

View File

@ -133,10 +133,14 @@ export class CloudWatchDatasource
this.annotations = CloudWatchAnnotationSupport; this.annotations = CloudWatchAnnotationSupport;
} }
filterQuery(query: CloudWatchQuery) {
return query.hide !== true || (isCloudWatchMetricsQuery(query) && query.id !== '');
}
query(options: DataQueryRequest<CloudWatchQuery>): Observable<DataQueryResponse> { query(options: DataQueryRequest<CloudWatchQuery>): Observable<DataQueryResponse> {
options = cloneDeep(options); options = cloneDeep(options);
let queries = options.targets.filter((item) => item.hide !== true); let queries = options.targets.filter(this.filterQuery);
const { logQueries, metricsQueries, annotationQueries } = this.getTargetsByQueryMode(queries); const { logQueries, metricsQueries, annotationQueries } = this.getTargetsByQueryMode(queries);
const dataQueryResponses: Array<Observable<DataQueryResponse>> = []; const dataQueryResponses: Array<Observable<DataQueryResponse>> = [];
@ -245,14 +249,7 @@ export class CloudWatchDatasource
); );
}; };
filterQuery(query: CloudWatchQuery): boolean { filterMetricQuery(query: CloudWatchMetricsQuery): boolean {
if (isCloudWatchLogsQuery(query)) {
return !!query.logGroupNames?.length;
} else if (isCloudWatchAnnotationQuery(query)) {
// annotation query validity already checked in annotationSupport
return true;
}
const { region, metricQueryType, metricEditorMode, expression, metricName, namespace, sqlExpression, statistic } = const { region, metricQueryType, metricEditorMode, expression, metricName, namespace, sqlExpression, statistic } =
query; query;
if (!region) { if (!region) {
@ -296,19 +293,21 @@ export class CloudWatchDatasource
format: 'Z', format: 'Z',
}).replace(':', ''); }).replace(':', '');
const validMetricsQueries = metricQueries.filter(this.filterQuery).map((q: CloudWatchMetricsQuery): MetricQuery => { const validMetricsQueries = metricQueries
const migratedQuery = migrateMetricQuery(q); .filter(this.filterMetricQuery)
const migratedAndIterpolatedQuery = this.replaceMetricQueryVars(migratedQuery, options); .map((q: CloudWatchMetricsQuery): MetricQuery => {
const migratedQuery = migrateMetricQuery(q);
const migratedAndIterpolatedQuery = this.replaceMetricQueryVars(migratedQuery, options);
return { return {
timezoneUTCOffset, timezoneUTCOffset,
intervalMs: options.intervalMs, intervalMs: options.intervalMs,
maxDataPoints: options.maxDataPoints, maxDataPoints: options.maxDataPoints,
...migratedAndIterpolatedQuery, ...migratedAndIterpolatedQuery,
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)) {