mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 15:45:43 -06:00
Tempo: NativeSearch code simplification and cleanup (#49365)
* Cleanup and show loading for service/span name * Method rename * Simplify logic for getting options * Simplify logic for setting/getting value * Move getTags to own async * Update tests * Promise all * Update const name
This commit is contained in:
parent
ac8951f689
commit
d463c3157c
@ -35,6 +35,7 @@ const mockQuery = {
|
||||
queryType: 'nativeSearch',
|
||||
key: 'Q-595a9bbc-2a25-49a7-9249-a52a0a475d83-0',
|
||||
serviceName: 'driver',
|
||||
spanName: 'customer',
|
||||
} as TempoQuery;
|
||||
|
||||
describe('NativeSearch', () => {
|
||||
@ -56,9 +57,9 @@ describe('NativeSearch', () => {
|
||||
<NativeSearch datasource={{} as TempoDatasource} query={mockQuery} onChange={jest.fn()} onRunQuery={jest.fn()} />
|
||||
);
|
||||
|
||||
const asyncServiceSelect = screen.getByRole('combobox', { name: 'select-span-name' });
|
||||
const select = screen.getByRole('combobox', { name: 'select-service-name' });
|
||||
|
||||
await user.click(asyncServiceSelect);
|
||||
await user.click(select);
|
||||
const loader = screen.getByText('Loading options...');
|
||||
|
||||
expect(loader).toBeInTheDocument();
|
||||
@ -76,7 +77,7 @@ describe('NativeSearch', () => {
|
||||
queryType: 'nativeSearch',
|
||||
refId: 'A',
|
||||
serviceName: 'driver',
|
||||
spanName: 'driver',
|
||||
spanName: 'customer',
|
||||
};
|
||||
|
||||
render(
|
||||
@ -88,12 +89,13 @@ describe('NativeSearch', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const asyncServiceSelect = await screen.findByRole('combobox', { name: 'select-span-name' });
|
||||
const select = await screen.findByRole('combobox', { name: 'select-service-name' });
|
||||
|
||||
expect(asyncServiceSelect).toBeInTheDocument();
|
||||
await user.click(asyncServiceSelect);
|
||||
expect(select).toBeInTheDocument();
|
||||
await user.click(select);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
await user.type(select, 'd');
|
||||
const driverOption = await screen.findByText('driver');
|
||||
await user.click(driverOption);
|
||||
|
||||
@ -105,19 +107,16 @@ describe('NativeSearch', () => {
|
||||
<NativeSearch datasource={{} as TempoDatasource} query={mockQuery} onChange={() => {}} onRunQuery={() => {}} />
|
||||
);
|
||||
|
||||
const asyncServiceSelect = await screen.findByRole('combobox', { name: 'select-span-name' });
|
||||
|
||||
expect(asyncServiceSelect).toBeInTheDocument();
|
||||
await user.click(asyncServiceSelect);
|
||||
const select = await screen.findByRole('combobox', { name: 'select-service-name' });
|
||||
await user.click(select);
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(select).toBeInTheDocument();
|
||||
|
||||
await user.type(asyncServiceSelect, 'd');
|
||||
jest.advanceTimersByTime(1000);
|
||||
await user.type(select, 'd');
|
||||
var option = await screen.findByText('driver');
|
||||
expect(option).toBeDefined();
|
||||
|
||||
await user.type(asyncServiceSelect, 'a');
|
||||
jest.advanceTimersByTime(1000);
|
||||
await user.type(select, 'a');
|
||||
option = await screen.findByText('No options found');
|
||||
expect(option).toBeDefined();
|
||||
});
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { debounce } from 'lodash';
|
||||
import Prism from 'prismjs';
|
||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { Node } from 'slate';
|
||||
@ -15,10 +14,10 @@ import {
|
||||
BracesPlugin,
|
||||
TypeaheadInput,
|
||||
TypeaheadOutput,
|
||||
AsyncSelect,
|
||||
Alert,
|
||||
useStyles2,
|
||||
fuzzyMatch,
|
||||
Select,
|
||||
} from '@grafana/ui';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||
@ -52,12 +51,8 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
|
||||
const styles = useStyles2(getStyles);
|
||||
const languageProvider = useMemo(() => new TempoLanguageProvider(datasource), [datasource]);
|
||||
const [hasSyntaxLoaded, setHasSyntaxLoaded] = useState(false);
|
||||
const [asyncServiceNameValue, setAsyncServiceNameValue] = useState<SelectableValue<any>>({
|
||||
value: '',
|
||||
});
|
||||
const [asyncSpanNameValue, setAsyncSpanNameValue] = useState<SelectableValue<any>>({
|
||||
value: '',
|
||||
});
|
||||
const [serviceOptions, setServiceOptions] = useState<Array<SelectableValue<string>>>();
|
||||
const [spanOptions, setSpanOptions] = useState<Array<SelectableValue<string>>>();
|
||||
const [error, setError] = useState(null);
|
||||
const [inputErrors, setInputErrors] = useState<{ [key: string]: boolean }>({});
|
||||
const [isLoading, setIsLoading] = useState<{
|
||||
@ -68,60 +63,35 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
|
||||
spanName: false,
|
||||
});
|
||||
|
||||
async function fetchOptionsCallback(name: string, lp: TempoLanguageProvider, query = '') {
|
||||
try {
|
||||
setIsLoading((prevValue) => ({ ...prevValue, [name]: false }));
|
||||
const options = await lp.getOptions(name);
|
||||
const filteredOptions = options.filter((item) => (item.value ? fuzzyMatch(item.value, query).found : false));
|
||||
return filteredOptions;
|
||||
} catch (error) {
|
||||
if (error?.status === 404) {
|
||||
setIsLoading((prevValue) => ({ ...prevValue, [name]: false }));
|
||||
} else {
|
||||
dispatch(notifyApp(createErrorNotification('Error', error)));
|
||||
const loadOptions = useCallback(
|
||||
async (name: string, query = '') => {
|
||||
const lpName = name === 'serviceName' ? 'service.name' : 'name';
|
||||
setIsLoading((prevValue) => ({ ...prevValue, [name]: true }));
|
||||
|
||||
try {
|
||||
const options = await languageProvider.getOptions(lpName);
|
||||
const filteredOptions = options.filter((item) => (item.value ? fuzzyMatch(item.value, query).found : false));
|
||||
return filteredOptions;
|
||||
} catch (error) {
|
||||
if (error?.status === 404) {
|
||||
setError(error);
|
||||
} else {
|
||||
dispatch(notifyApp(createErrorNotification('Error', error)));
|
||||
}
|
||||
return [];
|
||||
} finally {
|
||||
setIsLoading((prevValue) => ({ ...prevValue, [name]: false }));
|
||||
}
|
||||
setError(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const loadOptionsOfType = useCallback(
|
||||
(name: string) => {
|
||||
setIsLoading((prevValue) => ({ ...prevValue, [name]: true }));
|
||||
return fetchOptionsCallback(name, languageProvider);
|
||||
},
|
||||
[languageProvider]
|
||||
);
|
||||
|
||||
const fetchOptionsOfType = useCallback(
|
||||
(name: string) => debounce(() => loadOptionsOfType(name), 500, { leading: true, trailing: true }),
|
||||
[loadOptionsOfType]
|
||||
);
|
||||
|
||||
const getNameOptions = (query: string, name: string) => {
|
||||
setIsLoading((prevValue) => ({ ...prevValue, [name]: true }));
|
||||
return fetchOptionsCallback(name, languageProvider, query);
|
||||
};
|
||||
|
||||
const getServiceNameOptions = (query: string) => {
|
||||
return getNameOptions(query, 'service.name');
|
||||
};
|
||||
|
||||
const getSpanNameOptions = (query: string) => {
|
||||
return getNameOptions(query, 'name');
|
||||
};
|
||||
|
||||
const serviceNameSearch = debounce(getServiceNameOptions, 500, { leading: true, trailing: true });
|
||||
const spanNameSearch = debounce(getSpanNameOptions, 500, { leading: true, trailing: true });
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOptions = async () => {
|
||||
try {
|
||||
await languageProvider.start();
|
||||
fetchOptionsCallback('service.name', languageProvider);
|
||||
fetchOptionsCallback('name', languageProvider);
|
||||
setHasSyntaxLoaded(true);
|
||||
const [services, spans] = await Promise.all([loadOptions('serviceName'), loadOptions('spanName')]);
|
||||
setServiceOptions(services);
|
||||
setSpanOptions(spans);
|
||||
} catch (error) {
|
||||
// Display message if Tempo is connected but search 404's
|
||||
if (error?.status === 404) {
|
||||
@ -129,11 +99,22 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
|
||||
} else {
|
||||
dispatch(notifyApp(createErrorNotification('Error', error)));
|
||||
}
|
||||
setHasSyntaxLoaded(true);
|
||||
}
|
||||
};
|
||||
fetchOptions();
|
||||
}, [languageProvider, fetchOptionsOfType]);
|
||||
}, [languageProvider, loadOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTags = async () => {
|
||||
try {
|
||||
await languageProvider.start();
|
||||
setHasSyntaxLoaded(true);
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification('Error', error)));
|
||||
}
|
||||
};
|
||||
fetchTags();
|
||||
}, [languageProvider]);
|
||||
|
||||
const onTypeahead = async (typeahead: TypeaheadInput): Promise<TypeaheadOutput> => {
|
||||
return await languageProvider.provideCompletionItems(typeahead);
|
||||
@ -160,17 +141,15 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
|
||||
<div className={styles.container}>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Service Name" labelWidth={14} grow>
|
||||
<AsyncSelect
|
||||
<Select
|
||||
inputId="service"
|
||||
cacheOptions={false}
|
||||
loadOptions={serviceNameSearch}
|
||||
onOpenMenu={fetchOptionsOfType('service.name')}
|
||||
options={serviceOptions}
|
||||
onOpenMenu={() => {
|
||||
loadOptions('serviceName');
|
||||
}}
|
||||
isLoading={isLoading.serviceName}
|
||||
value={asyncServiceNameValue.value}
|
||||
value={serviceOptions?.find((v) => v?.value === query.serviceName) || undefined}
|
||||
onChange={(v) => {
|
||||
setAsyncServiceNameValue({
|
||||
value: v,
|
||||
});
|
||||
onChange({
|
||||
...query,
|
||||
serviceName: v?.value || undefined,
|
||||
@ -178,7 +157,6 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
|
||||
}}
|
||||
placeholder="Select a service"
|
||||
isClearable
|
||||
defaultOptions
|
||||
onKeyDown={onKeyDown}
|
||||
aria-label={'select-service-name'}
|
||||
/>
|
||||
@ -186,15 +164,15 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Span Name" labelWidth={14} grow>
|
||||
<AsyncSelect
|
||||
<Select
|
||||
inputId="spanName"
|
||||
cacheOptions={false}
|
||||
loadOptions={spanNameSearch}
|
||||
onOpenMenu={fetchOptionsOfType('name')}
|
||||
options={spanOptions}
|
||||
onOpenMenu={() => {
|
||||
loadOptions('spanName');
|
||||
}}
|
||||
isLoading={isLoading.spanName}
|
||||
value={asyncSpanNameValue.value}
|
||||
value={spanOptions?.find((v) => v?.value === query.spanName) || undefined}
|
||||
onChange={(v) => {
|
||||
setAsyncSpanNameValue({ value: v });
|
||||
onChange({
|
||||
...query,
|
||||
spanName: v?.value || undefined,
|
||||
@ -202,7 +180,6 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
|
||||
}}
|
||||
placeholder="Select a span"
|
||||
isClearable
|
||||
defaultOptions
|
||||
onKeyDown={onKeyDown}
|
||||
aria-label={'select-span-name'}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user