mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CloudWatch/Logs: Add error message when log groups are not selected (#24361)
* Add error message * Fix empty check
This commit is contained in:
parent
a521a39ebf
commit
d385045d16
@ -1,24 +1,39 @@
|
|||||||
import { CloudWatchDatasource } from './datasource';
|
import { CloudWatchDatasource } from './datasource';
|
||||||
import { TemplateSrv } from '../../../features/templating/template_srv';
|
import { TemplateSrv } from '../../../features/templating/template_srv';
|
||||||
import { setBackendSrv } from '@grafana/runtime';
|
import { setBackendSrv } from '@grafana/runtime';
|
||||||
import { DefaultTimeRange } from '@grafana/data';
|
import { DataQueryResponse, DefaultTimeRange } from '@grafana/data';
|
||||||
|
|
||||||
describe('datasource', () => {
|
describe('datasource', () => {
|
||||||
|
describe('query', () => {
|
||||||
|
it('should return error if log query and log groups is not specified', async () => {
|
||||||
|
const { datasource } = setup();
|
||||||
|
const response: DataQueryResponse = (await datasource.query({
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
queryMode: 'Logs' as 'Logs',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any)) as any;
|
||||||
|
expect(response.error.message).toBe('Log group is required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty response if queries are hidden', async () => {
|
||||||
|
const { datasource } = setup();
|
||||||
|
const response: DataQueryResponse = (await datasource.query({
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
queryMode: 'Logs' as 'Logs',
|
||||||
|
hide: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any)) as any;
|
||||||
|
expect(response.data).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('describeLogGroup', () => {
|
describe('describeLogGroup', () => {
|
||||||
it('replaces region correctly in the query', async () => {
|
it('replaces region correctly in the query', async () => {
|
||||||
const datasource = new CloudWatchDatasource(
|
const { datasource, datasourceRequestMock } = setup();
|
||||||
{ jsonData: { defaultRegion: 'us-west-1' } } as any,
|
|
||||||
new TemplateSrv(),
|
|
||||||
{
|
|
||||||
timeRange() {
|
|
||||||
return DefaultTimeRange;
|
|
||||||
},
|
|
||||||
} as any
|
|
||||||
);
|
|
||||||
const datasourceRequestMock = jest.fn();
|
|
||||||
datasourceRequestMock.mockResolvedValue({ data: [] });
|
|
||||||
setBackendSrv({ datasourceRequest: datasourceRequestMock } as any);
|
|
||||||
|
|
||||||
await datasource.describeLogGroups({ region: 'default' });
|
await datasource.describeLogGroups({ region: 'default' });
|
||||||
expect(datasourceRequestMock.mock.calls[0][0].data.queries[0].region).toBe('us-west-1');
|
expect(datasourceRequestMock.mock.calls[0][0].data.queries[0].region).toBe('us-west-1');
|
||||||
|
|
||||||
@ -27,3 +42,16 @@ describe('datasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
const datasource = new CloudWatchDatasource({ jsonData: { defaultRegion: 'us-west-1' } } as any, new TemplateSrv(), {
|
||||||
|
timeRange() {
|
||||||
|
return DefaultTimeRange;
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
const datasourceRequestMock = jest.fn();
|
||||||
|
datasourceRequestMock.mockResolvedValue({ data: [] });
|
||||||
|
setBackendSrv({ datasourceRequest: datasourceRequestMock } as any);
|
||||||
|
|
||||||
|
return { datasource, datasourceRequestMock };
|
||||||
|
}
|
||||||
|
@ -39,6 +39,7 @@ import {
|
|||||||
GetLogGroupFieldsResponse,
|
GetLogGroupFieldsResponse,
|
||||||
LogAction,
|
LogAction,
|
||||||
GetLogEventsRequest,
|
GetLogEventsRequest,
|
||||||
|
MetricQuery,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { from, empty, Observable } from 'rxjs';
|
import { from, empty, Observable } from 'rxjs';
|
||||||
import { delay, expand, map, mergeMap, tap, finalize, catchError } from 'rxjs/operators';
|
import { delay, expand, map, mergeMap, tap, finalize, catchError } from 'rxjs/operators';
|
||||||
@ -97,13 +98,27 @@ export class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery, CloudWa
|
|||||||
this.languageProvider = new CloudWatchLanguageProvider(this);
|
this.languageProvider = new CloudWatchLanguageProvider(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
query(options: DataQueryRequest<CloudWatchQuery>) {
|
query(options: DataQueryRequest<CloudWatchQuery>): Promise<DataQueryResponse> | Observable<DataQueryResponse> {
|
||||||
options = angular.copy(options);
|
options = angular.copy(options);
|
||||||
|
|
||||||
const firstTarget = options.targets[0];
|
const firstTarget = options.targets[0];
|
||||||
|
|
||||||
|
let queries = options.targets.filter(item => item.id !== '' || item.hide !== true);
|
||||||
|
|
||||||
if (firstTarget.queryMode === 'Logs') {
|
if (firstTarget.queryMode === 'Logs') {
|
||||||
const queryParams = options.targets.map((target: CloudWatchLogsQuery) => ({
|
const logQueries: CloudWatchLogsQuery[] = queries.filter(item => item.queryMode === 'Logs') as any;
|
||||||
|
|
||||||
|
const validLogQueries = logQueries.filter(item => item.logGroupNames?.length);
|
||||||
|
if (logQueries.length > validLogQueries.length) {
|
||||||
|
return Promise.resolve({ data: [], error: { message: 'Log group is required' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// No valid targets, return the empty result to save a round trip.
|
||||||
|
if (_.isEmpty(validLogQueries)) {
|
||||||
|
return Promise.resolve({ data: [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryParams = validLogQueries.map((target: CloudWatchLogsQuery) => ({
|
||||||
queryString: target.expression,
|
queryString: target.expression,
|
||||||
refId: target.refId,
|
refId: target.refId,
|
||||||
logGroupNames: target.logGroupNames,
|
logGroupNames: target.logGroupNames,
|
||||||
@ -124,57 +139,58 @@ export class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery, CloudWa
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const queries = options.targets
|
const metricQueries: MetricQuery[] = options.targets
|
||||||
.filter(
|
.filter(
|
||||||
item =>
|
item =>
|
||||||
(item.id !== '' || item.hide !== true) &&
|
|
||||||
item.queryMode !== 'Logs' &&
|
item.queryMode !== 'Logs' &&
|
||||||
((!!item.region && !!item.namespace && !!item.metricName && !_.isEmpty(item.statistics)) ||
|
((!!item.region && !!item.namespace && !!item.metricName && !_.isEmpty(item.statistics)) ||
|
||||||
item.expression?.length > 0)
|
item.expression?.length > 0)
|
||||||
)
|
)
|
||||||
.map((item: CloudWatchMetricsQuery) => {
|
.map(
|
||||||
item.region = this.replace(this.getActualRegion(item.region), options.scopedVars, true, 'region');
|
(item: CloudWatchMetricsQuery): MetricQuery => {
|
||||||
item.namespace = this.replace(item.namespace, options.scopedVars, true, 'namespace');
|
item.region = this.replace(this.getActualRegion(item.region), options.scopedVars, true, 'region');
|
||||||
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.statistics = item.statistics.map(stat => this.replace(stat, options.scopedVars, true, 'statistics'));
|
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.statistics = item.statistics.map(stat => this.replace(stat, options.scopedVars, true, 'statistics'));
|
||||||
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.expression = this.templateSrv.replace(item.expression, options.scopedVars);
|
||||||
|
|
||||||
// valid ExtendedStatistics is like p90.00, check the pattern
|
// valid ExtendedStatistics is like p90.00, check the pattern
|
||||||
const hasInvalidStatistics = item.statistics.some(s => {
|
const hasInvalidStatistics = item.statistics.some(s => {
|
||||||
if (s.indexOf('p') === 0) {
|
if (s.indexOf('p') === 0) {
|
||||||
const matches = /^p\d{2}(?:\.\d{1,2})?$/.exec(s);
|
const matches = /^p\d{2}(?:\.\d{1,2})?$/.exec(s);
|
||||||
return !matches || matches[0] !== s;
|
return !matches || matches[0] !== s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasInvalidStatistics) {
|
||||||
|
throw { message: 'Invalid extended statistics' };
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return {
|
||||||
});
|
refId: item.refId,
|
||||||
|
intervalMs: options.intervalMs,
|
||||||
if (hasInvalidStatistics) {
|
maxDataPoints: options.maxDataPoints,
|
||||||
throw { message: 'Invalid extended statistics' };
|
datasourceId: this.id,
|
||||||
|
type: 'timeSeriesQuery',
|
||||||
|
...item,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
);
|
||||||
return {
|
|
||||||
refId: item.refId,
|
|
||||||
intervalMs: options.intervalMs,
|
|
||||||
maxDataPoints: options.maxDataPoints,
|
|
||||||
datasourceId: this.id,
|
|
||||||
type: 'timeSeriesQuery',
|
|
||||||
...item,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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(queries)) {
|
if (_.isEmpty(metricQueries)) {
|
||||||
return Promise.resolve({ data: [] });
|
return Promise.resolve({ data: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
from: options?.range?.from.valueOf().toString(),
|
from: options?.range?.from.valueOf().toString(),
|
||||||
to: options?.range?.to.valueOf().toString(),
|
to: options?.range?.to.valueOf().toString(),
|
||||||
queries: queries,
|
queries: metricQueries,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.performTimeSeriesQuery(request, options.range);
|
return this.performTimeSeriesQuery(request, options.range);
|
||||||
|
@ -297,7 +297,7 @@ export interface MetricRequest {
|
|||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MetricQuery {
|
export interface MetricQuery {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
datasourceId: number;
|
datasourceId: number;
|
||||||
refId?: string;
|
refId?: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user