CloudWatch Logs: Set default logs query and disable button when empty (#61956)

This commit is contained in:
Isabella Siu 2023-01-25 09:39:42 -05:00 committed by GitHub
parent e46aa2e4e6
commit 7c85db5bfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 103 additions and 7 deletions

View File

@ -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(
<QueryHeader
datasource={datasource}
query={{ ...validMetricSearchBuilderQuery, expression: 'foo' }}
onChange={onChange}
onRunQuery={jest.fn()}
dataIsStale={false}
/>
);
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(
<QueryHeader
datasource={datasource}
query={{ ...validMetricSearchBuilderQuery, queryMode: 'Logs', expression: 'foo' }}
onChange={onChange}
onRunQuery={jest.fn()}
dataIsStale={false}
/>
);
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(
<QueryHeader
datasource={datasource}
query={{ ...validMetricSearchBuilderQuery, queryMode: 'Logs', expression: '' }}
onChange={onChange}
onRunQuery={jest.fn()}
dataIsStale={false}
/>
);
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(
<QueryHeader
datasource={datasource}
query={{ ...validMetricSearchBuilderQuery, queryMode: 'Logs', expression: DEFAULT_LOGS_QUERY_STRING }}
onChange={onChange}
onRunQuery={jest.fn()}
dataIsStale={false}
/>
);
expect(await screen.findByText('Run queries')).toBeInTheDocument();
expect(screen.getByText('Run queries').closest('button')).not.toBeDisabled();
});
});
});

View File

@ -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<Props> = ({
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<CloudWatchQueryMode>) => {
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<Props> = ({
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
</Button>

View File

@ -16,13 +16,14 @@ export const DEFAULT_METRICS_QUERY: Omit<CloudWatchMetricsQuery, 'refId'> = {
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<CloudWatchLogsQuery, 'refId' | 'queryMode'> => ({
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,