-
-
Notifications
You must be signed in to change notification settings - Fork 23
feat: filter spans on search #747
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
Conversation
- Removed the search context and related logic from SpanItem -Uplifted a search functionality in SpanTree to filter spans based on the query - Updated rendering logic to display only matching spans in the tree
@betegon is attempting to deploy a commit to the Sentry Team on Vercel. A member of the Team first needs to authorize it. |
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a pretty good start but I'm concerned about the efficiency.
I also would like to keep the "find & highlight" approach as @Shubhdeep12 mentioned too. Maybe we can add a toggle as he mentioned?
const matchesQuery = (span: Span): boolean | undefined => { | ||
return span.span_id.includes(query) || span.op?.includes(query) || span.description?.includes(query); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const matchesQuery = (span: Span): boolean | undefined => { | |
return span.span_id.includes(query) || span.op?.includes(query) || span.description?.includes(query); | |
}; | |
const matchesQuery = (span: Span): boolean | undefined => (span.span_id.includes(query) || span.op?.includes(query) || span.description?.includes(query)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also wonder if we can make useSearch()
return this matcher, wrapped in useCallback()
or something to make things more efficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! I also made sure that it returns a boolean:
const matchesQuery = useCallback(
(span: Span): boolean => {
const q = context.query.toLowerCase();
return (
(span.span_id.toLowerCase().includes(q) ||
span.op?.toLowerCase().includes(q) ||
span.description?.toLowerCase().includes(q)) ??
false
);
},
[context.query],
);
P.S. I added case-insensitive matching. I believe it’s one of the first things anyone would expect from a search feature.
const hasMatchingDescendant = (span: Span): boolean => { | ||
if (matchesQuery(span)) return true; | ||
if (!span.children) return false; | ||
return span.children.some(hasMatchingDescendant); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like we may want to cache this or do a more efficient depth-first search as we'd be scanning down the same spans as we do deeper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call! I’ve memoized each of the spans we process now.
if (showOnlyMatched) {
const spanMemo = new Map<string, boolean>();
const hasMatchingDescendant = (span: Span): boolean => {
if (spanMemo.has(span.span_id)) return spanMemo.get(span.span_id)!;
const result = matchesQuery(span) || (span.children?.some(child => hasMatchingDescendant(child)) ?? false);
spanMemo.set(span.span_id, result);
return result;
};
return tree.filter(span => hasMatchingDescendant(span));
}
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #747 +/- ##
==========================================
- Coverage 33.03% 32.81% -0.22%
==========================================
Files 93 93
Lines 5788 5838 +50
Branches 118 118
==========================================
+ Hits 1912 1916 +4
- Misses 3876 3922 +46 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Thanks for the detailed feedback, @BYK I’ve implemented your suggestions, including:
I think we’re in a better spot now, happy to keep iterating if you have more suggestions 😉 Here’s a quick screenshot video showing how the toggle works. Screen.Recording.2025-04-06.at.19.47.50.mp4 |
}`} | ||
onClick={() => setShowOnlyMatched(!showOnlyMatched)} | ||
> | ||
Only Show Matches |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to switch this into a checkbox and make the text shorter (or even use an icon with a very short label)
return span.span_id.includes(query) || span.op?.includes(query) || span.description?.includes(query); | ||
}, [query, span.span_id, span.op, span.description]); | ||
|
||
const isQueried = !showOnlyMatched && query && matchesQuery(span); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think renaming this to shouldHighlight
makes more sense with the new approach.
const isQueried = !showOnlyMatched && query && matchesQuery(span); | |
const shouldHighlight = !showOnlyMatched && query && matchesQuery(span); |
const { query, matchesQuery, showOnlyMatched } = useSearch(); | ||
|
||
const filteredTree = useMemo(() => { | ||
if (!query) return tree; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Early return here with showOnlyMatched
would make it easier to follow.
if (!query) return tree; | |
if (!query || !showOnlyMatched) return tree; |
const filteredTree = useMemo(() => { | ||
if (!query) return tree; | ||
if (showOnlyMatched) { | ||
const spanMemo = new Map<string, boolean>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implementation is not bad but I don't think the cache would be shared across children/siblings. I think this cache should be moved to useSearch()
level and get reset when query
changes.
} | ||
return tree; | ||
}, [query, tree, showOnlyMatched, matchesQuery]); | ||
|
||
if (!tree || !tree.length) return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we replace the tree
here with filteredTree
?
if (!tree || !tree.length) return null; | |
if (!filteredTree|| !tree.filteredTree) return null; |
const q = context.query.toLowerCase(); | ||
return ( | ||
(span.span_id.toLowerCase().includes(q) || | ||
span.op?.toLowerCase().includes(q) || | ||
span.description?.toLowerCase().includes(q)) ?? | ||
false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be more efficient if we create a:
const queryMatcher = new RegExp(RegExp.escape(query), 'i');
and then do queryMatcher.test(span.op)
etc. afterwards. You may need to polyfill RegExp.escape
as it doesn't seem to have wide browser support without flags.
Finally, a bit of a nitpick, we may wanna limit matching to only when the query is more than 2 characters as I don't want to match on a
or even ab
probably.
packages/sidecar/src/contextlines.ts
Outdated
@@ -1,8 +1,8 @@ | |||
import { LEAST_UPPER_BOUND, TraceMap, originalPositionFor, sourceContentFor } from '@jridgewell/trace-mapping'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not change any other files outside of the context of this patch. I'll revert this for you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we may wanna expand all trees when there's a query set and "only show matches" is selected as otherwise it gets a bit confusing when trying to figure out why a top-level span was not filtered (because it has some child matching the query but is collapsed so the child is not visible).
I also have all these notes but I think the PR is a net improvement so gonna approve, merge, and cut a new release and push the improvements to future releases.
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing). If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @spotlightjs/[email protected] ### Minor Changes - feat: Keep profile spans collapsed ([#737](#737)) ### Patch Changes - fix: Null-check sentryClient.\_options ([#736](#736)) - Updated dependencies \[[`3d569f30fb746d90ecabaac35d23d980360ea99c`](3d569f3), [`d3a2f0a0fae9074802b0551f3e1662833c1423c9`](d3a2f0a), [`d0315bc2ead9ccea722cc73c6b5fd7d9fed3f4a4`](d0315bc)]: - @spotlightjs/[email protected] ## @spotlightjs/[email protected] ### Minor Changes - feat: Keep profile spans collapsed ([#737](#737)) ### Patch Changes - fix: Null-check sentryClient.\_options ([#736](#736)) - Updated dependencies \[[`3d569f30fb746d90ecabaac35d23d980360ea99c`](3d569f3), [`7ea7de17b7ccf9f0c8edb8b413176d2f3482780c`](7ea7de1), [`263ba283efa247e05a022d7f1305cbf819e3e783`](263ba28), [`7ac9fd255cfe85d66e79d632e1d309c3e88d8092`](7ac9fd2), [`a76b2dadb28ce8699c80fc81b709050655bd4629`](a76b2da), [`d3a2f0a0fae9074802b0551f3e1662833c1423c9`](d3a2f0a), [`f2a48b05a41f80e08d1666247f7ccae60bc3d9e7`](f2a48b0), [`6d26f0d21b3ae75e3d81e48ceb2d8f727a94420f`](6d26f0d), [`a8f632357d9dcc46187b00724c14dd4423dfa467`](a8f6323), [`9888dbfc6778de910a2aeae9f3e86f0b2f716a18`](9888dbf), [`ced3e736dfef15d368cac202a10eec4eba7508be`](ced3e73)]: - @spotlightjs/[email protected] - @spotlightjs/[email protected] ## @spotlightjs/[email protected] ### Minor Changes - feat: Keep profile spans collapsed ([#737](#737)) - Swap "Errors" and "Explore" tab places, making "Explore" the default. It makes more sense this way as there is almost ([#758](#758)) guaranteed to have traces in a good setup but not really errors. - Restructured Sentry integration tabs and components ([#765](#765)) - - \#731: Search Bar sticky and fixed overflow across the overlay. ([#740](#740)) - Include filter functionality when searching spans ([#747](#747)) ### Patch Changes - Fixed parsing of envelope data. ([#751](#751)) - fix: Null-check sentryClient.\_options ([#736](#736)) - format node internals correctly in stacktrace ([#739](#739)) - Fixed #738 ([#741](#741)) ## @spotlightjs/[email protected] ### Minor Changes - feat: Keep profile spans collapsed ([#737](#737)) ### Patch Changes - fix: Null-check sentryClient.\_options ([#736](#736)) - Remove obsolete packages from dependencies ([#761](#761)) - Updated dependencies \[[`3d569f30fb746d90ecabaac35d23d980360ea99c`](3d569f3), [`7ea7de17b7ccf9f0c8edb8b413176d2f3482780c`](7ea7de1), [`263ba283efa247e05a022d7f1305cbf819e3e783`](263ba28), [`7ac9fd255cfe85d66e79d632e1d309c3e88d8092`](7ac9fd2), [`a76b2dadb28ce8699c80fc81b709050655bd4629`](a76b2da), [`d3a2f0a0fae9074802b0551f3e1662833c1423c9`](d3a2f0a), [`f2a48b05a41f80e08d1666247f7ccae60bc3d9e7`](f2a48b0), [`6d26f0d21b3ae75e3d81e48ceb2d8f727a94420f`](6d26f0d), [`a8f632357d9dcc46187b00724c14dd4423dfa467`](a8f6323), [`9888dbfc6778de910a2aeae9f3e86f0b2f716a18`](9888dbf), [`ced3e736dfef15d368cac202a10eec4eba7508be`](ced3e73)]: - @spotlightjs/[email protected] - @spotlightjs/[email protected] ## @spotlightjs/[email protected] ### Patch Changes - use nanosecond timestamp for captured filenames ([#776](#776)) - Improve DX by always showing the Spotlight URL, even if the sidecar was already running. Makes it easy to cmd/ctrl+click ([#748](#748)) and open in browser. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Related to #731
These changes add filtering to span search
SpanItem
toSpanTree
.The results display not only the direct matches but also their hierarchical context up to the root:
Since spans are time-sorted, it would be useful to add a feature like
+<#items>
between results. This would allow you to jump directly to the result you’re looking for when debugging. I’ve been using Spotlight for the past few days, and this addition would have been very helpful.Before opening this PR:
pnpm changeset:add