Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support in-page-search in trace view on key=value pairs #1391

Merged
merged 5 commits into from
Jun 4, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

import * as React from 'react';
import { Button, Input } from 'antd';
import { Button, Input, Tooltip } from 'antd';
import cx from 'classnames';
import IoAndroidLocate from 'react-icons/lib/io/android-locate';

Expand Down Expand Up @@ -56,49 +56,79 @@ export function TracePageSearchBarFn(props: TracePageSearchBarProps & { forwarde
suffix: count,
};

const renderTooltip = () => {
return (
<div style={{ wordBreak: 'normal' }}>
<p>
This is an in-page search. Enter the query as a list of space-separated string terms.
Each term is used in a substring match against any of the following data elements:
service name, operation name, span ID, and key-value pairs in tags and logs. The spans
that match any of the search terms will be highlighted.
</p>
<p>
When matching key-value pairs, the substring search is applied separately against
the key, the value, and the <code>"key=value"</code> string.
The latter allows searching for exact matches like <code>http.status_code=200</code>.
</p>
<p>
To preclude certain key-value pairs from participating in the matching, prefix the key
with the minus <code>'-'</code> sign, e.g., <code>-http.status_code</code>.
</p>
</div>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote the tooltip to make the explanation a bit more clear and to remove the styling applied to some key words which was difficult to read on the dark background.

);
};

return (
<div className="TracePageSearchBar">
{/* style inline because compact overwrites the display */}
<Input.Group className="ub-justify-end" compact style={{ display: 'flex' }}>
<UiFindInput
inputProps={uiFindInputInputProps}
forwardedRef={forwardedRef}
trackFindFunction={trackFilter}
/>
{navigable && (
<>
<Button
className={cx(btnClass, 'TracePageSearchBar--locateBtn')}
disabled={!textFilter}
htmlType="button"
onClick={focusUiFindMatches}
>
<IoAndroidLocate />
</Button>
<Button
className={btnClass}
disabled={!textFilter}
htmlType="button"
icon="up"
onClick={prevResult}
/>
<Button
className={btnClass}
disabled={!textFilter}
htmlType="button"
icon="down"
onClick={nextResult}
/>
</>
)}
<Button
className={btnClass}
disabled={!textFilter}
htmlType="button"
icon="close"
onClick={clearSearch}
/>
</Input.Group>
<Tooltip
arrowPointAtCenter
mouseLeaveDelay={0.5}
placement="bottom"
overlayStyle={{ maxWidth: '600px' }} // This is a large tooltip and the default is too narrow.
title={renderTooltip()}
>
{/* style inline because compact overwrites the display */}
<Input.Group className="ub-justify-end" compact style={{ display: 'flex' }}>
<UiFindInput
inputProps={uiFindInputInputProps}
forwardedRef={forwardedRef}
trackFindFunction={trackFilter}
/>
{navigable && (
<>
<Button
className={cx(btnClass, 'TracePageSearchBar--locateBtn')}
disabled={!textFilter}
htmlType="button"
onClick={focusUiFindMatches}
>
<IoAndroidLocate />
</Button>
<Button
className={btnClass}
disabled={!textFilter}
htmlType="button"
icon="up"
onClick={prevResult}
/>
<Button
className={btnClass}
disabled={!textFilter}
htmlType="button"
icon="down"
onClick={nextResult}
/>
</>
)}
<Button
className={btnClass}
disabled={!textFilter}
htmlType="button"
icon="close"
onClick={clearSearch}
/>
</Input.Group>
</Tooltip>
</div>
);
}
Expand Down
18 changes: 18 additions & 0 deletions packages/jaeger-ui/src/utils/filter-spans.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ describe('filterSpans', () => {
expect(filterSpans('tagValue2', spans)).toEqual(new Set([spanID2]));
});

it("should return spans whose tags' kv.key=kv.value match a filter", () => {
expect(filterSpans('tagKey1=tagValue1', spans)).toEqual(new Set([spanID0]));
expect(filterSpans('tagKey0=tagValue0', spans)).toEqual(new Set([spanID0]));
expect(filterSpans('tagKey2=tagValue1', spans)).toEqual(new Set([spanID2]));
});

it("should exclude span whose tags' kv.value or kv.key match a filter if the key matches an excludeKey", () => {
expect(filterSpans('tagValue1 -tagKey2', spans)).toEqual(new Set([spanID0]));
expect(filterSpans('tagValue1 -tagKey1', spans)).toEqual(new Set([spanID2]));
Expand All @@ -177,6 +183,12 @@ describe('filterSpans', () => {
expect(filterSpans('logFieldValue2', spans)).toEqual(new Set([spanID2]));
});

it('should return spans whose logs have a field whose kv.key=kv.value match a filter', () => {
expect(filterSpans('logFieldKey1=logFieldValue1', spans)).toEqual(new Set([spanID0]));
expect(filterSpans('logFieldKey0=logFieldValue0', spans)).toEqual(new Set([spanID0]));
expect(filterSpans('logFieldKey2=logFieldValue1', spans)).toEqual(new Set([spanID2]));
});

it('should exclude span whose logs have a field whose kv.value or kv.key match a filter if the key matches an excludeKey', () => {
expect(filterSpans('logFieldValue1 -logFieldKey2', spans)).toEqual(new Set([spanID0]));
expect(filterSpans('logFieldValue1 -logFieldKey1', spans)).toEqual(new Set([spanID2]));
Expand All @@ -199,6 +211,12 @@ describe('filterSpans', () => {
expect(filterSpans('processTagValue2', spans)).toEqual(new Set([spanID2]));
});

it("should return spans whose process.processTags' kv.keykv.value match a filter", () => {
expect(filterSpans('processTagKey1=processTagValue1', spans)).toEqual(new Set([spanID0]));
expect(filterSpans('processTagKey0=processTagValue0', spans)).toEqual(new Set([spanID0]));
expect(filterSpans('processTagKey2=processTagValue1', spans)).toEqual(new Set([spanID2]));
});

it("should exclude span whose process.processTags' kv.value or kv.key match a filter if the key matches an excludeKey", () => {
expect(filterSpans('processTagValue1 -processTagKey2', spans)).toEqual(new Set([spanID0]));
expect(filterSpans('processTagValue1 -processTagKey1', spans)).toEqual(new Set([spanID2]));
Expand Down
6 changes: 4 additions & 2 deletions packages/jaeger-ui/src/utils/filter-spans.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ export default function filterSpans(textFilter: string, spans: Span[] | TNil) {
? kvs.some(kv => {
// ignore checking key and value for a match if key is in excludeKeys
if (isTextInFilters(excludeKeys, kv.key)) return false;
// match if key or value matches an item in includeFilters
// match if key, value or key=value string matches an item in includeFilters
return (
isTextInFilters(includeFilters, kv.key) || isTextInFilters(includeFilters, kv.value.toString())
isTextInFilters(includeFilters, kv.key) ||
isTextInFilters(includeFilters, kv.value.toString()) ||
isTextInFilters(includeFilters, `${kv.key}=${kv.value.toString()}`)
);
})
: false;
Expand Down