diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.test.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.test.tsx index fa042b933ab..c045f618e29 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.test.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.test.tsx @@ -124,7 +124,7 @@ describe('', () => { jest.advanceTimersByTime(1000); await waitFor(() => { expect(screen.getByText(/Services: 2\/3/)).toBeDefined(); - expect(screen.getByText(/Depth: 1\/1/)).toBeDefined(); + expect(screen.getByText(/Depth: 1/)).toBeDefined(); }); }); }); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx index d4abc58acdc..50971264258 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx @@ -129,8 +129,6 @@ export default memo(function NextPrevResult(props: NextPrevResultProps) { const getMatchesMetadata = useCallback( (depth: number, services: number) => { - const matchedServices: string[] = []; - const matchedDepth: number[] = []; let metadata = ( <> {`${trace.spans.length} spans`} @@ -144,13 +142,6 @@ export default memo(function NextPrevResult(props: NextPrevResultProps) { ); if (spanFilterMatches) { - spanFilterMatches.forEach((spanID) => { - if (trace.processes[spanID]) { - matchedServices.push(trace.processes[spanID].serviceName); - matchedDepth.push(trace.spans.find((span) => span.spanID === spanID)?.depth || 0); - } - }); - if (spanFilterMatches.size === 0) { metadata = ( <> @@ -167,6 +158,13 @@ export default memo(function NextPrevResult(props: NextPrevResultProps) { ? `${focusedSpanIndexForSearch + 1}/${spanFilterMatches.size} ${type}` : `${spanFilterMatches.size} ${type}`; + const matchedServices: string[] = []; + spanFilterMatches.forEach((spanID) => { + if (trace.processes[spanID]) { + matchedServices.push(trace.processes[spanID].serviceName); + } + }); + metadata = ( <> {text} @@ -175,9 +173,7 @@ export default memo(function NextPrevResult(props: NextPrevResultProps) {
Services: {new Set(matchedServices).size}/{services}
-
- Depth: {new Set(matchedDepth).size}/{depth} -
+
Depth: {depth}
)} diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx index ecdcd93f51e..948615af288 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx @@ -69,9 +69,18 @@ export default memo(function TracePageSearchBar(props: TracePageSearchBarProps) search.tags.some((tag) => { return tag.key; }) || + (search.query && search.query !== '') || showSpanFilterMatchesOnly ); - }, [search.serviceName, search.spanName, search.from, search.to, search.tags, showSpanFilterMatchesOnly]); + }, [ + search.serviceName, + search.spanName, + search.from, + search.to, + search.tags, + search.query, + showSpanFilterMatchesOnly, + ]); return (
diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx index a130474565d..dd7318d880c 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx @@ -144,6 +144,7 @@ describe('SpanFilters', () => { expect(screen.getByText('ProcessKey1')).toBeInTheDocument(); expect(screen.getByText('LogKey0')).toBeInTheDocument(); expect(screen.getByText('LogKey1')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Find...')).toBeInTheDocument(); }); }); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx index 7a27e19a20c..e052ad598b7 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx @@ -23,6 +23,7 @@ import { Collapse, HorizontalGroup, Icon, InlineField, InlineFieldRow, Select, T import { IntervalInput } from 'app/core/components/IntervalInput/IntervalInput'; import { defaultFilters, randomId, SearchProps, Tag } from '../../../useSearch'; +import SearchBarInput from '../../common/SearchBarInput'; import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE, ID } from '../../constants/span'; import { Trace } from '../../types'; import NextPrevResult from '../SearchBar/NextPrevResult'; @@ -298,7 +299,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => { return (
- + this.props.onChange(e.currentTarget.value)} - suffix={suffix} - value={value} - /> +
+ this.props.onChange(e.currentTarget.value)} + suffix={suffix} + value={value} + /> +
); } } diff --git a/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts b/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts index c5652e4b43d..00fd835e1d9 100644 --- a/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts +++ b/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts @@ -465,6 +465,11 @@ describe('filterSpans', () => { spans ) ).toEqual(new Set()); + + // query + expect(filterSpans({ ...defaultFilters, query: 'serviceName0' }, spans)).toEqual(new Set([spanID0])); + expect(filterSpans({ ...defaultFilters, query: 'tagKey1' }, spans)).toEqual(new Set([spanID0, spanID2])); + expect(filterSpans({ ...defaultFilters, query: 'does_not_exist' }, spans)).toEqual(new Set([])); }); // Multiple @@ -541,6 +546,38 @@ describe('filterSpans', () => { ) ).toEqual(new Set([spanID2])); + // query + other + expect(filterSpans({ ...defaultFilters, serviceName: 'serviceName0', query: 'tag' }, spans)).toEqual( + new Set([spanID0]) + ); + expect(filterSpans({ ...defaultFilters, serviceName: 'serviceName0', query: 'tagKey2' }, spans)).toEqual( + new Set([]) + ); + expect( + filterSpans( + { ...defaultFilters, serviceName: 'serviceName2', spanName: 'operationName2', query: 'tagKey1' }, + spans + ) + ).toEqual(new Set([spanID2])); + expect( + filterSpans( + { ...defaultFilters, serviceName: 'serviceName2', spanName: 'operationName2', to: '6ms', query: 'kind2' }, + spans + ) + ).toEqual(new Set([spanID2])); + expect( + filterSpans( + { + ...defaultFilters, + serviceName: 'serviceName0', + spanName: 'operationName0', + from: '2ms', + query: 'logFieldKey1', + }, + spans + ) + ).toEqual(new Set([spanID0])); + // all expect( filterSpans( diff --git a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx index 03ed3fd85db..c141ca43ce7 100644 --- a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx +++ b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx @@ -44,9 +44,59 @@ export function filterSpans(searchProps: SearchProps, spans: TraceSpan[] | TNil) filteredSpans = true; } + if (searchProps.query) { + const queryMatches = getQueryMatches(searchProps.query, spans); + if (queryMatches) { + spans = queryMatches; + filteredSpans = true; + } + } + return filteredSpans ? new Set(spans.map((span: TraceSpan) => span.spanID)) : undefined; } +export function getQueryMatches(query: string, spans: TraceSpan[] | TNil) { + if (!spans) { + return undefined; + } + + const queryParts: string[] = []; + + // split query by whitespace, remove empty strings, and extract filters + query + .split(/\s+/) + .filter(Boolean) + .forEach((w) => { + queryParts.push(w.toLowerCase()); + }); + + const isTextInQuery = (queryParts: string[], text: string) => + queryParts.some((queryPart) => text.toLowerCase().includes(queryPart)); + + const isTextInKeyValues = (kvs: TraceKeyValuePair[]) => + kvs + ? kvs.some((kv) => { + return isTextInQuery(queryParts, kv.key) || isTextInQuery(queryParts, kv.value.toString()); + }) + : false; + + const isSpanAMatch = (span: TraceSpan) => + isTextInQuery(queryParts, span.operationName) || + isTextInQuery(queryParts, span.process.serviceName) || + isTextInKeyValues(span.tags) || + (span.kind && isTextInQuery(queryParts, span.kind)) || + (span.statusCode !== undefined && isTextInQuery(queryParts, SpanStatusCode[span.statusCode])) || + (span.statusMessage && isTextInQuery(queryParts, span.statusMessage)) || + (span.instrumentationLibraryName && isTextInQuery(queryParts, span.instrumentationLibraryName)) || + (span.instrumentationLibraryVersion && isTextInQuery(queryParts, span.instrumentationLibraryVersion)) || + (span.traceState && isTextInQuery(queryParts, span.traceState)) || + (span.logs !== null && span.logs.some((log) => isTextInKeyValues(log.fields))) || + isTextInKeyValues(span.process.tags) || + queryParts.some((queryPart) => queryPart === span.spanID); + + return spans.filter(isSpanAMatch); +} + const getTagMatches = (spans: TraceSpan[], tags: Tag[]) => { // remove empty/default tags tags = tags.filter((tag) => { diff --git a/public/app/features/explore/TraceView/useSearch.ts b/public/app/features/explore/TraceView/useSearch.ts index 59469f2af99..da25987d7f3 100644 --- a/public/app/features/explore/TraceView/useSearch.ts +++ b/public/app/features/explore/TraceView/useSearch.ts @@ -13,6 +13,7 @@ export interface SearchProps { to?: string; toOperator: string; tags: Tag[]; + query?: string; } export interface Tag {