mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
parent
630b8a30be
commit
a49e1ded8f
@ -124,7 +124,7 @@ describe('<NextPrevResult>', () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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 = (
|
||||
<>
|
||||
<span>{`${trace.spans.length} spans`}</span>
|
||||
@ -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 = (
|
||||
<>
|
||||
<span>{text}</span>
|
||||
@ -175,9 +173,7 @@ export default memo(function NextPrevResult(props: NextPrevResultProps) {
|
||||
<div>
|
||||
Services: {new Set(matchedServices).size}/{services}
|
||||
</div>
|
||||
<div>
|
||||
Depth: {new Set(matchedDepth).size}/{depth}
|
||||
</div>
|
||||
<div>Depth: {depth}</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
@ -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 (
|
||||
<div className={styles.container}>
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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 (
|
||||
<div className={styles.container}>
|
||||
<Collapse label={collapseLabel} collapsible={true} isOpen={showSpanFilters} onToggle={setShowSpanFilters}>
|
||||
<InlineFieldRow>
|
||||
<InlineFieldRow className={styles.flexContainer}>
|
||||
<InlineField label="Service Name" labelWidth={16}>
|
||||
<HorizontalGroup spacing={'xs'}>
|
||||
<Select
|
||||
@ -318,6 +319,15 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
||||
/>
|
||||
</HorizontalGroup>
|
||||
</InlineField>
|
||||
<SearchBarInput
|
||||
onChange={(v) => {
|
||||
setSpanFiltersSearch({ ...search, query: v });
|
||||
if (v === '') {
|
||||
setShowSpanFilterMatchesOnly(false);
|
||||
}
|
||||
}}
|
||||
value={search.query || ''}
|
||||
/>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Span Name" labelWidth={16}>
|
||||
@ -479,6 +489,7 @@ SpanFilters.displayName = 'SpanFilters';
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
container: css`
|
||||
label: SpanFilters;
|
||||
margin: 0.5em 0 -${theme.spacing(1)} 0;
|
||||
z-index: 5;
|
||||
|
||||
@ -493,6 +504,10 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
margin: -2px 0 0 10px;
|
||||
}
|
||||
`,
|
||||
flexContainer: css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
}),
|
||||
addTag: css`
|
||||
margin: 0 0 0 10px;
|
||||
`,
|
||||
|
@ -16,21 +16,13 @@ import * as React from 'react';
|
||||
|
||||
import { IconButton, Input } from '@grafana/ui';
|
||||
|
||||
import { TNil } from '../types';
|
||||
|
||||
type Props = {
|
||||
allowClear?: boolean;
|
||||
inputProps: Record<string, unknown>;
|
||||
location: Location;
|
||||
trackFindFunction?: (str: string | TNil) => void;
|
||||
value: string | undefined;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
export default class SearchBarInput extends React.PureComponent<Props> {
|
||||
static defaultProps: Partial<Props> = {
|
||||
inputProps: {},
|
||||
trackFindFunction: undefined,
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
@ -39,25 +31,21 @@ export default class SearchBarInput extends React.PureComponent<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { allowClear, inputProps, value } = this.props;
|
||||
const { value } = this.props;
|
||||
|
||||
const suffix = (
|
||||
<>
|
||||
{inputProps.suffix}
|
||||
{allowClear && value && value.length && (
|
||||
<IconButton name="times" onClick={this.clearUiFind} tooltip="Clear input" />
|
||||
)}
|
||||
</>
|
||||
<>{value && value.length && <IconButton name="times" onClick={this.clearUiFind} tooltip="Clear input" />}</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Input
|
||||
placeholder="Find..."
|
||||
{...inputProps}
|
||||
onChange={(e) => this.props.onChange(e.currentTarget.value)}
|
||||
suffix={suffix}
|
||||
value={value}
|
||||
/>
|
||||
<div style={{ width: '200px' }}>
|
||||
<Input
|
||||
placeholder="Find..."
|
||||
onChange={(e) => this.props.onChange(e.currentTarget.value)}
|
||||
suffix={suffix}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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) => {
|
||||
|
@ -13,6 +13,7 @@ export interface SearchProps {
|
||||
to?: string;
|
||||
toOperator: string;
|
||||
tags: Tag[];
|
||||
query?: string;
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
|
Loading…
Reference in New Issue
Block a user