mirror of
https://github.com/grafana/grafana.git
synced 2025-01-27 16:57:14 -06:00
Tempo: Show warning if search is not available. Add opt-out config (#39489)
* Tempo: Show warning if search is not available. Add opt-out config
This commit is contained in:
parent
11e362ff0a
commit
24479ff6b0
@ -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<Props> = ({ options, onOptionsChange }) => {
|
||||
<TraceToLogsSettings options={options} onOptionsChange={onOptionsChange} />
|
||||
</div>
|
||||
{config.featureToggles.tempoServiceGraph && (
|
||||
<ServiceMapSettings options={options} onOptionsChange={onOptionsChange} />
|
||||
<div className="gf-form-group">
|
||||
<ServiceMapSettings options={options} onOptionsChange={onOptionsChange} />
|
||||
</div>
|
||||
)}
|
||||
{config.featureToggles.tempoSearch && (
|
||||
<div className="gf-form-group">
|
||||
<SearchSettings options={options} onOptionsChange={onOptionsChange} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -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<SelectableValue<string>>;
|
||||
selectedServiceName: SelectableValue<string> | undefined;
|
||||
spanNameOptions: Array<SelectableValue<string>>;
|
||||
selectedSpanName: SelectableValue<string> | 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 (
|
||||
<div className={css({ maxWidth: '500px' })}>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Service Name" labelWidth={14} grow>
|
||||
<Select
|
||||
menuShouldPortal
|
||||
options={autocomplete.serviceNameOptions}
|
||||
value={query.serviceName || ''}
|
||||
onChange={(v) => {
|
||||
onChange({
|
||||
...query,
|
||||
serviceName: v?.value || undefined,
|
||||
});
|
||||
}}
|
||||
placeholder="Select a service"
|
||||
onOpenMenu={fetchServiceNameOptions}
|
||||
isClearable
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Span Name" labelWidth={14} grow>
|
||||
<Select
|
||||
menuShouldPortal
|
||||
options={autocomplete.spanNameOptions}
|
||||
value={query.spanName || ''}
|
||||
onChange={(v) => {
|
||||
onChange({
|
||||
...query,
|
||||
spanName: v?.value || undefined,
|
||||
});
|
||||
}}
|
||||
placeholder="Select a span"
|
||||
onOpenMenu={fetchSpanNameOptions}
|
||||
isClearable
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Tags" labelWidth={14} grow tooltip="Values should be in the logfmt format.">
|
||||
<QueryField
|
||||
additionalPlugins={plugins}
|
||||
query={query.search}
|
||||
onTypeahead={onTypeahead}
|
||||
onBlur={onBlur}
|
||||
onChange={(value) => {
|
||||
onChange({
|
||||
...query,
|
||||
search: value,
|
||||
});
|
||||
}}
|
||||
placeholder="http.status_code=200 error=true"
|
||||
cleanText={cleanText}
|
||||
onRunQuery={onRunQuery}
|
||||
syntaxLoaded={hasSyntaxLoaded}
|
||||
portalOrigin="tempo"
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Min Duration" labelWidth={14} grow>
|
||||
<Input
|
||||
value={query.minDuration || ''}
|
||||
placeholder={durationPlaceholder}
|
||||
onChange={(v) =>
|
||||
onChange({
|
||||
...query,
|
||||
minDuration: v.currentTarget.value,
|
||||
})
|
||||
}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Max Duration" labelWidth={14} grow>
|
||||
<Input
|
||||
value={query.maxDuration || ''}
|
||||
placeholder={durationPlaceholder}
|
||||
onChange={(v) =>
|
||||
onChange({
|
||||
...query,
|
||||
maxDuration: v.currentTarget.value,
|
||||
})
|
||||
}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Limit" labelWidth={14} grow tooltip="Maximum numbers of returned results">
|
||||
<Input
|
||||
value={query.limit || ''}
|
||||
type="number"
|
||||
onChange={(v) =>
|
||||
onChange({
|
||||
...query,
|
||||
limit: v.currentTarget.value ? parseInt(v.currentTarget.value, 10) : undefined,
|
||||
})
|
||||
}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
</div>
|
||||
<>
|
||||
<div className={styles.container}>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Service Name" labelWidth={14} grow>
|
||||
<Select
|
||||
menuShouldPortal
|
||||
options={autocomplete.serviceNameOptions}
|
||||
value={query.serviceName || ''}
|
||||
onChange={(v) => {
|
||||
onChange({
|
||||
...query,
|
||||
serviceName: v?.value || undefined,
|
||||
});
|
||||
}}
|
||||
placeholder="Select a service"
|
||||
onOpenMenu={fetchServiceNameOptions}
|
||||
isClearable
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Span Name" labelWidth={14} grow>
|
||||
<Select
|
||||
menuShouldPortal
|
||||
options={autocomplete.spanNameOptions}
|
||||
value={query.spanName || ''}
|
||||
onChange={(v) => {
|
||||
onChange({
|
||||
...query,
|
||||
spanName: v?.value || undefined,
|
||||
});
|
||||
}}
|
||||
placeholder="Select a span"
|
||||
onOpenMenu={fetchSpanNameOptions}
|
||||
isClearable
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Tags" labelWidth={14} grow tooltip="Values should be in the logfmt format.">
|
||||
<QueryField
|
||||
additionalPlugins={plugins}
|
||||
query={query.search}
|
||||
onTypeahead={onTypeahead}
|
||||
onBlur={onBlur}
|
||||
onChange={(value) => {
|
||||
onChange({
|
||||
...query,
|
||||
search: value,
|
||||
});
|
||||
}}
|
||||
placeholder="http.status_code=200 error=true"
|
||||
cleanText={cleanText}
|
||||
onRunQuery={onRunQuery}
|
||||
syntaxLoaded={hasSyntaxLoaded}
|
||||
portalOrigin="tempo"
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Min Duration" labelWidth={14} grow>
|
||||
<Input
|
||||
value={query.minDuration || ''}
|
||||
placeholder={durationPlaceholder}
|
||||
onChange={(v) =>
|
||||
onChange({
|
||||
...query,
|
||||
minDuration: v.currentTarget.value,
|
||||
})
|
||||
}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Max Duration" labelWidth={14} grow>
|
||||
<Input
|
||||
value={query.maxDuration || ''}
|
||||
placeholder={durationPlaceholder}
|
||||
onChange={(v) =>
|
||||
onChange({
|
||||
...query,
|
||||
maxDuration: v.currentTarget.value,
|
||||
})
|
||||
}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Limit" labelWidth={14} grow tooltip="Maximum numbers of returned results">
|
||||
<Input
|
||||
value={query.limit || ''}
|
||||
type="number"
|
||||
onChange={(v) =>
|
||||
onChange({
|
||||
...query,
|
||||
limit: v.currentTarget.value ? parseInt(v.currentTarget.value, 10) : undefined,
|
||||
})
|
||||
}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
</div>
|
||||
{error ? (
|
||||
<Alert title="Unable to connect to Tempo search" severity="info" className={styles.alert}>
|
||||
Please ensure that Tempo is configured with search enabled. If you would like to hide this tab, you can
|
||||
configure it in the <a href={`/datasources/${datasource.uid}`}>datasource settings</a>.
|
||||
</Alert>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NativeSearch;
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
container: css`
|
||||
max-width: 500px;
|
||||
`,
|
||||
alert: css`
|
||||
max-width: 75ch;
|
||||
margin-top: ${theme.spacing(2)};
|
||||
`,
|
||||
});
|
||||
|
@ -99,8 +99,8 @@ class TempoQueryFieldComponent extends React.PureComponent<Props, State> {
|
||||
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) {
|
||||
|
41
public/app/plugins/datasource/tempo/SearchSettings.tsx
Normal file
41
public/app/plugins/datasource/tempo/SearchSettings.tsx
Normal file
@ -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<TempoJsonData> {}
|
||||
|
||||
export function SearchSettings({ options, onOptionsChange }: Props) {
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<h3 className="page-heading">Search</h3>
|
||||
<InlineFieldRow className={styles.row}>
|
||||
<InlineField tooltip="Removes the Search tab from the Tempo query editor." label="Hide search" labelWidth={26}>
|
||||
<InlineSwitch
|
||||
value={options.jsonData.search?.hide}
|
||||
onChange={(event: React.SyntheticEvent<HTMLInputElement>) =>
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'search', {
|
||||
...options.jsonData.search,
|
||||
hide: event.currentTarget.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({
|
||||
container: css`
|
||||
label: container;
|
||||
width: 100%;
|
||||
`,
|
||||
row: css`
|
||||
label: row;
|
||||
align-items: baseline;
|
||||
`,
|
||||
});
|
@ -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<TempoQuery, TempoJson
|
||||
serviceMap?: {
|
||||
datasourceUid?: string;
|
||||
};
|
||||
search?: {
|
||||
hide?: boolean;
|
||||
};
|
||||
uploadedJson?: string | ArrayBuffer | null = null;
|
||||
|
||||
constructor(private instanceSettings: DataSourceInstanceSettings<TempoJsonData>) {
|
||||
super(instanceSettings);
|
||||
this.tracesToLogs = instanceSettings.jsonData.tracesToLogs;
|
||||
this.serviceMap = instanceSettings.jsonData.serviceMap;
|
||||
this.search = instanceSettings.jsonData.search;
|
||||
}
|
||||
|
||||
query(options: DataQueryRequest<TempoQuery>): Observable<DataQueryResponse> {
|
||||
|
@ -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<Array<SelectableValue<string>>> {
|
||||
const response = await this.request(`/api/search/tag/${tag}/values`, []);
|
||||
const response = await this.request(`/api/search/tag/${tag}/values`);
|
||||
let options: Array<SelectableValue<string>> = [];
|
||||
|
||||
if (response && response.tagValues) {
|
||||
|
Loading…
Reference in New Issue
Block a user