Commit 0e8638ec by kay delaney Committed by GitHub

Datasource/CloudWatch: Allows a user to search for log groups that aren't there initially (#24695)

Closes #24554
parent bc8c0513
......@@ -2,6 +2,9 @@ import { SelectableValue } from '@grafana/data';
import React from 'react';
export type SelectValue<T> = T | SelectableValue<T> | T[] | Array<SelectableValue<T>>;
export type InputActionMeta = {
action: 'set-value' | 'input-change' | 'input-blur' | 'menu-close';
};
export interface SelectCommonProps<T> {
allowCustomValue?: boolean;
......@@ -39,7 +42,7 @@ export interface SelectCommonProps<T> {
onCloseMenu?: () => void;
/** allowCustomValue must be enabled. Function decides what to do with that custom value. */
onCreateOption?: (value: string) => void;
onInputChange?: (label: string) => void;
onInputChange?: (value: string, actionMeta: InputActionMeta) => void;
onKeyDown?: (event: React.KeyboardEvent) => void;
onOpenMenu?: () => void;
openMenuOnFocus?: boolean;
......
......@@ -2,6 +2,13 @@ import React from 'react';
import { shallow } from 'enzyme';
import { CloudWatchLogsQueryField } from './LogsQueryField';
import { ExploreId } from '../../../../types';
import { DescribeLogGroupsRequest } from '../types';
import { SelectableValue } from '@grafana/data';
jest.mock('lodash/debounce', () => {
const fakeDebounce = (func: () => any, period: number) => func;
return fakeDebounce;
});
describe('CloudWatchLogsQueryField', () => {
it('updates upstream query log groups on region change', async () => {
......@@ -58,4 +65,151 @@ describe('CloudWatchLogsQueryField', () => {
// Make sure we correctly updated the upstream state
expect(onChange.mock.calls[onChange.mock.calls.length - 1][0]).toEqual({ region: 'region2', logGroupNames: [] });
});
it('should merge results of remote log groups search with existing results', async () => {
const allLogGroups = [
'AmazingGroup',
'AmazingGroup2',
'AmazingGroup3',
'BeautifulGroup',
'BeautifulGroup2',
'BeautifulGroup3',
'CrazyGroup',
'CrazyGroup2',
'CrazyGroup3',
'DeliciousGroup',
'DeliciousGroup2',
'DeliciousGroup3',
'EnjoyableGroup',
'EnjoyableGroup2',
'EnjoyableGroup3',
'FavouriteGroup',
'FavouriteGroup2',
'FavouriteGroup3',
'GorgeousGroup',
'GorgeousGroup2',
'GorgeousGroup3',
'HappyGroup',
'HappyGroup2',
'HappyGroup3',
'IncredibleGroup',
'IncredibleGroup2',
'IncredibleGroup3',
'JollyGroup',
'JollyGroup2',
'JollyGroup3',
'KoolGroup',
'KoolGroup2',
'KoolGroup3',
'LovelyGroup',
'LovelyGroup2',
'LovelyGroup3',
'MagnificentGroup',
'MagnificentGroup2',
'MagnificentGroup3',
'NiceGroup',
'NiceGroup2',
'NiceGroup3',
'OddGroup',
'OddGroup2',
'OddGroup3',
'PerfectGroup',
'PerfectGroup2',
'PerfectGroup3',
'QuietGroup',
'QuietGroup2',
'QuietGroup3',
'RestlessGroup',
'RestlessGroup2',
'RestlessGroup3',
'SurpriseGroup',
'SurpriseGroup2',
'SurpriseGroup3',
'TestingGroup',
'TestingGroup2',
'TestingGroup3',
'UmbrellaGroup',
'UmbrellaGroup2',
'UmbrellaGroup3',
'VelvetGroup',
'VelvetGroup2',
'VelvetGroup3',
'WaterGroup',
'WaterGroup2',
'WaterGroup3',
'XylophoneGroup',
'XylophoneGroup2',
'XylophoneGroup3',
'YellowGroup',
'YellowGroup2',
'YellowGroup3',
'ZebraGroup',
'ZebraGroup2',
'ZebraGroup3',
];
const onChange = jest.fn();
const wrapper = shallow<CloudWatchLogsQueryField>(
<CloudWatchLogsQueryField
history={[]}
absoluteRange={{ from: 1, to: 10 }}
syntaxLoaded={false}
syntax={{} as any}
exploreId={ExploreId.left}
datasource={
{
getRegions() {
return Promise.resolve([
{
label: 'region1',
value: 'region1',
text: 'region1',
},
{
label: 'region2',
value: 'region2',
text: 'region2',
},
]);
},
describeLogGroups(params: DescribeLogGroupsRequest) {
const theLogGroups = allLogGroups
.filter(logGroupName => logGroupName.startsWith(params.logGroupNamePrefix ?? ''))
.slice(0, Math.max(params.limit ?? 50, 50));
return Promise.resolve(theLogGroups);
},
} as any
}
query={{} as any}
onRunQuery={() => {}}
onChange={onChange}
/>
);
const initialAvailableGroups = allLogGroups
.slice(0, 50)
.map(logGroupName => ({ value: logGroupName, label: logGroupName }));
wrapper.setState({
availableLogGroups: initialAvailableGroups,
});
await wrapper.instance().onLogGroupSearch('Water', 'default', { action: 'input-change' });
let nextAvailableGroups = (wrapper.state('availableLogGroups') as Array<SelectableValue<string>>).map(
logGroup => logGroup.value
);
expect(nextAvailableGroups).toEqual(
initialAvailableGroups.map(logGroup => logGroup.value).concat(['WaterGroup', 'WaterGroup2', 'WaterGroup3'])
);
await wrapper.instance().onLogGroupSearch('Velv', 'default', { action: 'input-change' });
nextAvailableGroups = (wrapper.state('availableLogGroups') as Array<SelectableValue<string>>).map(
logGroup => logGroup.value
);
expect(nextAvailableGroups).toEqual(
initialAvailableGroups
.map(logGroup => logGroup.value)
.concat(['WaterGroup', 'WaterGroup2', 'WaterGroup3', 'VelvetGroup', 'VelvetGroup2', 'VelvetGroup3'])
);
});
});
......@@ -2,6 +2,7 @@
import React, { ReactNode } from 'react';
import intersectionBy from 'lodash/intersectionBy';
import debounce from 'lodash/debounce';
import unionBy from 'lodash/unionBy';
import {
QueryField,
......@@ -31,6 +32,7 @@ import { ExploreId } from 'app/types';
import { dispatch } from 'app/store/store';
import { changeModeAction } from 'app/features/explore/state/actionTypes';
import { appEvents } from 'app/core/core';
import { InputActionMeta } from '@grafana/ui/src/components/Select/types';
export interface CloudWatchLogsQueryFieldProps extends ExploreQueryFieldProps<CloudWatchDatasource, CloudWatchQuery> {
absoluteRange: AbsoluteTimeRange;
......@@ -104,11 +106,12 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
];
}
fetchLogGroupOptions = async (region: string) => {
fetchLogGroupOptions = async (region: string, logGroupNamePrefix?: string) => {
try {
const logGroups: string[] = await this.props.datasource.describeLogGroups({
refId: this.props.query.refId,
region,
logGroupNamePrefix,
});
return logGroups.map(logGroup => ({
......@@ -121,6 +124,30 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
}
};
onLogGroupSearch = (searchTerm: string, region: string, actionMeta: InputActionMeta) => {
if (actionMeta.action !== 'input-change') {
return Promise.resolve();
}
this.setState({
loadingLogGroups: true,
});
return this.fetchLogGroupOptions(region, searchTerm)
.then(matchingLogGroups => {
this.setState(state => ({
availableLogGroups: unionBy(state.availableLogGroups, matchingLogGroups, 'value'),
}));
})
.finally(() => {
this.setState({
loadingLogGroups: false,
});
});
};
onLogGroupSearchDebounced = debounce(this.onLogGroupSearch, 300);
componentWillMount = () => {
const { datasource, query, onChange } = this.props;
......@@ -355,6 +382,9 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
noOptionsMessage="No log groups available"
isLoading={loadingLogGroups}
onOpenMenu={this.onOpenLogGroupMenu}
onInputChange={(value, actionMeta) => {
this.onLogGroupSearchDebounced(value, selectedRegion.value ?? 'default', actionMeta);
}}
/>
}
/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment