From 7c85db5bfaa742c1026bf6d776210ca84e11d89e Mon Sep 17 00:00:00 2001 From: Isabella Siu Date: Wed, 25 Jan 2023 09:39:42 -0500 Subject: [PATCH] CloudWatch Logs: Set default logs query and disable button when empty (#61956) --- .../components/QueryHeader.test.tsx | 95 ++++++++++++++++++- .../cloudwatch/components/QueryHeader.tsx | 12 ++- .../datasource/cloudwatch/defaultQueries.ts | 3 +- 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryHeader.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryHeader.test.tsx index 2e782cc07a1..9cc6fef9582 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryHeader.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryHeader.test.tsx @@ -6,6 +6,7 @@ import { config } from '@grafana/runtime'; import { setupMockedDataSource } from '../__mocks__/CloudWatchDataSource'; import { validLogsQuery, validMetricSearchBuilderQuery } from '../__mocks__/queries'; +import { DEFAULT_LOGS_QUERY_STRING } from '../defaultQueries'; import QueryHeader from './QueryHeader'; @@ -16,11 +17,10 @@ const ds = setupMockedDataSource({ ds.datasource.resources.getRegions = jest.fn().mockResolvedValue([]); describe('QueryHeader', () => { - afterEach(() => { - config.featureToggles.cloudWatchCrossAccountQuerying = originalFeatureToggleValue; - }); - describe('when changing region', () => { + afterEach(() => { + config.featureToggles.cloudWatchCrossAccountQuerying = originalFeatureToggleValue; + }); const { datasource } = setupMockedDataSource(); datasource.resources.getRegions = jest.fn().mockResolvedValue([ { value: 'us-east-2', label: 'us-east-2' }, @@ -117,4 +117,91 @@ describe('QueryHeader', () => { expect(datasource.resources.isMonitoringAccount).not.toHaveBeenCalledWith(); }); }); + + describe('when changing query mode', () => { + const { datasource } = setupMockedDataSource(); + it('should set default log query when switching to log mode', async () => { + const onChange = jest.fn(); + datasource.resources.isMonitoringAccount = jest.fn().mockResolvedValue(false); + render( + + ); + expect(await screen.findByText('CloudWatch Metrics')).toBeInTheDocument(); + await selectEvent.select(await screen.findByLabelText('Query mode'), 'CloudWatch Logs', { + container: document.body, + }); + expect(onChange).toHaveBeenCalledWith({ + ...validMetricSearchBuilderQuery, + logGroupNames: undefined, + logGroups: [], + queryMode: 'Logs', + sqlExpression: '', + expression: DEFAULT_LOGS_QUERY_STRING, + }); + }); + + it('should set expression to empty when switching to metrics mode', async () => { + const onChange = jest.fn(); + datasource.resources.isMonitoringAccount = jest.fn().mockResolvedValue(false); + render( + + ); + expect(await screen.findByText('CloudWatch Logs')).toBeInTheDocument(); + await selectEvent.select(await screen.findByLabelText('Query mode'), 'CloudWatch Metrics', { + container: document.body, + }); + expect(onChange).toHaveBeenCalledWith({ + ...validMetricSearchBuilderQuery, + logGroupNames: undefined, + logGroups: [], + sqlExpression: '', + expression: '', + }); + }); + }); + describe('log expression', () => { + const { datasource } = setupMockedDataSource(); + it('should disable run query button when empty', async () => { + const onChange = jest.fn(); + datasource.resources.isMonitoringAccount = jest.fn().mockResolvedValue(false); + render( + + ); + expect(await screen.findByText('Run queries')).toBeInTheDocument(); + expect(screen.getByText('Run queries').closest('button')).toBeDisabled(); + }); + it('should enable run query button when set', async () => { + const onChange = jest.fn(); + datasource.resources.isMonitoringAccount = jest.fn().mockResolvedValue(false); + render( + + ); + expect(await screen.findByText('Run queries')).toBeInTheDocument(); + expect(screen.getByText('Run queries').closest('button')).not.toBeDisabled(); + }); + }); }); diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryHeader.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryHeader.tsx index 76f05ee0468..e5f1e5eb4e6 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryHeader.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryHeader.tsx @@ -6,7 +6,8 @@ import { config } from '@grafana/runtime'; import { Badge, Button } from '@grafana/ui'; import { CloudWatchDatasource } from '../datasource'; -import { isCloudWatchMetricsQuery } from '../guards'; +import { DEFAULT_LOGS_QUERY_STRING } from '../defaultQueries'; +import { isCloudWatchLogsQuery, isCloudWatchMetricsQuery } from '../guards'; import { useIsMonitoringAccount, useRegions } from '../hooks'; import { CloudWatchJsonData, CloudWatchQuery, CloudWatchQueryMode, MetricQueryType } from '../types'; @@ -34,12 +35,19 @@ const QueryHeader: React.FC = ({ const { queryMode, region } = query; const isMonitoringAccount = useIsMonitoringAccount(datasource.resources, query.region); const [regions, regionIsLoading] = useRegions(datasource); + const emptyLogsExpression = isCloudWatchLogsQuery(query) ? !query.expression : false; const onQueryModeChange = ({ value }: SelectableValue) => { if (value && value !== queryMode) { + // reset expression to a default string when the query mode changes + let expression = ''; + if (value === 'Logs') { + expression = DEFAULT_LOGS_QUERY_STRING; + } onChange({ ...datasource.getDefaultQuery(CoreApp.Unknown), ...query, + expression, queryMode: value, }); } @@ -100,7 +108,7 @@ const QueryHeader: React.FC = ({ size="sm" onClick={onRunQuery} icon={data?.state === LoadingState.Loading ? 'fa fa-spinner' : undefined} - disabled={data?.state === LoadingState.Loading} + disabled={data?.state === LoadingState.Loading || emptyLogsExpression} > Run queries diff --git a/public/app/plugins/datasource/cloudwatch/defaultQueries.ts b/public/app/plugins/datasource/cloudwatch/defaultQueries.ts index 5eff9bbd8cb..e921f9d75cc 100644 --- a/public/app/plugins/datasource/cloudwatch/defaultQueries.ts +++ b/public/app/plugins/datasource/cloudwatch/defaultQueries.ts @@ -16,13 +16,14 @@ export const DEFAULT_METRICS_QUERY: Omit = { matchExact: true, }; +export const DEFAULT_LOGS_QUERY_STRING = 'fields @timestamp, @message |\n sort @timestamp desc |\n limit 20'; + export const getDefaultLogsQuery = ( defaultLogGroups?: LogGroup[], legacyDefaultLogGroups?: string[] ): Omit => ({ id: '', region: 'default', - expression: '', // in case legacy default log groups have been defined in the ConfigEditor, they will be migrated in the LogGroupsField component or the next time the ConfigEditor is opened. // the migration requires async backend calls, so we don't want to do it here as it would block the UI. logGroupNames: legacyDefaultLogGroups,