mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Tempo: Easily filter by trace duration (#79931)
* Easily filter by trace duration * Add test * Update onChange
This commit is contained in:
@@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { EditorRow } from '@grafana/experimental';
|
||||
import { config, FetchError, getTemplateSrv } from '@grafana/runtime';
|
||||
import { Alert, HorizontalGroup, useStyles2 } from '@grafana/ui';
|
||||
import { Alert, HorizontalGroup, Select, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { createErrorNotification } from '../../../../core/copy/appNotification';
|
||||
import { notifyApp } from '../../../../core/reducers/appNotification';
|
||||
@@ -95,12 +95,15 @@ const TraceQLSearch = ({ datasource, query, onChange }: Props) => {
|
||||
// filter out tags that already exist in the static fields
|
||||
const staticTags = datasource.search?.filters?.map((f) => f.tag) || [];
|
||||
staticTags.push('duration');
|
||||
staticTags.push('traceDuration');
|
||||
|
||||
// Dynamic filters are all filters that don't match the ID of a filter in the datasource configuration
|
||||
// The duration and status fields are a special case since its selector is hard-coded
|
||||
const dynamicFilters = (query.filters || []).filter(
|
||||
(f) =>
|
||||
!hardCodedFilterIds.includes(f.id) && (datasource.search?.filters?.findIndex((sf) => sf.id === f.id) || 0) === -1
|
||||
!hardCodedFilterIds.includes(f.id) &&
|
||||
(datasource.search?.filters?.findIndex((sf) => sf.id === f.id) || 0) === -1 &&
|
||||
f.id !== 'duration-type'
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -150,10 +153,25 @@ const TraceQLSearch = ({ datasource, query, onChange }: Props) => {
|
||||
/>
|
||||
</InlineSearchField>
|
||||
<InlineSearchField
|
||||
label={'Span Duration'}
|
||||
tooltip="The span duration, i.e. end - start time of the span. Accepted units are ns, ms, s, m, h"
|
||||
label={'Duration'}
|
||||
tooltip="The trace or span duration, i.e. end - start time of the trace/span. Accepted units are ns, ms, s, m, h"
|
||||
>
|
||||
<HorizontalGroup spacing={'sm'}>
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'span', value: 'span' },
|
||||
{ label: 'trace', value: 'trace' },
|
||||
]}
|
||||
value={findFilter('duration-type')?.value ?? 'span'}
|
||||
onChange={(v) => {
|
||||
const filter = findFilter('duration-type') || {
|
||||
id: 'duration-type',
|
||||
value: 'span',
|
||||
};
|
||||
updateFilter({ ...filter, value: v?.value });
|
||||
}}
|
||||
aria-label={'duration type'}
|
||||
/>
|
||||
<DurationInput
|
||||
filter={
|
||||
findFilter('min-duration') || {
|
||||
|
||||
@@ -21,6 +21,32 @@ describe('generateQueryFromFilters generates the correct query for', () => {
|
||||
expect(generateQueryFromFilters([{ id: 'foo', tag: 'footag', value: 'foovalue' }])).toBe('{}');
|
||||
});
|
||||
|
||||
describe('generates correct query for duration when duration type', () => {
|
||||
it('not set', () => {
|
||||
expect(
|
||||
generateQueryFromFilters([
|
||||
{ id: 'min-duration', operator: '>', valueType: 'duration', tag: 'duration', value: '100ms' },
|
||||
])
|
||||
).toBe('{duration>100ms}');
|
||||
});
|
||||
it('set to span', () => {
|
||||
expect(
|
||||
generateQueryFromFilters([
|
||||
{ id: 'min-duration', operator: '>', valueType: 'duration', tag: 'duration', value: '100ms' },
|
||||
{ id: 'duration-type', value: 'span' },
|
||||
])
|
||||
).toBe('{duration>100ms}');
|
||||
});
|
||||
it('set to trace', () => {
|
||||
expect(
|
||||
generateQueryFromFilters([
|
||||
{ id: 'min-duration', operator: '>', valueType: 'duration', tag: 'duration', value: '100ms' },
|
||||
{ id: 'duration-type', value: 'trace' },
|
||||
])
|
||||
).toBe('{traceDuration>100ms}');
|
||||
});
|
||||
});
|
||||
|
||||
it('a field with tag, operator and tag', () => {
|
||||
expect(generateQueryFromFilters([{ id: 'foo', tag: 'footag', value: 'foovalue', operator: '=' }])).toBe(
|
||||
'{.footag=foovalue}'
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Scope } from '../types';
|
||||
export const generateQueryFromFilters = (filters: TraceqlFilter[]) => {
|
||||
return `{${filters
|
||||
.filter((f) => f.tag && f.operator && f.value?.length)
|
||||
.map((f) => `${scopeHelper(f)}${f.tag}${f.operator}${valueHelper(f)}`)
|
||||
.map((f) => `${scopeHelper(f)}${tagHelper(f, filters)}${f.operator}${valueHelper(f)}`)
|
||||
.join(' && ')}}`;
|
||||
};
|
||||
|
||||
@@ -31,6 +31,16 @@ 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');
|
||||
if (durationType) {
|
||||
return durationType.value === 'trace' ? 'traceDuration' : 'duration';
|
||||
}
|
||||
return f.tag;
|
||||
}
|
||||
return f.tag;
|
||||
};
|
||||
|
||||
export const filterScopedTag = (f: TraceqlFilter) => {
|
||||
return scopeHelper(f) + f.tag;
|
||||
|
||||
Reference in New Issue
Block a user