Tempo: Merge Trace ID and TraceQL tabs (#60180)

* Remove TraceID tab when TraceQL is enabled. Use TraceQL editor to query for trace IDs by checking whether the content is an hex only string

* Highlight valid trace IDs in traceql editor

* Update trace and span links to use TraceQL tab when feature flag is enabled

* Remove traceqlEditor feature flag.

* Remove traceId query type from Tempo and replace it with traceQl
This commit is contained in:
Andre Pereira 2022-12-13 13:27:45 +00:00 committed by GitHub
parent 75a11e92b2
commit 209b1848b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 53 additions and 83 deletions

View File

@ -79,7 +79,6 @@ Alpha features might be changed or removed without prior notice.
| `logRequestsInstrumentedAsUnknown` | Logs the path for requests that are instrumented as unknown |
| `dataConnectionsConsole` | Enables a new top-level page called Connections. This page is an experiment that provides a better experience when you install and configure data sources and other plugins. |
| `topnav` | New top nav and page layouts |
| `traceqlEditor` | Show the TraceQL editor in the explore page |
| `flameGraph` | Show the flame graph |
| `cloudWatchCrossAccountQuerying` | Use cross-account querying in CloudWatch datasource |
| `redshiftAsyncQueryDataSupport` | Enable async query data support for Redshift |

View File

@ -66,7 +66,6 @@ export interface FeatureToggles {
topnav?: boolean;
grpcServer?: boolean;
entityStore?: boolean;
traceqlEditor?: boolean;
flameGraph?: boolean;
cloudWatchCrossAccountQuerying?: boolean;
redshiftAsyncQueryDataSupport?: boolean;

View File

@ -287,11 +287,6 @@ var (
State: FeatureStateAlpha,
RequiresDevMode: true,
},
{
Name: "traceqlEditor",
Description: "Show the TraceQL editor in the explore page",
State: FeatureStateAlpha,
},
{
Name: "flameGraph",
Description: "Show the flame graph",

View File

@ -207,10 +207,6 @@ const (
// SQL-based entity store (requires storage flag also)
FlagEntityStore = "entityStore"
// FlagTraceqlEditor
// Show the TraceQL editor in the explore page
FlagTraceqlEditor = "traceqlEditor"
// FlagFlameGraph
// Show the flame graph
FlagFlameGraph = "flameGraph"

View File

@ -354,7 +354,7 @@ function getDataLinks(options: ExemplarTraceIdDestination): DataLink[] {
title: options.urlDisplayLabel || `Query with ${dsSettings?.name}`,
url: '',
internal: {
query: { query: '${__value.raw}', queryType: 'traceId' },
query: { query: '${__value.raw}', queryType: 'traceql' },
datasourceUid: options.datasourceUid,
datasourceName: dsSettings?.name ?? 'Data source not found',
},

View File

@ -3,13 +3,12 @@ import React from 'react';
import useAsync from 'react-use/lib/useAsync';
import { QueryEditorProps, SelectableValue } from '@grafana/data';
import { config, reportInteraction } from '@grafana/runtime';
import { reportInteraction } from '@grafana/runtime';
import {
FileDropzone,
InlineField,
InlineFieldRow,
InlineLabel,
QueryField,
RadioButtonGroup,
Themeable2,
withTheme2,
@ -28,7 +27,7 @@ import { getDS } from './utils';
interface Props extends QueryEditorProps<TempoDatasource, TempoQuery>, Themeable2 {}
const DEFAULT_QUERY_TYPE: TempoQueryType = 'traceId';
const DEFAULT_QUERY_TYPE: TempoQueryType = 'traceql';
class TempoQueryFieldComponent extends React.PureComponent<Props> {
constructor(props: Props) {
@ -77,8 +76,8 @@ class TempoQueryFieldComponent extends React.PureComponent<Props> {
const graphDatasourceUid = datasource.serviceMap?.datasourceUid;
const queryTypeOptions: Array<SelectableValue<TempoQueryType>> = [
{ value: 'traceId', label: 'TraceID' },
let queryTypeOptions: Array<SelectableValue<TempoQueryType>> = [
{ value: 'traceql', label: 'TraceQL' },
{ value: 'upload', label: 'JSON File' },
{ value: 'serviceMap', label: 'Service Graph' },
];
@ -97,10 +96,6 @@ class TempoQueryFieldComponent extends React.PureComponent<Props> {
}
}
if (config.featureToggles.traceqlEditor) {
queryTypeOptions.push({ value: 'traceql', label: 'TraceQL' });
}
return (
<>
<InlineFieldRow>
@ -155,27 +150,6 @@ class TempoQueryFieldComponent extends React.PureComponent<Props> {
/>
</div>
)}
{query.queryType === 'traceId' && (
<InlineFieldRow>
<InlineField label="Trace ID" labelWidth={14} grow>
<QueryField
query={query.query}
onChange={(val) => {
onChange({
...query,
query: val,
queryType: 'traceId',
linkedQuery: undefined,
});
}}
onBlur={this.props.onBlur}
onRunQuery={this.props.onRunQuery}
placeholder={'Enter a Trace ID (run with Shift+Enter)'}
portalOrigin="tempo"
/>
</InlineField>
</InlineFieldRow>
)}
{query.queryType === 'serviceMap' && (
<ServiceGraphSection graphDatasourceUid={graphDatasourceUid} query={query} onChange={onChange} />
)}

View File

@ -51,7 +51,7 @@ describe('Tempo data source', () => {
it('returns empty response when traceId is empty', async () => {
const ds = new TempoDatasource(defaultSettings);
const response = await lastValueFrom(
ds.query({ targets: [{ refId: 'refid1', queryType: 'traceId', query: '' } as Partial<TempoQuery>] } as any),
ds.query({ targets: [{ refId: 'refid1', queryType: 'traceql', query: '' } as Partial<TempoQuery>] } as any),
{ defaultValue: 'empty' }
);
expect(response).toBe('empty');
@ -61,7 +61,7 @@ describe('Tempo data source', () => {
function getQuery(): TempoQuery {
return {
refId: 'x',
queryType: 'traceId',
queryType: 'traceql',
linkedQuery: {
refId: 'linked',
expr: '{instance="$interpolationVar"}',
@ -388,7 +388,7 @@ describe('Tempo data source', () => {
raw: { from: '15m', to: 'now' },
},
},
[{ refId: 'refid1', queryType: 'traceId', query: '' } as TempoQuery]
[{ refId: 'refid1', queryType: 'traceql', query: '' } as TempoQuery]
);
expect(request.range.from.unix()).toBe(dateTime(new Date(2022, 8, 13, 15, 58, 0, 0)).unix());
@ -417,7 +417,7 @@ describe('Tempo data source', () => {
raw: { from: '15m', to: 'now' },
},
},
[{ refId: 'refid1', queryType: 'traceId', query: '' } as TempoQuery]
[{ refId: 'refid1', queryType: 'traceql', query: '' } as TempoQuery]
);
expect(request.range.from.unix()).toBe(dateTime(0).unix());

View File

@ -95,7 +95,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
query(options: DataQueryRequest<TempoQuery>): Observable<DataQueryResponse> {
const subQueries: Array<Observable<DataQueryResponse>> = [];
const filteredTargets = options.targets.filter((target) => !target.hide);
const targets: { [type: string]: TempoQuery[] } = groupBy(filteredTargets, (t) => t.queryType || 'traceId');
const targets: { [type: string]: TempoQuery[] } = groupBy(filteredTargets, (t) => t.queryType || 'traceql');
if (targets.clear) {
return of({ data: [], state: LoadingState.Done });
@ -175,28 +175,42 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
}
if (targets.traceql?.length) {
try {
reportInteraction('grafana_traces_traceql_queried', {
datasourceType: 'tempo',
app: options.app ?? '',
});
const queryValue = targets.traceql[0].query;
const hexOnlyRegex = /^[0-9A-Fa-f]*$/;
// Check whether this is a trace ID or traceQL query by checking if it only contains hex characters
if (queryValue.trim().match(hexOnlyRegex)) {
// There's only hex characters so let's assume that this is a trace ID
reportInteraction('grafana_traces_traceql_traceID_queried', {
datasourceType: 'tempo',
app: options.app ?? '',
query: queryValue ?? '',
});
subQueries.push(
this._request('/api/search', {
q: targets.traceql[0].query,
limit: options.targets[0].limit,
start: options.range.from.unix(),
end: options.range.to.unix(),
}).pipe(
map((response) => {
return {
data: createTableFrameFromTraceQlQuery(response.data.traces, this.instanceSettings),
};
}),
catchError((error) => {
return of({ error: { message: error.data.message }, data: [] });
})
)
);
subQueries.push(this.handleTraceIdQuery(options, targets.traceql));
} else {
reportInteraction('grafana_traces_traceql_queried', {
datasourceType: 'tempo',
app: options.app ?? '',
query: queryValue ?? '',
});
subQueries.push(
this._request('/api/search', {
q: queryValue,
limit: options.targets[0].limit,
start: options.range.from.unix(),
end: options.range.to.unix(),
}).pipe(
map((response) => {
return {
data: createTableFrameFromTraceQlQuery(response.data.traces, this.instanceSettings),
};
}),
catchError((error) => {
return of({ error: { message: error.data.message }, data: [] });
})
)
);
}
} catch (error) {
return of({ error: { message: error instanceof Error ? error.message : 'Unknown error occurred' }, data: [] });
}
@ -250,16 +264,6 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
}
}
if (targets.traceId?.length > 0) {
reportInteraction('grafana_traces_traceID_queried', {
datasourceType: 'tempo',
app: options.app ?? '',
query: targets.traceId[0].query ?? '',
});
subQueries.push(this.handleTraceIdQuery(options, targets.traceId));
}
return merge(...subQueries);
}

View File

@ -567,7 +567,7 @@ export function createTableFrameFromSearch(data: TraceSearchMetadata[], instance
datasourceName: instanceSettings.name,
query: {
query: '${__value.raw}',
queryType: 'traceId',
queryType: 'traceql',
},
},
},
@ -639,7 +639,7 @@ export function createTableFrameFromTraceQlQuery(
datasourceName: instanceSettings.name,
query: {
query: '${__value.raw}',
queryType: 'traceId',
queryType: 'traceql',
},
},
},
@ -719,7 +719,7 @@ const traceSubFrame = (
datasourceName: instanceSettings.name,
query: {
query: '${__data.fields.traceIdHidden}',
queryType: 'traceId',
queryType: 'traceql',
},
panelsState: {
trace: {

View File

@ -34,7 +34,7 @@ export function QueryEditor(props: Props) {
</a>
</InlineLabel>
<TraceQLEditor
placeholder="Enter a TraceQL query (run with Shift+Enter)"
placeholder="Enter a TraceQL query or trace ID (run with Shift+Enter)"
value={query.query}
onChange={onEditorChange}
datasource={props.datasource}

View File

@ -53,6 +53,9 @@ export const language = {
// labels
[/[a-z_.][\w./_-]*(?=\s*(=|!=|>|<|>=|<=|=~|!~))/, 'tag'],
//trace ID
[/^\s*[0-9A-Fa-f]+\s*$/, 'tag'],
// all keywords have the same color
[
/[a-zA-Z_.]\w*/,

View File

@ -37,7 +37,7 @@ export interface TempoJsonData extends DataSourceJsonData {
}
// search = Loki search, nativeSearch = Tempo search for backwards compatibility
export type TempoQueryType = 'traceql' | 'search' | 'traceId' | 'serviceMap' | 'upload' | 'nativeSearch' | 'clear';
export type TempoQueryType = 'traceql' | 'search' | 'serviceMap' | 'upload' | 'nativeSearch' | 'clear';
export interface TempoQuery extends DataQuery {
query: string;