diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/AnnotationQueryRunner.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/AnnotationQueryRunner.ts index e58db07ade7..68db686c975 100644 --- a/public/app/plugins/datasource/cloudwatch/__mocks__/AnnotationQueryRunner.ts +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/AnnotationQueryRunner.ts @@ -8,7 +8,7 @@ import { CloudWatchAnnotationQueryRunner } from '../query-runner/CloudWatchAnnot import { CloudWatchQuery } from '../types'; import { CloudWatchSettings, setupMockedTemplateService } from './CloudWatchDataSource'; -import { timeRange } from './timeRange'; +import { TimeRangeMock } from './timeRange'; export function setupMockedAnnotationQueryRunner({ variables }: { variables?: CustomVariableModel[] }) { let templateService = new TemplateSrv(); @@ -25,7 +25,7 @@ export function setupMockedAnnotationQueryRunner({ variables }: { variables?: Cu }); const request: DataQueryRequest = { - range: timeRange, + range: TimeRangeMock, rangeRaw: { from: '1483228800', to: '1483232400' }, targets: [], requestId: '', @@ -37,5 +37,5 @@ export function setupMockedAnnotationQueryRunner({ variables }: { variables?: Cu startTime: 0, }; - return { runner, fetchMock, templateService, request, timeRange }; + return { runner, fetchMock, templateService, request, timeRange: TimeRangeMock }; } diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/MetricsQueryRunner.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/MetricsQueryRunner.ts index 20bb2966c44..483150c2894 100644 --- a/public/app/plugins/datasource/cloudwatch/__mocks__/MetricsQueryRunner.ts +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/MetricsQueryRunner.ts @@ -8,7 +8,7 @@ import { CloudWatchMetricsQueryRunner } from '../query-runner/CloudWatchMetricsQ import { CloudWatchJsonData, CloudWatchQuery } from '../types'; import { CloudWatchSettings, setupMockedTemplateService } from './CloudWatchDataSource'; -import { timeRange } from './timeRange'; +import { TimeRangeMock } from './timeRange'; export function setupMockedMetricsQueryRunner({ data = { @@ -44,7 +44,7 @@ export function setupMockedMetricsQueryRunner({ }); const request: DataQueryRequest = { - range: timeRange, + range: TimeRangeMock, rangeRaw: { from: '1483228800', to: '1483232400' }, targets: [], requestId: '', @@ -56,5 +56,5 @@ export function setupMockedMetricsQueryRunner({ startTime: 0, }; - return { runner, fetchMock, templateService, instanceSettings, request, timeRange }; + return { runner, fetchMock, templateService, instanceSettings, request, timeRange: TimeRangeMock }; } diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/Request.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/Request.ts new file mode 100644 index 00000000000..efb8420b351 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/Request.ts @@ -0,0 +1,18 @@ +import { DataQueryRequest } from '@grafana/data'; + +import { CloudWatchQuery } from '../types'; + +import { TimeRangeMock } from './timeRange'; + +export const RequestMock: DataQueryRequest = { + range: TimeRangeMock, + rangeRaw: { from: TimeRangeMock.from, to: TimeRangeMock.to }, + targets: [], + requestId: '', + interval: '', + intervalMs: 0, + scopedVars: {}, + timezone: '', + app: '', + startTime: 0, +}; diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/queries.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/queries.ts index 1280b72be72..ecfd25d3f1b 100644 --- a/public/app/plugins/datasource/cloudwatch/__mocks__/queries.ts +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/queries.ts @@ -91,4 +91,5 @@ export const validLogsQuery: CloudWatchLogsQuery = { id: '', region: 'us-east-2', refId: 'A', + expression: `fields @timestamp, @message | sort @timestamp desc | limit 25`, }; diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/timeRange.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/timeRange.ts index dac1431d6ce..77696776e68 100644 --- a/public/app/plugins/datasource/cloudwatch/__mocks__/timeRange.ts +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/timeRange.ts @@ -3,4 +3,4 @@ import { dateTime, TimeRange } from '@grafana/data'; const start = 1483196400 * 1000; const from = dateTime(start); const to = dateTime(start + 3600 * 1000); -export const timeRange: TimeRange = { from, to, raw: { from, to } }; +export const TimeRangeMock: TimeRange = { from, to, raw: { from, to } }; diff --git a/public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.test.tsx b/public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.test.tsx new file mode 100644 index 00000000000..4ea04cf0ea7 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.test.tsx @@ -0,0 +1,87 @@ +import { act, render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; + +import { LoadingState } from '@grafana/data'; + +import { setupMockedDataSource } from '../__mocks__/CloudWatchDataSource'; +import { RequestMock } from '../__mocks__/Request'; +import { validLogsQuery } from '../__mocks__/queries'; +import { CloudWatchLogsQuery } from '../types'; + +import { CloudWatchLink } from './CloudWatchLink'; + +describe('CloudWatchLink', () => { + it('generates a link with log group names', async () => { + const ds = setupMockedDataSource(); + + const { rerender } = render( + + ); + + await waitFor(() => { + expect(screen.getByText('CloudWatch Logs Insights').closest('a')).toHaveAttribute('href', ''); + }); + + await act(async () => { + rerender( + + ); + }); + + await waitFor(() => { + expect(screen.getByText('CloudWatch Logs Insights').closest('a')).toHaveAttribute( + 'href', + "https://us-east-2.console.aws.amazon.com/cloudwatch/home?region=us-east-2#logs-insights:queryDetail=~(end~'2016-12-31T16*3a00*3a00.000Z~start~'2016-12-31T15*3a00*3a00.000Z~timeType~'ABSOLUTE~tz~'UTC~editorString~'fields*20*40timestamp*2c*20*40message*20*7c*20sort*20*40timestamp*20desc*20*7c*20limit*2025~isLiveTail~false~source~(~'group-A~'group-B))" + ); + }); + }); + + it('generates a link with log group names', async () => { + const ds = setupMockedDataSource(); + const query: CloudWatchLogsQuery = { + ...validLogsQuery, + logGroupNames: undefined, + logGroups: [ + { value: 'arn:aws:logs:us-east-1:111111111111:log-group:/aws/lambda/test1', text: '/aws/lambda/test1' }, + { value: 'arn:aws:logs:us-east-1:111111111111:log-group:/aws/lambda/test2', text: '/aws/lambda/test2' }, + ], + }; + + const { rerender } = render(); + + await waitFor(() => { + expect(screen.getByText('CloudWatch Logs Insights').closest('a')).toHaveAttribute('href', ''); + }); + + await act(async () => { + rerender( + + ); + }); + + await waitFor(() => { + expect(screen.getByText('CloudWatch Logs Insights').closest('a')).toHaveAttribute( + 'href', + "https://us-east-2.console.aws.amazon.com/cloudwatch/home?region=us-east-2#logs-insights:queryDetail=~(end~'2016-12-31T16*3a00*3a00.000Z~start~'2016-12-31T15*3a00*3a00.000Z~timeType~'ABSOLUTE~tz~'UTC~editorString~'fields*20*40timestamp*2c*20*40message*20*7c*20sort*20*40timestamp*20desc*20*7c*20limit*2025~isLiveTail~false~source~(~'arn*3aaws*3alogs*3aus-east-1*3a111111111111*3alog-group*3a*2faws*2flambda*2ftest1~'arn*3aaws*3alogs*3aus-east-1*3a111111111111*3alog-group*3a*2faws*2flambda*2ftest2))" + ); + }); + }); +}); diff --git a/public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.tsx b/public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.tsx index 85ea6a4fc81..e8e4e5c8929 100644 --- a/public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.tsx @@ -1,9 +1,10 @@ -import React, { Component } from 'react'; +import React, { useEffect, useState } from 'react'; +import { usePrevious } from 'react-use'; import { PanelData } from '@grafana/data'; import { Icon } from '@grafana/ui'; -import { encodeUrl, AwsUrl } from '../aws_url'; +import { AwsUrl, encodeUrl } from '../aws_url'; import { CloudWatchDatasource } from '../datasource'; import { CloudWatchLogsQuery } from '../types'; @@ -13,54 +14,39 @@ interface Props { datasource: CloudWatchDatasource; } -interface State { - href: string; -} +export function CloudWatchLink({ panelData, query, datasource }: Props) { + const [href, setHref] = useState(''); + const prevPanelData = usePrevious(panelData); -export default class CloudWatchLink extends Component { - state: State = { href: '' }; + useEffect(() => { + if (prevPanelData !== panelData && panelData?.request?.range) { + const arns = (query.logGroups ?? []) + .filter((group) => group?.value) + .map((group) => (group.value ?? '').replace(/:\*$/, '')); // remove `:*` from end of arn + const logGroupNames = query.logGroupNames; + let sources = arns?.length ? arns : logGroupNames; - async componentDidUpdate(prevProps: Props) { - const { panelData: panelDataNew } = this.props; - const { panelData: panelDataOld } = prevProps; + const range = panelData?.request?.range; + const start = range.from.toISOString(); + const end = range.to.toISOString(); - if (panelDataOld !== panelDataNew && panelDataNew?.request) { - const href = this.getExternalLink(); - this.setState({ href }); + const urlProps: AwsUrl = { + end, + start, + timeType: 'ABSOLUTE', + tz: 'UTC', + editorString: query.expression ?? '', + isLiveTail: false, + source: sources ?? [], + }; + + setHref(encodeUrl(urlProps, datasource.api.getActualRegion(query.region))); } - } + }, [panelData, prevPanelData, datasource, query]); - getExternalLink(): string { - const { query, panelData, datasource } = this.props; - - const range = panelData?.request?.range; - - if (!range) { - return ''; - } - - const start = range.from.toISOString(); - const end = range.to.toISOString(); - - const urlProps: AwsUrl = { - end, - start, - timeType: 'ABSOLUTE', - tz: 'UTC', - editorString: query.expression ?? '', - isLiveTail: false, - source: query.logGroupNames ?? [], - }; - - return encodeUrl(urlProps, datasource.api.getActualRegion(query.region)); - } - - render() { - const { href } = this.state; - return ( - - CloudWatch Logs Insights - - ); - } + return ( + + CloudWatch Logs Insights + + ); } diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx index dca3678399d..69dfe3077f9 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx @@ -9,7 +9,7 @@ import { InlineFormLabel } from '@grafana/ui'; import { CloudWatchDatasource } from '../datasource'; import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery } from '../types'; -import CloudWatchLink from './CloudWatchLink'; +import { CloudWatchLink } from './CloudWatchLink'; import CloudWatchLogsQueryField from './LogsQueryField'; type Props = QueryEditorProps & { diff --git a/public/app/plugins/datasource/cloudwatch/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/datasource.test.ts index 42e1f6cdf62..545525d1b9c 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.test.ts @@ -11,7 +11,7 @@ import { } from './__mocks__/CloudWatchDataSource'; import { setupForLogs } from './__mocks__/logsTestContext'; import { validLogsQuery, validMetricSearchBuilderQuery } from './__mocks__/queries'; -import { timeRange } from './__mocks__/timeRange'; +import { TimeRangeMock } from './__mocks__/timeRange'; import { CloudWatchLogsQuery, CloudWatchMetricsQuery, CloudWatchQuery } from './types'; describe('datasource', () => { @@ -43,7 +43,7 @@ describe('datasource', () => { requestId: '', interval: '', intervalMs: 0, - range: timeRange, + range: TimeRangeMock, scopedVars: {}, timezone: '', app: '', @@ -83,7 +83,7 @@ describe('datasource', () => { requestId: '', interval: '', intervalMs: 0, - range: timeRange, + range: TimeRangeMock, scopedVars: {}, timezone: '', app: '', @@ -106,7 +106,7 @@ describe('datasource', () => { requestId: '', interval: '', intervalMs: 0, - range: timeRange, + range: TimeRangeMock, scopedVars: {}, timezone: '', app: '', @@ -152,7 +152,7 @@ describe('datasource', () => { requestId: '', interval: '', intervalMs: 0, - range: timeRange, + range: TimeRangeMock, scopedVars: {}, timezone: '', app: '', @@ -188,7 +188,7 @@ describe('datasource', () => { requestId: '', interval: '', intervalMs: 0, - range: timeRange, + range: TimeRangeMock, scopedVars: {}, timezone: '', app: '', @@ -232,7 +232,7 @@ describe('datasource', () => { requestId: '', interval: '', intervalMs: 0, - range: timeRange, + range: TimeRangeMock, scopedVars: {}, timezone: '', app: '',