CloudWatch Logs: Add selected region to autocomplete requests (#42194)

* Add region to get fields query

* Fix and add tests
This commit is contained in:
Andrej Ocenas 2021-11-26 13:33:34 +01:00 committed by GitHub
parent 879cdcd0c7
commit 802ffa3f03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 51 deletions

View File

@ -263,7 +263,7 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
}; };
onTypeahead = async (typeahead: TypeaheadInput): Promise<TypeaheadOutput> => { onTypeahead = async (typeahead: TypeaheadInput): Promise<TypeaheadOutput> => {
const { datasource } = this.props; const { datasource, query } = this.props;
const { selectedLogGroups } = this.state; const { selectedLogGroups } = this.state;
if (!datasource.languageProvider) { if (!datasource.languageProvider) {
@ -276,7 +276,12 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
return await cloudwatchLanguageProvider.provideCompletionItems( return await cloudwatchLanguageProvider.provideCompletionItems(
{ text, value, prefix, wrapperClasses, labelKey, editor }, { text, value, prefix, wrapperClasses, labelKey, editor },
{ history, absoluteRange, logGroupNames: selectedLogGroups.map((logGroup) => logGroup.value!) } {
history,
absoluteRange,
logGroupNames: selectedLogGroups.map((logGroup) => logGroup.value!),
region: query.region,
}
); );
}; };

View File

@ -125,6 +125,34 @@ describe('datasource', () => {
expect(fetchMock.mock.calls[1][0].data.queries[0].region).toBe('eu-east'); expect(fetchMock.mock.calls[1][0].data.queries[0].region).toBe('eu-east');
}); });
}); });
describe('getLogGroupFields', () => {
it('passes region correctly', async () => {
const { datasource, fetchMock } = setupMockedDataSource();
fetchMock.mockReturnValueOnce(
of({
data: {
results: {
A: {
frames: [
dataFrameToJSON(
new MutableDataFrame({
fields: [
{ name: 'key', values: [] },
{ name: 'val', values: [] },
],
})
),
],
},
},
},
})
);
await datasource.getLogGroupFields({ region: 'us-west-1', logGroupName: 'test' });
expect(fetchMock.mock.calls[0][0].data.queries[0].region).toBe('us-west-1');
});
});
}); });
function setupForLogs() { function setupForLogs() {

View File

@ -49,6 +49,7 @@ import {
TSDBResponse, TSDBResponse,
Dimensions, Dimensions,
MetricFindSuggestData, MetricFindSuggestData,
CloudWatchLogsRequest,
} from './types'; } from './types';
import { CloudWatchLanguageProvider } from './language_provider'; import { CloudWatchLanguageProvider } from './language_provider';
import { VariableWithMultiSupport } from 'app/features/variables/types'; import { VariableWithMultiSupport } from 'app/features/variables/types';
@ -547,7 +548,7 @@ export class CloudWatchDatasource
makeLogActionRequest( makeLogActionRequest(
subtype: LogAction, subtype: LogAction,
queryParams: Array<GetLogEventsRequest | StartQueryRequest | DescribeLogGroupsRequest | GetLogGroupFieldsRequest>, queryParams: CloudWatchLogsRequest[],
options: { options: {
scopedVars?: ScopedVars; scopedVars?: ScopedVars;
makeReplacements?: boolean; makeReplacements?: boolean;
@ -562,47 +563,43 @@ export class CloudWatchDatasource
const requestParams = { const requestParams = {
from: range.from.valueOf().toString(), from: range.from.valueOf().toString(),
to: range.to.valueOf().toString(), to: range.to.valueOf().toString(),
queries: queryParams.map( queries: queryParams.map((param: CloudWatchLogsRequest) => ({
(param: GetLogEventsRequest | StartQueryRequest | DescribeLogGroupsRequest | GetLogGroupFieldsRequest) => ({ refId: (param as StartQueryRequest).refId || 'A',
refId: (param as StartQueryRequest).refId || 'A', intervalMs: 1, // dummy
intervalMs: 1, // dummy maxDataPoints: 1, // dummy
maxDataPoints: 1, // dummy datasource: this.getRef(),
datasource: this.getRef(), type: 'logAction',
type: 'logAction', subtype: subtype,
subtype: subtype, ...param,
...param, })),
})
),
}; };
if (options.makeReplacements) { if (options.makeReplacements) {
requestParams.queries.forEach( requestParams.queries.forEach((query: CloudWatchLogsRequest) => {
(query: GetLogEventsRequest | StartQueryRequest | DescribeLogGroupsRequest | GetLogGroupFieldsRequest) => { const fieldsToReplace: Array<
const fieldsToReplace: Array< keyof (GetLogEventsRequest & StartQueryRequest & DescribeLogGroupsRequest & GetLogGroupFieldsRequest)
keyof (GetLogEventsRequest & StartQueryRequest & DescribeLogGroupsRequest & GetLogGroupFieldsRequest) > = ['queryString', 'logGroupNames', 'logGroupName', 'logGroupNamePrefix'];
> = ['queryString', 'logGroupNames', 'logGroupName', 'logGroupNamePrefix'];
const anyQuery: any = query; const anyQuery: any = query;
for (const fieldName of fieldsToReplace) { for (const fieldName of fieldsToReplace) {
if (query.hasOwnProperty(fieldName)) { if (query.hasOwnProperty(fieldName)) {
if (Array.isArray(anyQuery[fieldName])) { if (Array.isArray(anyQuery[fieldName])) {
anyQuery[fieldName] = anyQuery[fieldName].map((val: string) => anyQuery[fieldName] = anyQuery[fieldName].map((val: string) =>
this.replace(val, options.scopedVars, true, fieldName) this.replace(val, options.scopedVars, true, fieldName)
); );
} else { } else {
anyQuery[fieldName] = this.replace(anyQuery[fieldName], options.scopedVars, true, fieldName); anyQuery[fieldName] = this.replace(anyQuery[fieldName], options.scopedVars, true, fieldName);
}
} }
} }
// TODO: seems to be some sort of bug that we don't really send region with all queries. This means
// if you select different than default region in editor you will get results for autocomplete from wrong
// region.
if (anyQuery.region) {
anyQuery.region = this.replace(anyQuery.region, options.scopedVars, true, 'region');
anyQuery.region = this.getActualRegion(anyQuery.region);
}
} }
); // TODO: seems to be some sort of bug that we don't really send region with all queries. This means
// if you select different than default region in editor you will get results for autocomplete from wrong
// region.
if (anyQuery.region) {
anyQuery.region = this.replace(anyQuery.region, options.scopedVars, true, 'region');
anyQuery.region = this.getActualRegion(anyQuery.region);
}
});
} }
const resultsToDataFrames = (val: any): DataFrame[] => toDataQueryResponse(val).data || []; const resultsToDataFrames = (val: any): DataFrame[] => toDataQueryResponse(val).data || [];

View File

@ -127,7 +127,7 @@ function getProvideCompletionItems(query: string): Promise<TypeaheadOutput> {
{ {
value, value,
} as any, } as any,
{ logGroupNames: ['logGroup1'] } { logGroupNames: ['logGroup1'], region: 'custom' }
); );
} }

View File

@ -23,6 +23,7 @@ type TypeaheadContext = {
history?: CloudWatchHistoryItem[]; history?: CloudWatchHistoryItem[];
absoluteRange?: AbsoluteTimeRange; absoluteRange?: AbsoluteTimeRange;
logGroupNames?: string[]; logGroupNames?: string[];
region: string;
}; };
export class CloudWatchLanguageProvider extends LanguageProvider { export class CloudWatchLanguageProvider extends LanguageProvider {
@ -104,7 +105,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
} }
if (isInsideFunctionParenthesis(curToken)) { if (isInsideFunctionParenthesis(curToken)) {
return await this.getFieldCompletionItems(context?.logGroupNames ?? []); return await this.getFieldCompletionItems(context?.logGroupNames ?? [], context?.region || 'default');
} }
if (isAfterKeyword('by', curToken)) { if (isAfterKeyword('by', curToken)) {
@ -133,7 +134,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
} }
| undefined; | undefined;
private fetchFields = async (logGroups: string[]): Promise<string[]> => { private fetchFields = async (logGroups: string[], region: string): Promise<string[]> => {
if ( if (
this.fetchedFieldsCache && this.fetchedFieldsCache &&
Date.now() - this.fetchedFieldsCache.time < 30 * 1000 && Date.now() - this.fetchedFieldsCache.time < 30 * 1000 &&
@ -143,7 +144,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
} }
const results = await Promise.all( const results = await Promise.all(
logGroups.map((logGroup) => this.datasource.getLogGroupFields({ logGroupName: logGroup })) logGroups.map((logGroup) => this.datasource.getLogGroupFields({ logGroupName: logGroup, region }))
); );
const fields = [ const fields = [
@ -162,7 +163,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
}; };
private handleKeyword = async (context?: TypeaheadContext): Promise<TypeaheadOutput> => { private handleKeyword = async (context?: TypeaheadContext): Promise<TypeaheadOutput> => {
const suggs = await this.getFieldCompletionItems(context?.logGroupNames ?? []); const suggs = await this.getFieldCompletionItems(context?.logGroupNames ?? [], context?.region || 'default');
const functionSuggestions: CompletionItemGroup[] = [ const functionSuggestions: CompletionItemGroup[] = [
{ {
searchFunctionType: SearchFunctionType.Prefix, searchFunctionType: SearchFunctionType.Prefix,
@ -190,7 +191,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
if (queryCommand === 'parse') { if (queryCommand === 'parse') {
if (currentTokenIsFirstArg) { if (currentTokenIsFirstArg) {
return await this.getFieldCompletionItems(context?.logGroupNames ?? []); return await this.getFieldCompletionItems(context?.logGroupNames ?? [], context?.region || 'default');
} }
} }
@ -207,7 +208,10 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
} }
if (['display', 'fields'].includes(queryCommand)) { if (['display', 'fields'].includes(queryCommand)) {
const typeaheadOutput = await this.getFieldCompletionItems(context?.logGroupNames ?? []); const typeaheadOutput = await this.getFieldCompletionItems(
context?.logGroupNames ?? [],
context?.region || 'default'
);
typeaheadOutput.suggestions.push(...this.getFieldAndFilterFunctionCompletionItems().suggestions); typeaheadOutput.suggestions.push(...this.getFieldAndFilterFunctionCompletionItems().suggestions);
return typeaheadOutput; return typeaheadOutput;
@ -224,7 +228,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
} }
if (queryCommand === 'filter' && currentTokenIsFirstArg) { if (queryCommand === 'filter' && currentTokenIsFirstArg) {
const sugg = await this.getFieldCompletionItems(context?.logGroupNames ?? []); const sugg = await this.getFieldCompletionItems(context?.logGroupNames ?? [], context?.region || 'default');
const boolFuncs = this.getBoolFuncCompletionItems(); const boolFuncs = this.getBoolFuncCompletionItems();
sugg.suggestions.push(...boolFuncs.suggestions); sugg.suggestions.push(...boolFuncs.suggestions);
return sugg; return sugg;
@ -238,7 +242,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
context?: TypeaheadContext context?: TypeaheadContext
): Promise<TypeaheadOutput> { ): Promise<TypeaheadOutput> {
if (isFirstArgument) { if (isFirstArgument) {
return await this.getFieldCompletionItems(context?.logGroupNames ?? []); return await this.getFieldCompletionItems(context?.logGroupNames ?? [], context?.region || 'default');
} else if (isTokenType(prevNonWhitespaceToken(curToken), 'field-name')) { } else if (isTokenType(prevNonWhitespaceToken(curToken), 'field-name')) {
// suggest sort options // suggest sort options
return { return {
@ -261,7 +265,10 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
} }
private handleComparison = async (context?: TypeaheadContext) => { private handleComparison = async (context?: TypeaheadContext) => {
const fieldsSuggestions = await this.getFieldCompletionItems(context?.logGroupNames ?? []); const fieldsSuggestions = await this.getFieldCompletionItems(
context?.logGroupNames ?? [],
context?.region || 'default'
);
const comparisonSuggestions = this.getComparisonCompletionItems(); const comparisonSuggestions = this.getComparisonCompletionItems();
fieldsSuggestions.suggestions.push(...comparisonSuggestions.suggestions); fieldsSuggestions.suggestions.push(...comparisonSuggestions.suggestions);
return fieldsSuggestions; return fieldsSuggestions;
@ -313,8 +320,8 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
}; };
}; };
private getFieldCompletionItems = async (logGroups: string[]): Promise<TypeaheadOutput> => { private getFieldCompletionItems = async (logGroups: string[], region: string): Promise<TypeaheadOutput> => {
const fields = await this.fetchFields(logGroups); const fields = await this.fetchFields(logGroups, region);
return { return {
suggestions: [ suggestions: [

View File

@ -164,7 +164,7 @@ describe('CloudWatchDatasource', () => {
'container-insights-prometheus-demo', 'container-insights-prometheus-demo',
]; ];
const logGroups = await ds.describeLogGroups({}); const logGroups = await ds.describeLogGroups({ region: 'default' });
expect(logGroups).toEqual(expectedLogGroups); expect(logGroups).toEqual(expectedLogGroups);
}); });

View File

@ -122,6 +122,12 @@ export interface QueryStatistics {
export type QueryStatus = 'Scheduled' | 'Running' | 'Complete' | 'Failed' | 'Cancelled' | string; export type QueryStatus = 'Scheduled' | 'Running' | 'Complete' | 'Failed' | 'Cancelled' | string;
export type CloudWatchLogsRequest =
| GetLogEventsRequest
| StartQueryRequest
| DescribeLogGroupsRequest
| GetLogGroupFieldsRequest;
export interface GetLogEventsRequest { export interface GetLogEventsRequest {
/** /**
* The name of the log group. * The name of the log group.
@ -182,7 +188,7 @@ export interface DescribeLogGroupsRequest {
*/ */
limit?: number; limit?: number;
refId?: string; refId?: string;
region?: string; region: string;
} }
export interface TSDBResponse<T = any> { export interface TSDBResponse<T = any> {
@ -256,6 +262,7 @@ export interface GetLogGroupFieldsRequest {
* The time to set as the center of the query. If you specify time, the 8 minutes before and 8 minutes after this time are searched. If you omit time, the past 15 minutes are queried. The time value is specified as epoch time, the number of seconds since January 1, 1970, 00:00:00 UTC. * The time to set as the center of the query. If you specify time, the 8 minutes before and 8 minutes after this time are searched. If you omit time, the past 15 minutes are queried. The time value is specified as epoch time, the number of seconds since January 1, 1970, 00:00:00 UTC.
*/ */
time?: number; time?: number;
region: string;
} }
export interface LogGroupField { export interface LogGroupField {