diff --git a/public/app/plugins/datasource/tempo/ConfigEditor.tsx b/public/app/plugins/datasource/tempo/ConfigEditor.tsx index 36fb8d59bdd..deb596c20e5 100644 --- a/public/app/plugins/datasource/tempo/ConfigEditor.tsx +++ b/public/app/plugins/datasource/tempo/ConfigEditor.tsx @@ -4,6 +4,7 @@ import { TraceToLogsSettings } from 'app/core/components/TraceToLogsSettings'; import React from 'react'; import { ServiceMapSettings } from './ServiceMapSettings'; import { config } from '@grafana/runtime'; +import { SearchSettings } from './SearchSettings'; export type Props = DataSourcePluginOptionsEditorProps; @@ -21,7 +22,14 @@ export const ConfigEditor: React.FC = ({ options, onOptionsChange }) => { {config.featureToggles.tempoServiceGraph && ( - +
+ +
+ )} + {config.featureToggles.tempoSearch && ( +
+ +
)} ); diff --git a/public/app/plugins/datasource/tempo/NativeSearch.tsx b/public/app/plugins/datasource/tempo/NativeSearch.tsx index b7396b9c913..663ccd284ad 100644 --- a/public/app/plugins/datasource/tempo/NativeSearch.tsx +++ b/public/app/plugins/datasource/tempo/NativeSearch.tsx @@ -9,15 +9,20 @@ import { TypeaheadInput, TypeaheadOutput, Select, + Alert, + useStyles2, } from '@grafana/ui'; import { tokenizer } from './syntax'; import Prism from 'prismjs'; import { Node } from 'slate'; import { css } from '@emotion/css'; -import { SelectableValue } from '@grafana/data'; +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import TempoLanguageProvider from './language_provider'; import { TempoDatasource, TempoQuery } from './datasource'; import { debounce } from 'lodash'; +import { dispatch } from 'app/store/store'; +import { notifyApp } from 'app/core/actions'; +import { createErrorNotification } from 'app/core/copy/appNotification'; interface Props { datasource: TempoDatasource; @@ -40,19 +45,17 @@ const plugins = [ Prism.languages[PRISM_LANGUAGE] = tokenizer; 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 [autocomplete, setAutocomplete] = useState<{ serviceNameOptions: Array>; - selectedServiceName: SelectableValue | undefined; spanNameOptions: Array>; - selectedSpanName: SelectableValue | undefined; }>({ serviceNameOptions: [], - selectedServiceName: undefined, spanNameOptions: [], - selectedSpanName: undefined, }); + const [error, setError] = useState(null); const fetchServiceNameOptions = useMemo( () => @@ -82,10 +85,20 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props useEffect(() => { const fetchAutocomplete = async () => { - await languageProvider.start(); - await fetchServiceNameOptions(); - await fetchSpanNameOptions(); - setHasSyntaxLoaded(true); + try { + await languageProvider.start(); + const serviceNameOptions = await languageProvider.getOptions('service.name'); + const spanNameOptions = await languageProvider.getOptions('name'); + setHasSyntaxLoaded(true); + setAutocomplete({ serviceNameOptions, spanNameOptions }); + } catch (error) { + // Display message if Tempo is connected but search 404's + if (error?.status === 404) { + setError(error); + } else { + dispatch(notifyApp(createErrorNotification('Error', error))); + } + } }; fetchAutocomplete(); }, [languageProvider, fetchServiceNameOptions, fetchSpanNameOptions]); @@ -109,111 +122,129 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props }; return ( -
- - - { - onChange({ - ...query, - spanName: v?.value || undefined, - }); - }} - placeholder="Select a span" - onOpenMenu={fetchSpanNameOptions} - isClearable - /> - - - - - { - onChange({ - ...query, - search: value, - }); - }} - placeholder="http.status_code=200 error=true" - cleanText={cleanText} - onRunQuery={onRunQuery} - syntaxLoaded={hasSyntaxLoaded} - portalOrigin="tempo" - /> - - - - - - onChange({ - ...query, - minDuration: v.currentTarget.value, - }) - } - onKeyDown={onKeyDown} - /> - - - - - - onChange({ - ...query, - maxDuration: v.currentTarget.value, - }) - } - onKeyDown={onKeyDown} - /> - - - - - - onChange({ - ...query, - limit: v.currentTarget.value ? parseInt(v.currentTarget.value, 10) : undefined, - }) - } - onKeyDown={onKeyDown} - /> - - -
+ <> +
+ + + { + onChange({ + ...query, + spanName: v?.value || undefined, + }); + }} + placeholder="Select a span" + onOpenMenu={fetchSpanNameOptions} + isClearable + /> + + + + + { + onChange({ + ...query, + search: value, + }); + }} + placeholder="http.status_code=200 error=true" + cleanText={cleanText} + onRunQuery={onRunQuery} + syntaxLoaded={hasSyntaxLoaded} + portalOrigin="tempo" + /> + + + + + + onChange({ + ...query, + minDuration: v.currentTarget.value, + }) + } + onKeyDown={onKeyDown} + /> + + + + + + onChange({ + ...query, + maxDuration: v.currentTarget.value, + }) + } + onKeyDown={onKeyDown} + /> + + + + + + onChange({ + ...query, + limit: v.currentTarget.value ? parseInt(v.currentTarget.value, 10) : undefined, + }) + } + onKeyDown={onKeyDown} + /> + + +
+ {error ? ( + + Please ensure that Tempo is configured with search enabled. If you would like to hide this tab, you can + configure it in the datasource settings. + + ) : null} + ); }; export default NativeSearch; + +const getStyles = (theme: GrafanaTheme2) => ({ + container: css` + max-width: 500px; + `, + alert: css` + max-width: 75ch; + margin-top: ${theme.spacing(2)}; + `, +}); diff --git a/public/app/plugins/datasource/tempo/QueryField.tsx b/public/app/plugins/datasource/tempo/QueryField.tsx index aa7dfa3d132..29ab4cf2a28 100644 --- a/public/app/plugins/datasource/tempo/QueryField.tsx +++ b/public/app/plugins/datasource/tempo/QueryField.tsx @@ -99,8 +99,8 @@ class TempoQueryFieldComponent extends React.PureComponent { queryTypeOptions.push({ value: 'serviceMap', label: 'Service Map' }); } - if (config.featureToggles.tempoSearch) { - queryTypeOptions.unshift({ value: 'nativeSearch', label: 'Search' }); + if (config.featureToggles.tempoSearch && !datasource?.search?.hide) { + queryTypeOptions.unshift({ value: 'nativeSearch', label: 'Search - Beta' }); } if (logsDatasourceUid) { diff --git a/public/app/plugins/datasource/tempo/SearchSettings.tsx b/public/app/plugins/datasource/tempo/SearchSettings.tsx new file mode 100644 index 00000000000..6ea9e3df021 --- /dev/null +++ b/public/app/plugins/datasource/tempo/SearchSettings.tsx @@ -0,0 +1,41 @@ +import { css } from '@emotion/css'; +import { DataSourcePluginOptionsEditorProps, GrafanaTheme, updateDatasourcePluginJsonDataOption } from '@grafana/data'; +import { InlineField, InlineFieldRow, InlineSwitch, useStyles } from '@grafana/ui'; +import React from 'react'; +import { TempoJsonData } from './datasource'; + +interface Props extends DataSourcePluginOptionsEditorProps {} + +export function SearchSettings({ options, onOptionsChange }: Props) { + const styles = useStyles(getStyles); + + return ( +
+

Search

+ + + ) => + updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'search', { + ...options.jsonData.search, + hide: event.currentTarget.checked, + }) + } + /> + + +
+ ); +} + +const getStyles = (theme: GrafanaTheme) => ({ + container: css` + label: container; + width: 100%; + `, + row: css` + label: row; + align-items: baseline; + `, +}); diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index 67341f709e0..486bb7e8b2b 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -35,6 +35,9 @@ export interface TempoJsonData extends DataSourceJsonData { serviceMap?: { datasourceUid?: string; }; + search?: { + hide?: boolean; + }; } export type TempoQuery = { @@ -55,12 +58,16 @@ export class TempoDatasource extends DataSourceWithBackend) { super(instanceSettings); this.tracesToLogs = instanceSettings.jsonData.tracesToLogs; this.serviceMap = instanceSettings.jsonData.serviceMap; + this.search = instanceSettings.jsonData.search; } query(options: DataQueryRequest): Observable { diff --git a/public/app/plugins/datasource/tempo/language_provider.ts b/public/app/plugins/datasource/tempo/language_provider.ts index 8e02843af69..556c51e9114 100644 --- a/public/app/plugins/datasource/tempo/language_provider.ts +++ b/public/app/plugins/datasource/tempo/language_provider.ts @@ -13,15 +13,9 @@ export default class TempoLanguageProvider extends LanguageProvider { Object.assign(this, initialValues); } - request = async (url: string, defaultValue: any, params = {}) => { - try { - const res = await this.datasource.metadataRequest(url, params); - return res?.data; - } catch (error) { - console.error(error); - } - - return defaultValue; + request = async (url: string, params = {}) => { + const res = await this.datasource.metadataRequest(url, params); + return res?.data; }; start = async () => { @@ -30,12 +24,8 @@ export default class TempoLanguageProvider extends LanguageProvider { }; async fetchTags() { - try { - const response = await this.request('/api/search/tags', []); - this.tags = response.tagNames; - } catch (error) { - console.error(error); - } + const response = await this.request('/api/search/tags', []); + this.tags = response.tagNames; } provideCompletionItems = async ( @@ -88,7 +78,7 @@ export default class TempoLanguageProvider extends LanguageProvider { } async getOptions(tag: string): Promise>> { - const response = await this.request(`/api/search/tag/${tag}/values`, []); + const response = await this.request(`/api/search/tag/${tag}/values`); let options: Array> = []; if (response && response.tagValues) {