2021-08-05 15:13:44 +02:00
|
|
|
import { css } from '@emotion/css';
|
2021-08-17 15:48:29 +02:00
|
|
|
import { DataSourceApi, ExploreQueryFieldProps, SelectableValue } from '@grafana/data';
|
2021-02-02 10:17:38 +01:00
|
|
|
import { selectors } from '@grafana/e2e-selectors';
|
2021-08-17 15:48:29 +02:00
|
|
|
import { config, getDataSourceSrv } from '@grafana/runtime';
|
2021-08-05 15:13:44 +02:00
|
|
|
import {
|
|
|
|
|
FileDropzone,
|
|
|
|
|
InlineField,
|
|
|
|
|
InlineFieldRow,
|
|
|
|
|
InlineLabel,
|
|
|
|
|
LegacyForms,
|
|
|
|
|
RadioButtonGroup,
|
|
|
|
|
Themeable2,
|
|
|
|
|
withTheme2,
|
|
|
|
|
} from '@grafana/ui';
|
2021-05-10 17:12:19 +02:00
|
|
|
import { TraceToLogsOptions } from 'app/core/components/TraceToLogsSettings';
|
2021-02-02 10:17:38 +01:00
|
|
|
import React from 'react';
|
2021-05-10 17:12:19 +02:00
|
|
|
import { LokiQueryField } from '../loki/components/LokiQueryField';
|
2021-08-19 09:15:46 -06:00
|
|
|
import { LokiQuery } from '../loki/types';
|
2021-05-10 17:12:19 +02:00
|
|
|
import { TempoDatasource, TempoQuery, TempoQueryType } from './datasource';
|
2021-08-17 15:48:29 +02:00
|
|
|
import LokiDatasource from '../loki/datasource';
|
|
|
|
|
import { PrometheusDatasource } from '../prometheus/datasource';
|
|
|
|
|
import useAsync from 'react-use/lib/useAsync';
|
2021-08-19 09:15:46 -06:00
|
|
|
import NativeSearch from './NativeSearch';
|
2020-10-13 19:12:49 +02:00
|
|
|
|
2021-08-05 15:13:44 +02:00
|
|
|
interface Props extends ExploreQueryFieldProps<TempoDatasource, TempoQuery>, Themeable2 {}
|
|
|
|
|
|
2021-05-10 17:12:19 +02:00
|
|
|
const DEFAULT_QUERY_TYPE: TempoQueryType = 'traceId';
|
2021-08-17 15:48:29 +02:00
|
|
|
|
2021-05-10 17:12:19 +02:00
|
|
|
interface State {
|
2021-08-17 15:48:29 +02:00
|
|
|
linkedDatasourceUid?: string;
|
|
|
|
|
linkedDatasource?: LokiDatasource;
|
|
|
|
|
serviceMapDatasourceUid?: string;
|
|
|
|
|
serviceMapDatasource?: PrometheusDatasource;
|
2021-05-10 17:12:19 +02:00
|
|
|
}
|
2021-08-19 09:15:46 -06:00
|
|
|
|
2021-08-05 15:13:44 +02:00
|
|
|
class TempoQueryFieldComponent extends React.PureComponent<Props, State> {
|
2021-05-10 17:12:19 +02:00
|
|
|
state = {
|
2021-08-17 15:48:29 +02:00
|
|
|
linkedDatasourceUid: undefined,
|
2021-05-10 17:12:19 +02:00
|
|
|
linkedDatasource: undefined,
|
2021-08-17 15:48:29 +02:00
|
|
|
serviceMapDatasourceUid: undefined,
|
|
|
|
|
serviceMapDatasource: undefined,
|
2021-05-10 17:12:19 +02:00
|
|
|
};
|
2021-06-02 16:37:36 +02:00
|
|
|
|
2021-05-10 17:12:19 +02:00
|
|
|
constructor(props: Props) {
|
|
|
|
|
super(props);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async componentDidMount() {
|
|
|
|
|
const { datasource } = this.props;
|
|
|
|
|
// Find query field from linked datasource
|
|
|
|
|
const tracesToLogsOptions: TraceToLogsOptions = datasource.tracesToLogs || {};
|
|
|
|
|
const linkedDatasourceUid = tracesToLogsOptions.datasourceUid;
|
2021-08-17 15:48:29 +02:00
|
|
|
|
|
|
|
|
const serviceMapDsUid = datasource.serviceMap?.datasourceUid;
|
|
|
|
|
|
|
|
|
|
// Check status of linked data sources so we can show warnings if needed.
|
|
|
|
|
const [logsDs, serviceMapDs] = await Promise.all([getDS(linkedDatasourceUid), getDS(serviceMapDsUid)]);
|
|
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
linkedDatasourceUid: linkedDatasourceUid,
|
|
|
|
|
linkedDatasource: logsDs as LokiDatasource,
|
|
|
|
|
serviceMapDatasourceUid: serviceMapDsUid,
|
|
|
|
|
serviceMapDatasource: serviceMapDs as PrometheusDatasource,
|
|
|
|
|
});
|
2021-08-23 07:51:45 -06:00
|
|
|
|
|
|
|
|
// Set initial query type to ensure traceID field appears
|
|
|
|
|
if (!this.props.query.queryType) {
|
|
|
|
|
this.props.onChange({
|
|
|
|
|
...this.props.query,
|
|
|
|
|
queryType: DEFAULT_QUERY_TYPE,
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-05-10 17:12:19 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-17 15:48:29 +02:00
|
|
|
onChangeLinkedQuery = (value: LokiQuery) => {
|
2020-10-13 19:12:49 +02:00
|
|
|
const { query, onChange } = this.props;
|
2021-05-10 17:12:19 +02:00
|
|
|
onChange({
|
|
|
|
|
...query,
|
2021-06-02 16:37:36 +02:00
|
|
|
linkedQuery: { ...value, refId: 'linked' },
|
2021-05-10 17:12:19 +02:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onRunLinkedQuery = () => {
|
|
|
|
|
this.props.onRunQuery();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
render() {
|
2021-08-17 15:48:29 +02:00
|
|
|
const { query, onChange, datasource } = this.props;
|
|
|
|
|
// Find query field from linked datasource
|
|
|
|
|
const tracesToLogsOptions: TraceToLogsOptions = datasource.tracesToLogs || {};
|
|
|
|
|
const logsDatasourceUid = tracesToLogsOptions.datasourceUid;
|
|
|
|
|
const graphDatasourceUid = datasource.serviceMap?.datasourceUid;
|
|
|
|
|
|
|
|
|
|
const queryTypeOptions: Array<SelectableValue<TempoQueryType>> = [
|
|
|
|
|
{ value: 'traceId', label: 'TraceID' },
|
|
|
|
|
{ value: 'upload', label: 'JSON file' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (config.featureToggles.tempoServiceGraph) {
|
|
|
|
|
queryTypeOptions.push({ value: 'serviceMap', label: 'Service Map' });
|
|
|
|
|
}
|
2021-05-10 17:12:19 +02:00
|
|
|
|
2021-08-19 09:15:46 -06:00
|
|
|
if (config.featureToggles.tempoSearch) {
|
|
|
|
|
queryTypeOptions.unshift({ value: 'nativeSearch', label: 'Search' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (logsDatasourceUid) {
|
2021-08-23 07:51:45 -06:00
|
|
|
if (!config.featureToggles.tempoSearch) {
|
|
|
|
|
// Place at beginning as Search if no native search
|
|
|
|
|
queryTypeOptions.unshift({ value: 'search', label: 'Search' });
|
|
|
|
|
} else {
|
|
|
|
|
// Place at end as Loki Search if native search is enabled
|
|
|
|
|
queryTypeOptions.push({ value: 'search', label: 'Loki Search' });
|
|
|
|
|
}
|
2021-08-19 09:15:46 -06:00
|
|
|
}
|
|
|
|
|
|
2020-10-13 19:12:49 +02:00
|
|
|
return (
|
2021-05-10 17:12:19 +02:00
|
|
|
<>
|
|
|
|
|
<InlineFieldRow>
|
|
|
|
|
<InlineField label="Query type">
|
|
|
|
|
<RadioButtonGroup<TempoQueryType>
|
2021-08-17 15:48:29 +02:00
|
|
|
options={queryTypeOptions}
|
2021-08-23 07:51:45 -06:00
|
|
|
value={query.queryType}
|
2021-05-10 17:12:19 +02:00
|
|
|
onChange={(v) =>
|
|
|
|
|
onChange({
|
|
|
|
|
...query,
|
|
|
|
|
queryType: v,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
size="md"
|
|
|
|
|
/>
|
|
|
|
|
</InlineField>
|
|
|
|
|
</InlineFieldRow>
|
2021-08-17 15:48:29 +02:00
|
|
|
{query.queryType === 'search' && (
|
|
|
|
|
<SearchSection
|
|
|
|
|
linkedDatasourceUid={logsDatasourceUid}
|
|
|
|
|
query={query}
|
|
|
|
|
onRunQuery={this.onRunLinkedQuery}
|
|
|
|
|
onChange={this.onChangeLinkedQuery}
|
|
|
|
|
/>
|
2021-05-10 17:12:19 +02:00
|
|
|
)}
|
2021-08-19 09:15:46 -06:00
|
|
|
{query.queryType === 'nativeSearch' && (
|
|
|
|
|
<NativeSearch
|
|
|
|
|
datasource={this.props.datasource}
|
|
|
|
|
query={query}
|
|
|
|
|
onChange={onChange}
|
|
|
|
|
onBlur={this.props.onBlur}
|
|
|
|
|
onRunQuery={this.props.onRunQuery}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2021-08-05 15:13:44 +02:00
|
|
|
{query.queryType === 'upload' && (
|
|
|
|
|
<div className={css({ padding: this.props.theme.spacing(2) })}>
|
|
|
|
|
<FileDropzone
|
|
|
|
|
options={{ multiple: false }}
|
|
|
|
|
onLoad={(result) => {
|
|
|
|
|
this.props.datasource.uploadedJson = result;
|
|
|
|
|
this.props.onRunQuery();
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2021-08-17 15:48:29 +02:00
|
|
|
{query.queryType === 'traceId' && (
|
2021-05-10 17:12:19 +02:00
|
|
|
<LegacyForms.FormField
|
|
|
|
|
label="Trace ID"
|
|
|
|
|
labelWidth={4}
|
|
|
|
|
inputEl={
|
|
|
|
|
<div className="slate-query-field__wrapper">
|
|
|
|
|
<div className="slate-query-field" aria-label={selectors.components.QueryField.container}>
|
|
|
|
|
<input
|
|
|
|
|
style={{ width: '100%' }}
|
|
|
|
|
value={query.query || ''}
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
onChange({
|
|
|
|
|
...query,
|
|
|
|
|
query: e.currentTarget.value,
|
|
|
|
|
queryType: 'traceId',
|
|
|
|
|
linkedQuery: undefined,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2021-08-17 15:48:29 +02:00
|
|
|
{query.queryType === 'serviceMap' && <ServiceMapSection graphDatasourceUid={graphDatasourceUid} />}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ServiceMapSection({ graphDatasourceUid }: { graphDatasourceUid?: string }) {
|
|
|
|
|
const dsState = useAsync(() => getDS(graphDatasourceUid), [graphDatasourceUid]);
|
|
|
|
|
if (dsState.loading) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ds = dsState.value as LokiDatasource;
|
|
|
|
|
|
|
|
|
|
if (!graphDatasourceUid) {
|
|
|
|
|
return <div className="text-warning">Please set up a service graph datasource in the datasource settings.</div>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (graphDatasourceUid && !ds) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="text-warning">
|
|
|
|
|
Service graph datasource is configured but the data source no longer exists. Please configure existing data
|
|
|
|
|
source to use the service graph functionality.
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface SearchSectionProps {
|
|
|
|
|
linkedDatasourceUid?: string;
|
|
|
|
|
onChange: (value: LokiQuery) => void;
|
|
|
|
|
onRunQuery: () => void;
|
|
|
|
|
query: TempoQuery;
|
|
|
|
|
}
|
|
|
|
|
function SearchSection({ linkedDatasourceUid, onChange, onRunQuery, query }: SearchSectionProps) {
|
|
|
|
|
const dsState = useAsync(() => getDS(linkedDatasourceUid), [linkedDatasourceUid]);
|
|
|
|
|
if (dsState.loading) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ds = dsState.value as LokiDatasource;
|
|
|
|
|
|
|
|
|
|
if (ds) {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<InlineLabel>Tempo uses {ds.name} to find traces.</InlineLabel>
|
|
|
|
|
|
|
|
|
|
<LokiQueryField
|
|
|
|
|
datasource={ds}
|
|
|
|
|
onChange={onChange}
|
|
|
|
|
onRunQuery={onRunQuery}
|
|
|
|
|
query={query.linkedQuery ?? ({ refId: 'linked' } as any)}
|
|
|
|
|
history={[]}
|
|
|
|
|
/>
|
2021-05-10 17:12:19 +02:00
|
|
|
</>
|
2020-10-13 19:12:49 +02:00
|
|
|
);
|
|
|
|
|
}
|
2021-08-17 15:48:29 +02:00
|
|
|
|
|
|
|
|
if (!linkedDatasourceUid) {
|
|
|
|
|
return <div className="text-warning">Please set up a Traces-to-logs datasource in the datasource settings.</div>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (linkedDatasourceUid && !ds) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="text-warning">
|
|
|
|
|
Traces-to-logs datasource is configured but the data source no longer exists. Please configure existing data
|
|
|
|
|
source to use the search.
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getDS(uid?: string): Promise<DataSourceApi | undefined> {
|
|
|
|
|
if (!uid) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dsSrv = getDataSourceSrv();
|
|
|
|
|
try {
|
|
|
|
|
return await dsSrv.get(uid);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to load data source', error);
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
2020-10-13 19:12:49 +02:00
|
|
|
}
|
2021-08-05 15:13:44 +02:00
|
|
|
|
|
|
|
|
export const TempoQueryField = withTheme2(TempoQueryFieldComponent);
|