From b35fac590577786eae8f4a4a7cb6245efec31673 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 26 Nov 2021 07:55:30 -0500 Subject: [PATCH] CloudWatch Logs: Add selected region to autocomplete requests (#42194) (#42367) * Add region to get fields query * Fix and add tests (cherry picked from commit 802ffa3f03cb19af1ba1017b538ae20fd021469e) Co-authored-by: Andrej Ocenas --- .../cloudwatch/components/LogsQueryField.tsx | 9 ++- .../datasource/cloudwatch/datasource.test.ts | 28 ++++++++ .../datasource/cloudwatch/datasource.ts | 67 +++++++++---------- .../cloudwatch/language_provider.test.ts | 2 +- .../cloudwatch/language_provider.ts | 29 +++++--- .../cloudwatch/specs/datasource.test.ts | 2 +- .../plugins/datasource/cloudwatch/types.ts | 9 ++- 7 files changed, 95 insertions(+), 51 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx index 1e0c423ded0..524ece4c90b 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx @@ -263,7 +263,7 @@ export class CloudWatchLogsQueryField extends React.PureComponent => { - const { datasource } = this.props; + const { datasource, query } = this.props; const { selectedLogGroups } = this.state; if (!datasource.languageProvider) { @@ -276,7 +276,12 @@ export class CloudWatchLogsQueryField extends React.PureComponent logGroup.value!) } + { + history, + absoluteRange, + logGroupNames: selectedLogGroups.map((logGroup) => logGroup.value!), + region: query.region, + } ); }; diff --git a/public/app/plugins/datasource/cloudwatch/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/datasource.test.ts index cda8ca02007..c367d9ccf62 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.test.ts @@ -125,6 +125,34 @@ describe('datasource', () => { 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() { diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index 3909b681c19..f4c4f8e83a1 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -49,6 +49,7 @@ import { TSDBResponse, Dimensions, MetricFindSuggestData, + CloudWatchLogsRequest, } from './types'; import { CloudWatchLanguageProvider } from './language_provider'; import { VariableWithMultiSupport } from 'app/features/variables/types'; @@ -547,7 +548,7 @@ export class CloudWatchDatasource makeLogActionRequest( subtype: LogAction, - queryParams: Array, + queryParams: CloudWatchLogsRequest[], options: { scopedVars?: ScopedVars; makeReplacements?: boolean; @@ -562,47 +563,43 @@ export class CloudWatchDatasource const requestParams = { from: range.from.valueOf().toString(), to: range.to.valueOf().toString(), - queries: queryParams.map( - (param: GetLogEventsRequest | StartQueryRequest | DescribeLogGroupsRequest | GetLogGroupFieldsRequest) => ({ - refId: (param as StartQueryRequest).refId || 'A', - intervalMs: 1, // dummy - maxDataPoints: 1, // dummy - datasource: this.getRef(), - type: 'logAction', - subtype: subtype, - ...param, - }) - ), + queries: queryParams.map((param: CloudWatchLogsRequest) => ({ + refId: (param as StartQueryRequest).refId || 'A', + intervalMs: 1, // dummy + maxDataPoints: 1, // dummy + datasource: this.getRef(), + type: 'logAction', + subtype: subtype, + ...param, + })), }; if (options.makeReplacements) { - requestParams.queries.forEach( - (query: GetLogEventsRequest | StartQueryRequest | DescribeLogGroupsRequest | GetLogGroupFieldsRequest) => { - const fieldsToReplace: Array< - keyof (GetLogEventsRequest & StartQueryRequest & DescribeLogGroupsRequest & GetLogGroupFieldsRequest) - > = ['queryString', 'logGroupNames', 'logGroupName', 'logGroupNamePrefix']; + requestParams.queries.forEach((query: CloudWatchLogsRequest) => { + const fieldsToReplace: Array< + keyof (GetLogEventsRequest & StartQueryRequest & DescribeLogGroupsRequest & GetLogGroupFieldsRequest) + > = ['queryString', 'logGroupNames', 'logGroupName', 'logGroupNamePrefix']; - const anyQuery: any = query; - for (const fieldName of fieldsToReplace) { - if (query.hasOwnProperty(fieldName)) { - if (Array.isArray(anyQuery[fieldName])) { - anyQuery[fieldName] = anyQuery[fieldName].map((val: string) => - this.replace(val, options.scopedVars, true, fieldName) - ); - } else { - anyQuery[fieldName] = this.replace(anyQuery[fieldName], options.scopedVars, true, fieldName); - } + const anyQuery: any = query; + for (const fieldName of fieldsToReplace) { + if (query.hasOwnProperty(fieldName)) { + if (Array.isArray(anyQuery[fieldName])) { + anyQuery[fieldName] = anyQuery[fieldName].map((val: string) => + this.replace(val, options.scopedVars, true, fieldName) + ); + } else { + 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 || []; diff --git a/public/app/plugins/datasource/cloudwatch/language_provider.test.ts b/public/app/plugins/datasource/cloudwatch/language_provider.test.ts index a7efeb9ac64..38743252743 100644 --- a/public/app/plugins/datasource/cloudwatch/language_provider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language_provider.test.ts @@ -127,7 +127,7 @@ function getProvideCompletionItems(query: string): Promise { { value, } as any, - { logGroupNames: ['logGroup1'] } + { logGroupNames: ['logGroup1'], region: 'custom' } ); } diff --git a/public/app/plugins/datasource/cloudwatch/language_provider.ts b/public/app/plugins/datasource/cloudwatch/language_provider.ts index 35449641638..24ec295638c 100644 --- a/public/app/plugins/datasource/cloudwatch/language_provider.ts +++ b/public/app/plugins/datasource/cloudwatch/language_provider.ts @@ -23,6 +23,7 @@ type TypeaheadContext = { history?: CloudWatchHistoryItem[]; absoluteRange?: AbsoluteTimeRange; logGroupNames?: string[]; + region: string; }; export class CloudWatchLanguageProvider extends LanguageProvider { @@ -104,7 +105,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider { } if (isInsideFunctionParenthesis(curToken)) { - return await this.getFieldCompletionItems(context?.logGroupNames ?? []); + return await this.getFieldCompletionItems(context?.logGroupNames ?? [], context?.region || 'default'); } if (isAfterKeyword('by', curToken)) { @@ -133,7 +134,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider { } | undefined; - private fetchFields = async (logGroups: string[]): Promise => { + private fetchFields = async (logGroups: string[], region: string): Promise => { if ( this.fetchedFieldsCache && Date.now() - this.fetchedFieldsCache.time < 30 * 1000 && @@ -143,7 +144,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider { } const results = await Promise.all( - logGroups.map((logGroup) => this.datasource.getLogGroupFields({ logGroupName: logGroup })) + logGroups.map((logGroup) => this.datasource.getLogGroupFields({ logGroupName: logGroup, region })) ); const fields = [ @@ -162,7 +163,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider { }; private handleKeyword = async (context?: TypeaheadContext): Promise => { - const suggs = await this.getFieldCompletionItems(context?.logGroupNames ?? []); + const suggs = await this.getFieldCompletionItems(context?.logGroupNames ?? [], context?.region || 'default'); const functionSuggestions: CompletionItemGroup[] = [ { searchFunctionType: SearchFunctionType.Prefix, @@ -190,7 +191,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider { if (queryCommand === 'parse') { 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)) { - const typeaheadOutput = await this.getFieldCompletionItems(context?.logGroupNames ?? []); + const typeaheadOutput = await this.getFieldCompletionItems( + context?.logGroupNames ?? [], + context?.region || 'default' + ); typeaheadOutput.suggestions.push(...this.getFieldAndFilterFunctionCompletionItems().suggestions); return typeaheadOutput; @@ -224,7 +228,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider { } 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(); sugg.suggestions.push(...boolFuncs.suggestions); return sugg; @@ -238,7 +242,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider { context?: TypeaheadContext ): Promise { if (isFirstArgument) { - return await this.getFieldCompletionItems(context?.logGroupNames ?? []); + return await this.getFieldCompletionItems(context?.logGroupNames ?? [], context?.region || 'default'); } else if (isTokenType(prevNonWhitespaceToken(curToken), 'field-name')) { // suggest sort options return { @@ -261,7 +265,10 @@ export class CloudWatchLanguageProvider extends LanguageProvider { } 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(); fieldsSuggestions.suggestions.push(...comparisonSuggestions.suggestions); return fieldsSuggestions; @@ -313,8 +320,8 @@ export class CloudWatchLanguageProvider extends LanguageProvider { }; }; - private getFieldCompletionItems = async (logGroups: string[]): Promise => { - const fields = await this.fetchFields(logGroups); + private getFieldCompletionItems = async (logGroups: string[], region: string): Promise => { + const fields = await this.fetchFields(logGroups, region); return { suggestions: [ diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts index 862684cb4f7..d272dff4853 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts @@ -164,7 +164,7 @@ describe('CloudWatchDatasource', () => { 'container-insights-prometheus-demo', ]; - const logGroups = await ds.describeLogGroups({}); + const logGroups = await ds.describeLogGroups({ region: 'default' }); expect(logGroups).toEqual(expectedLogGroups); }); diff --git a/public/app/plugins/datasource/cloudwatch/types.ts b/public/app/plugins/datasource/cloudwatch/types.ts index 32e9bd0dc69..00a9ec74ec2 100644 --- a/public/app/plugins/datasource/cloudwatch/types.ts +++ b/public/app/plugins/datasource/cloudwatch/types.ts @@ -122,6 +122,12 @@ export interface QueryStatistics { export type QueryStatus = 'Scheduled' | 'Running' | 'Complete' | 'Failed' | 'Cancelled' | string; +export type CloudWatchLogsRequest = + | GetLogEventsRequest + | StartQueryRequest + | DescribeLogGroupsRequest + | GetLogGroupFieldsRequest; + export interface GetLogEventsRequest { /** * The name of the log group. @@ -182,7 +188,7 @@ export interface DescribeLogGroupsRequest { */ limit?: number; refId?: string; - region?: string; + region: string; } export interface TSDBResponse { @@ -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. */ time?: number; + region: string; } export interface LogGroupField {