Repository navigation
fix: Stale waterfall selection when opening another span #3285
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
Changes from all commits
c0cf38d
d29dc33
00fae35
30a6b97
7c5d48f
5c7646c
a0ce39b
4602d88
cb5dde5
0c3862e
010f01e
a14431e
85d723f
b74f028
2a7f725
0d800d6
8b8ae5a
3491880
62829cc
a75720e
c7a6898
22833ce
2cf87e4
5b7cb58
011d4c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@hyperdx/app': patch | ||
| --- | ||
|
|
||
| Fix the trace waterfall sticking on the previously opened span when another span of the same trace is opened, including from a session replay. Closing the result clears the selected span. Opening a different result selects that result's span, including when the waterfall stays mounted on cached data. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import { | |
| } from 'react'; | ||
| import { add } from 'date-fns'; | ||
| import { isString } from 'lodash'; | ||
| import { useQueryState } from 'nuqs'; | ||
| import { ErrorBoundary, FallbackProps } from 'react-error-boundary'; | ||
| import { useHotkeys } from 'react-hotkeys-hook'; | ||
| import SqlString from 'sqlstring'; | ||
|
|
@@ -37,6 +38,7 @@ import { | |
| import { IconCopy, IconKeyboard, IconShare, IconX } from '@tabler/icons-react'; | ||
|
|
||
| import { useCloseOnClickOutside } from '@/hooks/useCloseOnClickOutside'; | ||
| import { useOpenedRowSpanSelection } from '@/hooks/useOpenedRowSpanSelection'; | ||
| import useResizable from '@/hooks/useResizable'; | ||
| import { WithClause } from '@/hooks/useRowWhere'; | ||
| import useSidePanelStack, { | ||
|
|
@@ -77,6 +79,7 @@ import { | |
| DrawerFullWidthToggle, | ||
| INITIAL_DRAWER_WIDTH_PERCENT, | ||
| } from './DrawerUtils'; | ||
| import { eventRowWhereParser } from './eventRowWhere'; | ||
| import LogLevel from './LogLevel'; | ||
| import SidePanelBreadcrumbs, { BreadcrumbItem } from './SidePanelBreadcrumbs'; | ||
| import { SpanLinkData } from './SpanLinksSubpanel'; | ||
|
|
@@ -203,6 +206,7 @@ function SidePanelHeaderActions({ | |
| size="sm" | ||
| onClick={onClose} | ||
| aria-label="Close" | ||
| data-testid="side-panel-close" | ||
| > | ||
| <IconX size={16} /> | ||
| </ActionIcon> | ||
|
|
@@ -407,18 +411,22 @@ export const DBRowSidePanelInner = ({ | |
| [pushSource], | ||
| ); | ||
|
|
||
| const clearSpanSelection = useOpenedRowSpanSelection(initialRowId); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 major — Span selection is reset only when the root rowId changes, so stack navigation still shows a stale span
Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 major — Span reset is keyed on the root row id, so in-panel navigation to another row in the same trace still shows the old span The hint is built from Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Context Used: CLAUDE.md (source) Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 major — Span selection is cleared only when the root row changes, so drilldowns inside the panel still show the previous span
Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 major — Navigating to another row inside the panel doesn't clear the span, so the stale-span bug comes back
Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 major — Span selection is cleared only when the root row changes, not when in-panel navigation changes the row on screen The hook is keyed on Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond |
||
|
|
||
| const handlePanelBack = useCallback(() => { | ||
| // Pop one level (nav → source), restoring the tab active before that level | ||
| // was entered. When the trail is empty, leave the panel: hand off to an | ||
| // embedding parent (session) or close. | ||
| // embedding parent (session) or close. The search drawer clears the span | ||
| // in its own close handler; a parent (the session) does not. | ||
| if (popOne() === 'none') { | ||
| if (onNavigateToParent) { | ||
| clearSpanSelection(); | ||
| onNavigateToParent(); | ||
| } else { | ||
| onClose(); | ||
| } | ||
| } | ||
| }, [popOne, onNavigateToParent, onClose]); | ||
| }, [popOne, onNavigateToParent, onClose, clearSpanSelection]); | ||
|
|
||
| useHotkeys(['esc'], handlePanelBack); | ||
|
|
||
|
|
@@ -760,7 +768,23 @@ export const DBRowSidePanelInner = ({ | |
| const items: BreadcrumbItem[] = []; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — The inner panel wraps the parent's callbacks to clear the span on the session's behalf Both Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Clearing on return to the session is done in the child by wrapping the parent's callbacks The only parent that passes Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond |
||
|
|
||
| if (parentBreadcrumbs) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Session span clearing is spread across Inner instead of living in SessionSidePanel.handleBackToSession The new Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Session-only cleanup is spread into the generic inner panel instead of SessionSidePanel is the only caller that passes Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond |
||
| items.push(...parentBreadcrumbs); | ||
| // A parent crumb (the session name) leaves this row without going | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Inner panel wraps the parent's breadcrumb and back callbacks to clear the span for the parent
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Span clearing for 'leave to session' is added in DBRowSidePanelInner by wrapping callbacks the parent passes in, when the parent already has a single handler for it Only Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond |
||
| // through Back or Close. Clear the span on that click too. | ||
| items.push( | ||
| ...parentBreadcrumbs.map(item => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Wrapping parent breadcrumb / onNavigateToParent to clear the span adds nothing and puts session logic in the shared inner panel In Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Clearing the span when leaving a session event lives in the wrong component
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond |
||
| const onClick = item.onClick; | ||
| if (onClick == null) { | ||
| return item; | ||
| } | ||
| return { | ||
| ...item, | ||
| onClick: () => { | ||
| clearSpanSelection(); | ||
| onClick(); | ||
| }, | ||
| }; | ||
| }), | ||
| ); | ||
| } | ||
|
|
||
| const crumbSourceStack = sourceStack; | ||
|
|
@@ -822,6 +846,7 @@ export const DBRowSidePanelInner = ({ | |
| source.kind, | ||
| handleBreadcrumbNavigation, | ||
| parentBreadcrumbs, | ||
| clearSpanSelection, | ||
| ]); | ||
|
|
||
| const availableTabs = useMemo<Tab[]>(() => { | ||
|
|
@@ -1327,6 +1352,10 @@ export default function DBRowSidePanelErrorBoundary({ | |
| }, [isFullWidth, setSize]); | ||
|
|
||
| const { clear: clearTraceWaterfallSearchState } = useWaterfallSearchState({}); | ||
| const [, setEventRowWhere] = useQueryState( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — _onClose sets up its own eventRowWhere setter instead of using the new useClearSpanSelection This PR adds Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Drawer close re-implements the span-clearing hook this PR adds
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — The close handler sets up its own Replace the local Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Close handler clears the span with its own useQueryState instead of the new useClearSpanSelection hook
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Close handler re-implements
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — The drawer close handler repeats
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Second hand-rolled eventRowWhere setter instead of the new useClearSpanSelection hook
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — The close handler sets up its own eventRowWhere setter instead of using the new useClearSpanSelection hook This PR adds Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Drawer close sets up its own Replace the local Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond |
||
| 'eventRowWhere', | ||
| eventRowWhereParser, | ||
| ); | ||
|
|
||
| const sidePanelStack = useSidePanelStack({ initialRowId: rowId }); | ||
|
|
||
|
|
@@ -1336,8 +1365,16 @@ export default function DBRowSidePanelErrorBoundary({ | |
| // Clear waterfall search state on close, so that filters don't | ||
| // persist when reopening another trace. | ||
| clearTraceWaterfallSearchState(); | ||
| // The selected span is in the URL. Leaving it there makes the next open, | ||
| // and any shared row link, show the previous span. | ||
| setEventRowWhere(null); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Span-selection clearing is spread across three call sites and runs twice on normal close With the boundary, the X button runs Inner's Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond |
||
| onClose(); | ||
| }, [sidePanelStack, onClose, clearTraceWaterfallSearchState]); | ||
| }, [ | ||
| sidePanelStack, | ||
| onClose, | ||
| clearTraceWaterfallSearchState, | ||
| setEventRowWhere, | ||
| ]); | ||
|
|
||
| useCloseOnClickOutside({ | ||
| // Only close on outside click at the root level. When the user has | ||
|
|
||
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.
🟠 major — Clicking the session breadcrumb leaves the old span selected, so the session replay bug still happens
The span is only cleared in
handlePanelBack(Esc and the Back button) and inclosePanel. Inpackages/app/src/SessionSidePanel.tsx:236-241, the session breadcrumb passesonClick: handleBackToSessionstraight through toparentBreadcrumbs, and that path never callsclearSpanSelection. Here is how it breaks. Open event A from a replay; the waterfall selects span A. Click the session breadcrumb:selectedEventbecomes null andDBRowSidePanelInnerunmounts. Open event B in the same trace.DBRowSidePanelInnermounts again, andopenedRowIdRefstarts out as B, souseOpenedRowSpanSelectionnever clears.useRowHighlightHintthen treats span A as a restored selection on its first hint and keeps it. Fix: cleareventRowWhereinSessionSidePanel'shandleBackToSession(andhandleEventNavigate). Another option is to wrap theonClickof eachparentBreadcrumbsitem in DBRowSidePanelInner withclearSpanSelection. Then add a test for the breadcrumb path.Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond