Tempo: TraceQL editor bug fixes (#60414)

* Set width of common columns between trace and span rows

* Better syntax highlighting for non string constants

* Apply variables to traceql query

* Fix test
This commit is contained in:
Andre Pereira
2023-01-02 14:23:50 +00:00
committed by GitHub
parent b179ac91a2
commit 950f357175
5 changed files with 66 additions and 20 deletions

View File

@@ -13,7 +13,13 @@ import {
MutableDataFrame, MutableDataFrame,
PluginType, PluginType,
} from '@grafana/data'; } from '@grafana/data';
import { BackendDataSourceResponse, FetchResponse, setBackendSrv, setDataSourceSrv } from '@grafana/runtime'; import {
BackendDataSourceResponse,
FetchResponse,
setBackendSrv,
setDataSourceSrv,
TemplateSrv,
} from '@grafana/runtime';
import config from 'app/core/config'; import config from 'app/core/config';
import { import {
@@ -49,7 +55,8 @@ describe('Tempo data source', () => {
beforeEach(() => (console.error = consoleErrorMock)); beforeEach(() => (console.error = consoleErrorMock));
it('returns empty response when traceId is empty', async () => { it('returns empty response when traceId is empty', async () => {
const ds = new TempoDatasource(defaultSettings); const templateSrv: TemplateSrv = { replace: jest.fn() } as unknown as TemplateSrv;
const ds = new TempoDatasource(defaultSettings, templateSrv);
const response = await lastValueFrom( const response = await lastValueFrom(
ds.query({ targets: [{ refId: 'refid1', queryType: 'traceql', query: '' } as Partial<TempoQuery>] } as any), ds.query({ targets: [{ refId: 'refid1', queryType: 'traceql', query: '' } as Partial<TempoQuery>] } as any),
{ defaultValue: 'empty' } { defaultValue: 'empty' }

View File

@@ -175,7 +175,8 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
} }
if (targets.traceql?.length) { if (targets.traceql?.length) {
try { try {
const queryValue = targets.traceql[0].query; const appliedQuery = this.applyVariables(targets.traceql[0], options.scopedVars);
const queryValue = appliedQuery?.query || '';
const hexOnlyRegex = /^[0-9A-Fa-f]*$/; const hexOnlyRegex = /^[0-9A-Fa-f]*$/;
// Check whether this is a trace ID or traceQL query by checking if it only contains hex characters // Check whether this is a trace ID or traceQL query by checking if it only contains hex characters
if (queryValue.trim().match(hexOnlyRegex)) { if (queryValue.trim().match(hexOnlyRegex)) {

View File

@@ -131,15 +131,15 @@ describe('createTableFrameFromTraceQlQuery()', () => {
expect(frame.fields[0].values.get(0)).toBe('b1586c3c8c34d'); expect(frame.fields[0].values.get(0)).toBe('b1586c3c8c34d');
expect(frame.fields[0].config.unit).toBe('string'); expect(frame.fields[0].config.unit).toBe('string');
expect(frame.fields[0].values).toBeInstanceOf(ArrayVector); expect(frame.fields[0].values).toBeInstanceOf(ArrayVector);
// Trace name field
expect(frame.fields[1].name).toBe('traceName');
expect(frame.fields[1].type).toBe('string');
expect(frame.fields[1].values.get(0)).toBe('lb HTTP Client');
expect(frame.fields[1].values).toBeInstanceOf(ArrayVector);
// Start time field // Start time field
expect(frame.fields[2].name).toBe('startTime'); expect(frame.fields[1].name).toBe('startTime');
expect(frame.fields[1].type).toBe('string');
expect(frame.fields[1].values.get(1)).toBe('2022-01-27 22:56:06');
expect(frame.fields[1].values).toBeInstanceOf(ArrayVector);
// Trace name field
expect(frame.fields[2].name).toBe('traceName');
expect(frame.fields[2].type).toBe('string'); expect(frame.fields[2].type).toBe('string');
expect(frame.fields[2].values.get(1)).toBe('2022-01-27 22:56:06'); expect(frame.fields[2].values.get(0)).toBe('lb HTTP Client');
expect(frame.fields[2].values).toBeInstanceOf(ArrayVector); expect(frame.fields[2].values).toBeInstanceOf(ArrayVector);
// Duration field // Duration field
expect(frame.fields[3].name).toBe('traceDuration'); expect(frame.fields[3].name).toBe('traceDuration');

View File

@@ -630,6 +630,9 @@ export function createTableFrameFromTraceQlQuery(
config: { config: {
unit: 'string', unit: 'string',
displayNameFromDS: 'Trace ID', displayNameFromDS: 'Trace ID',
custom: {
width: 200,
},
links: [ links: [
{ {
title: 'Trace: ${__value.raw}', title: 'Trace: ${__value.raw}',
@@ -646,9 +649,28 @@ export function createTableFrameFromTraceQlQuery(
], ],
}, },
}, },
{
name: 'startTime',
type: FieldType.string,
config: {
displayNameFromDS: 'Start time',
custom: {
width: 200,
},
},
},
{ name: 'traceName', type: FieldType.string, config: { displayNameFromDS: 'Name' } }, { name: 'traceName', type: FieldType.string, config: { displayNameFromDS: 'Name' } },
{ name: 'startTime', type: FieldType.string, config: { displayNameFromDS: 'Start time' } }, {
{ name: 'traceDuration', type: FieldType.number, config: { displayNameFromDS: 'Duration', unit: 'ms' } }, name: 'traceDuration',
type: FieldType.number,
config: {
displayNameFromDS: 'Duration',
unit: 'ms',
custom: {
width: 120,
},
},
},
], ],
meta: { meta: {
preferredVisualisationType: 'table', preferredVisualisationType: 'table',
@@ -710,6 +732,9 @@ const traceSubFrame = (
config: { config: {
unit: 'string', unit: 'string',
displayNameFromDS: 'Span ID', displayNameFromDS: 'Span ID',
custom: {
width: 200,
},
links: [ links: [
{ {
title: 'Span: ${__value.raw}', title: 'Span: ${__value.raw}',
@@ -731,21 +756,32 @@ const traceSubFrame = (
], ],
}, },
}, },
{
name: 'spanStartTime',
type: FieldType.string,
config: {
displayNameFromDS: 'Start time',
custom: {
width: 200,
},
},
},
{ {
name: 'name', name: 'name',
type: FieldType.string, type: FieldType.string,
config: { displayNameFromDS: 'Name', custom: { hidden: !hasNameAttribute } }, config: { displayNameFromDS: 'Name', custom: { hidden: !hasNameAttribute } },
}, },
{
name: 'spanStartTime',
type: FieldType.string,
config: { displayNameFromDS: 'Start time' },
},
...Object.values(spanDynamicAttrs), ...Object.values(spanDynamicAttrs),
{ {
name: 'duration', name: 'duration',
type: FieldType.number, type: FieldType.number,
config: { displayNameFromDS: 'Duration', unit: 'ns' }, config: {
displayNameFromDS: 'Duration',
unit: 'ns',
custom: {
width: 120,
},
},
}, },
], ],
meta: { meta: {

View File

@@ -26,9 +26,9 @@ const intrinsics = ['duration', 'name', 'status', 'parent'];
const scopes: string[] = ['resource', 'span']; const scopes: string[] = ['resource', 'span'];
const booleans = ['false', 'true']; const keywords = intrinsics.concat(scopes);
const keywords = intrinsics.concat(scopes).concat(booleans); const statusValues = ['ok', 'unset', 'error', 'false', 'true'];
export const language = { export const language = {
ignoreCase: false, ignoreCase: false,
@@ -37,6 +37,7 @@ export const language = {
keywords, keywords,
operators, operators,
statusValues,
// we include these common regular expressions // we include these common regular expressions
symbols: /[=><!~?:&|+\-*\/^%]+/, symbols: /[=><!~?:&|+\-*\/^%]+/,
@@ -62,6 +63,7 @@ export const language = {
{ {
cases: { cases: {
'@keywords': 'type', '@keywords': 'type',
'@statusValues': 'type.identifier',
'@default': 'identifier', '@default': 'identifier',
}, },
}, },