diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.test.ts b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.test.ts index 98efdc71823..842ca2745ac 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.test.ts +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.test.ts @@ -2,7 +2,14 @@ import { uniq } from 'lodash'; import { TraceqlSearchScope } from '../dataquery.gen'; -import { generateQueryFromFilters, getUnscopedTags, getFilteredTags, getAllTags, getTagsByScope } from './utils'; +import { + generateQueryFromFilters, + getUnscopedTags, + getFilteredTags, + getAllTags, + getTagsByScope, + generateQueryFromAdHocFilters, +} from './utils'; describe('generateQueryFromFilters generates the correct query for', () => { it('an empty array', () => { @@ -129,6 +136,31 @@ describe('generateQueryFromFilters generates the correct query for', () => { }); }); +describe('generateQueryFromAdHocFilters generates the correct query for', () => { + it('an empty array', () => { + expect(generateQueryFromAdHocFilters([])).toBe('{}'); + }); + + it('a filter with values', () => { + expect(generateQueryFromAdHocFilters([{ key: 'footag', operator: '=', value: 'foovalue' }])).toBe( + '{footag="foovalue"}' + ); + }); + + it('two filters with values', () => { + expect( + generateQueryFromAdHocFilters([ + { key: 'footag', operator: '=', value: 'foovalue' }, + { key: 'bartag', operator: '=', value: 'barvalue' }, + ]) + ).toBe('{footag="foovalue" && bartag="barvalue"}'); + }); + + it('a filter with intrinsic values', () => { + expect(generateQueryFromAdHocFilters([{ key: 'kind', operator: '=', value: 'server' }])).toBe('{kind=server}'); + }); +}); + describe('gets correct tags', () => { it('for filtered tags when no tags supplied', () => { const tags = getFilteredTags(emptyTags, []); diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts index 2cb2a79f3ec..378076ed50b 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts @@ -1,6 +1,6 @@ import { startCase, uniq } from 'lodash'; -import { SelectableValue } from '@grafana/data'; +import { AdHocVariableFilter, SelectableValue } from '@grafana/data'; import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen'; import { intrinsics } from '../traceql/traceql'; @@ -22,6 +22,7 @@ const valueHelper = (f: TraceqlFilter) => { } return f.value; }; + const scopeHelper = (f: TraceqlFilter) => { // Intrinsic fields don't have a scope if (intrinsics.find((t) => t === f.tag)) { @@ -31,6 +32,7 @@ const scopeHelper = (f: TraceqlFilter) => { (f.scope === TraceqlSearchScope.Resource || f.scope === TraceqlSearchScope.Span ? f.scope?.toLowerCase() : '') + '.' ); }; + const tagHelper = (f: TraceqlFilter, filters: TraceqlFilter[]) => { if (f.tag === 'duration') { const durationType = filters.find((f) => f.id === 'duration-type'); @@ -42,6 +44,20 @@ const tagHelper = (f: TraceqlFilter, filters: TraceqlFilter[]) => { return f.tag; }; +export const generateQueryFromAdHocFilters = (filters: AdHocVariableFilter[]) => { + return `{${filters + .filter((f) => f.key && f.operator && f.value) + .map((f) => `${f.key}${f.operator}${adHocValueHelper(f)}`) + .join(' && ')}}`; +}; + +const adHocValueHelper = (f: AdHocVariableFilter) => { + if (intrinsics.find((t) => t === f.key)) { + return f.value; + } + return `"${f.value}"`; +}; + export const filterScopedTag = (f: TraceqlFilter) => { return scopeHelper(f) + f.tag; }; diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index 2f48b0abdd1..ff195732714 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -34,7 +34,7 @@ import { } from '@grafana/runtime'; import { BarGaugeDisplayMode, TableCellDisplayMode, VariableFormatID } from '@grafana/schema'; -import { generateQueryFromFilters } from './SearchTraceQLEditor/utils'; +import { generateQueryFromAdHocFilters, generateQueryFromFilters } from './SearchTraceQLEditor/utils'; import { TempoVariableQuery, TempoVariableQueryType } from './VariableQueryEditor'; import { PrometheusDatasource, PromQuery } from './_importedDependencies/datasources/prometheus/types'; import { TraceqlFilter, TraceqlSearchScope } from './dataquery.gen'; @@ -170,7 +170,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson return tags.filter((tag) => tag !== undefined).map((tag) => ({ text: tag })); } - async labelValuesQuery(labelName?: string): Promise<Array<{ text: string }>> { + async labelValuesQuery(labelName?: string, query?: string): Promise<Array<{ text: string }>> { if (!labelName) { return []; } @@ -192,7 +192,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson // For V2, we need to send scope and tag name, e.g. `span.http.status_code`, // unless the tag has intrinsic scope const scopeAndTag = scope === 'intrinsic' ? labelName : `${scope}.${labelName}`; - options = await this.languageProvider.getOptionsV2(scopeAndTag); + options = await this.languageProvider.getOptionsV2(scopeAndTag, query); } catch { // For V1, the tag name (e.g. `http.status_code`) is enough options = await this.languageProvider.getOptionsV1(labelName); @@ -217,7 +217,8 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson // Allows to retrieve the list of tag values for ad-hoc filters getTagValues(options: DataSourceGetTagValuesOptions<TempoQuery>): Promise<Array<{ text: string }>> { - return this.labelValuesQuery(options.key.replace(/^(resource|span)\./, '')); + const query = generateQueryFromAdHocFilters(options.filters); + return this.labelValuesQuery(options.key.replace(/^(resource|span)\./, ''), query); } init = async () => {