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 { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { EditorRow } from '@grafana/experimental';
|
import { EditorRow } from '@grafana/experimental';
|
||||||
import { config, FetchError, getTemplateSrv } from '@grafana/runtime';
|
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 { createErrorNotification } from '../../../../core/copy/appNotification';
|
||||||
import { notifyApp } from '../../../../core/reducers/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
|
// filter out tags that already exist in the static fields
|
||||||
const staticTags = datasource.search?.filters?.map((f) => f.tag) || [];
|
const staticTags = datasource.search?.filters?.map((f) => f.tag) || [];
|
||||||
staticTags.push('duration');
|
staticTags.push('duration');
|
||||||
|
staticTags.push('traceDuration');
|
||||||
|
|
||||||
// Dynamic filters are all filters that don't match the ID of a filter in the datasource configuration
|
// 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
|
// The duration and status fields are a special case since its selector is hard-coded
|
||||||
const dynamicFilters = (query.filters || []).filter(
|
const dynamicFilters = (query.filters || []).filter(
|
||||||
(f) =>
|
(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 (
|
return (
|
||||||
@@ -150,10 +153,25 @@ const TraceQLSearch = ({ datasource, query, onChange }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</InlineSearchField>
|
</InlineSearchField>
|
||||||
<InlineSearchField
|
<InlineSearchField
|
||||||
label={'Span Duration'}
|
label={'Duration'}
|
||||||
tooltip="The span duration, i.e. end - start time of the span. Accepted units are ns, ms, s, m, h"
|
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'}>
|
<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
|
<DurationInput
|
||||||
filter={
|
filter={
|
||||||
findFilter('min-duration') || {
|
findFilter('min-duration') || {
|
||||||
|
|||||||
@@ -21,6 +21,32 @@ describe('generateQueryFromFilters generates the correct query for', () => {
|
|||||||
expect(generateQueryFromFilters([{ id: 'foo', tag: 'footag', value: 'foovalue' }])).toBe('{}');
|
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', () => {
|
it('a field with tag, operator and tag', () => {
|
||||||
expect(generateQueryFromFilters([{ id: 'foo', tag: 'footag', value: 'foovalue', operator: '=' }])).toBe(
|
expect(generateQueryFromFilters([{ id: 'foo', tag: 'footag', value: 'foovalue', operator: '=' }])).toBe(
|
||||||
'{.footag=foovalue}'
|
'{.footag=foovalue}'
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Scope } from '../types';
|
|||||||
export const generateQueryFromFilters = (filters: TraceqlFilter[]) => {
|
export const generateQueryFromFilters = (filters: TraceqlFilter[]) => {
|
||||||
return `{${filters
|
return `{${filters
|
||||||
.filter((f) => f.tag && f.operator && f.value?.length)
|
.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(' && ')}}`;
|
.join(' && ')}}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,6 +31,16 @@ const scopeHelper = (f: TraceqlFilter) => {
|
|||||||
(f.scope === TraceqlSearchScope.Resource || f.scope === TraceqlSearchScope.Span ? f.scope?.toLowerCase() : '') + '.'
|
(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) => {
|
export const filterScopedTag = (f: TraceqlFilter) => {
|
||||||
return scopeHelper(f) + f.tag;
|
return scopeHelper(f) + f.tag;
|
||||||
|
|||||||
Reference in New Issue
Block a user