mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Datasource/CloudWatch: Allows a user to search for log groups that aren't there initially (#24695)
Closes #24554
This commit is contained in:
parent
bc8c05137b
commit
0e8638ec92
@ -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);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user