diff --git a/public/app/plugins/datasource/cloudwatch/aws_url.ts b/public/app/plugins/datasource/cloudwatch/aws_url.ts index 5e820a77690..3982b3af421 100644 --- a/public/app/plugins/datasource/cloudwatch/aws_url.ts +++ b/public/app/plugins/datasource/cloudwatch/aws_url.ts @@ -12,7 +12,7 @@ export interface AwsUrl { } export function encodeUrl(obj: AwsUrl, region: string): string { - return `https://${region}.console.aws.amazon.com/cloudwatch/home?region=${region}#logsV2:logs-insights$3FqueryDetail$3D${JSURL.stringify( + return `https://${region}.console.aws.amazon.com/cloudwatch/home?region=${region}#logs-insights:queryDetail=${JSURL.stringify( obj )}`; } diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index f1ea1ea48c2..aa00c133f65 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -47,6 +47,7 @@ import { CloudWatchLanguageProvider } from './language_provider'; const TSDB_QUERY_ENDPOINT = '/api/tsdb/query'; import { VariableWithMultiSupport } from 'app/features/templating/types'; import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider'; +import { AwsUrl, encodeUrl } from './aws_url'; const displayAlert = (datasourceName: string, region: string) => store.dispatch( @@ -118,7 +119,8 @@ export class CloudWatchDatasource extends DataSourceApi this.addDataLinksToLogsResponse(response, options)) ); } @@ -224,12 +226,48 @@ export class CloudWatchDatasource extends DataSourceApi) { + for (const dataFrame of response.data as DataFrame[]) { + const range = this.timeSrv.timeRange(); + const start = range.from.toISOString(); + const end = range.to.toISOString(); + + const curTarget = options.targets.find(target => target.refId === dataFrame.refId) as CloudWatchLogsQuery; + const urlProps: AwsUrl = { + end, + start, + timeType: 'ABSOLUTE', + tz: 'UTC', + editorString: curTarget.expression, + isLiveTail: false, + source: curTarget.logGroupNames, + }; + + const encodedUrl = encodeUrl( + urlProps, + this.getActualRegion(this.replace(curTarget.region, options.scopedVars, true, 'region')) + ); + + for (const field of dataFrame.fields) { + field.config.links = [ + { + url: encodedUrl, + title: 'View in CloudWatch console', + targetBlank: true, + }, + ]; + } + } + + return response; + } + stopQueries() { if (this.logQueries.size > 0) { this.makeLogActionRequest( 'StopQuery', [...this.logQueries.values()].map(logQuery => ({ queryId: logQuery.id, region: logQuery.region })), - null, + undefined, false ).pipe(finalize(() => this.logQueries.clear())); } diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts index cbf34b42cd5..a26480bde35 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts @@ -1,7 +1,7 @@ import '../datasource'; import { CloudWatchDatasource } from '../datasource'; import * as redux from 'app/store/store'; -import { DataSourceInstanceSettings, dateMath, getFrameDisplayTitle } from '@grafana/data'; +import { DataSourceInstanceSettings, dateMath, getFrameDisplayTitle, DataQueryResponse } from '@grafana/data'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { CustomVariable } from 'app/features/templating/all'; import { CloudWatchQuery, CloudWatchMetricsQuery } from '../types'; @@ -28,7 +28,7 @@ describe('CloudWatchDatasource', () => { const defaultTimeRange = { from: new Date(start), to: new Date(start + 3600 * 1000) }; const timeSrv = { - time: { from: 'now-1h', to: 'now' }, + time: { from: '2016-12-31 15:00:00', to: '2016-12-31 16:00:00' }, timeRange: () => { return { from: dateMath.parse(timeSrv.time.from, false), @@ -105,6 +105,59 @@ describe('CloudWatchDatasource', () => { }); }); + describe('When performing CloudWatch logs query', () => { + it('should add data links to response', () => { + const mockResponse: DataQueryResponse = { + data: [ + { + fields: [ + { + config: { + links: [], + }, + }, + ], + refId: 'A', + }, + ], + }; + + const mockOptions = { + targets: [ + { + refId: 'A', + expression: 'stats count(@message) by bin(1h)', + logGroupNames: ['fake-log-group-one', 'fake-log-group-two'], + region: 'default', + }, + ], + }; + + const saturatedResponse = ctx.ds.addDataLinksToLogsResponse(mockResponse, mockOptions); + expect(saturatedResponse).toMatchObject({ + data: [ + { + fields: [ + { + config: { + links: [ + { + url: + "https://us-east-1.console.aws.amazon.com/cloudwatch/home?region=us-east-1#logs-insights:queryDetail=~(end~'2016-12-31T16*3a00*3a00.000Z~start~'2016-12-31T15*3a00*3a00.000Z~timeType~'ABSOLUTE~tz~'UTC~editorString~'stats*20count*28*40message*29*20by*20bin*281h*29~isLiveTail~false~source~(~'fake-log-group-one~'fake-log-group-two))", + title: 'View in CloudWatch console', + targetBlank: true, + }, + ], + }, + }, + ], + refId: 'A', + }, + ], + }); + }); + }); + describe('When performing CloudWatch metrics query', () => { const query = { range: defaultTimeRange,