From c301cd393a9618d97d400585aff5da9e89cfe2bd Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Tue, 7 Apr 2026 14:15:27 -0600 Subject: [PATCH 1/6] fix(app): fix session replay sub-event modal stacking and tab conflict When clicking a log/error event inside the session replay event list, the detail panel was opening behind the session replay drawer (z-index issue) and, when opened from the search page's Session Replay tab, was re-opening the session replay instead of showing event details (URL param conflict). Root causes: - SessionSubpanel rendered DBRowSidePanel via without wrapping it in ZIndexContext.Provider, so the sub-panel did not inherit the correct z-index value from the parent session replay drawer. - DBRowSidePanel was not passed isNestedPanel=true, causing it to read the sidePanelTab URL param (set to 'replay' by the outer panel) and open to the Session Replay tab again instead of event details. - withOverlay={!isNestedPanel} removed the overlay on nested panels, making it impossible to close by clicking outside. Fixes: - Wrap Portal-rendered DBRowSidePanel with ZIndexContext.Provider to ensure correct z-index propagation through the Portal boundary. - Pass isNestedPanel=true so the inner panel uses local state for tab selection (ignoring the URL param) and renders with an overlay. - Remove withOverlay={!isNestedPanel} so all drawer instances always render with an overlay (click-outside-to-close works). Also adds: - data-testid on SessionEventList rows and SessionSidePanel for testability. - data-testid on the Session Replay tab content wrapper in DBRowSidePanel. - E2E test that reproduces the exact URL conflict regression by injecting sidePanelTab=replay into the URL before opening a session event, then asserting the detail panel does not open to the replay tab. - Seed data: session traces now include routeChange and console.error events so the session event list is populated in E2E tests. --- packages/app/package.json | 2 +- packages/app/src/SessionEventList.tsx | 1 + packages/app/src/SessionSidePanel.tsx | 5 +- packages/app/src/SessionSubpanel.tsx | 24 +++-- .../app/src/components/DBRowSidePanel.tsx | 6 +- .../app/tests/e2e/features/sessions.spec.ts | 64 +++++++++++ .../tests/e2e/page-objects/SessionsPage.ts | 28 +++++ packages/app/tests/e2e/seed-clickhouse.ts | 20 ++++ yarn.lock | 101 ++++++++++++++++-- 9 files changed, 229 insertions(+), 22 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 1710c36a4e..437652c4e1 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -132,7 +132,7 @@ "eslint-plugin-playwright": "^2.4.0", "eslint-plugin-react-hook-form": "^0.3.1", "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-storybook": "10.1.4", + "eslint-plugin-storybook": "^10.3.3", "identity-obj-proxy": "^3.0.0", "jest": "^30.2.0", "jest-environment-jsdom": "^30.2.0", diff --git a/packages/app/src/SessionEventList.tsx b/packages/app/src/SessionEventList.tsx index 5accc571b5..7134f44a95 100644 --- a/packages/app/src/SessionEventList.tsx +++ b/packages/app/src/SessionEventList.tsx @@ -62,6 +62,7 @@ const EventRow = React.forwardRef( return (
-
+
diff --git a/packages/app/src/SessionSubpanel.tsx b/packages/app/src/SessionSubpanel.tsx index f5a302f162..2032d4eb01 100644 --- a/packages/app/src/SessionSubpanel.tsx +++ b/packages/app/src/SessionSubpanel.tsx @@ -35,6 +35,7 @@ import { import DBRowSidePanel from '@/components/DBRowSidePanel'; import { RowWhereResult, WithClause } from '@/hooks/useRowWhere'; +import { useZIndex, ZIndexContext } from '@/zIndex'; import SearchWhereInput from './components/SearchInput/SearchWhereInput'; import useFieldExpressionGenerator from './hooks/useFieldExpressionGenerator'; @@ -267,6 +268,8 @@ export default function SessionSubpanel({ whereLanguage?: SearchConditionLanguage; onLanguageChange?: (lang: 'sql' | 'lucene') => void; }) { + const contextZIndex = useZIndex(); + const [rowId, setRowId] = useState(undefined); const [aliasWith, setAliasWith] = useState([]); @@ -466,15 +469,18 @@ export default function SessionSubpanel({
{rowId != null && traceSource && ( - { - setDrawerOpen(false); - setRowId(undefined); - }} - /> + + { + setDrawerOpen(false); + setRowId(undefined); + }} + /> + )}
diff --git a/packages/app/src/components/DBRowSidePanel.tsx b/packages/app/src/components/DBRowSidePanel.tsx index adf027db4f..97f18b5d61 100644 --- a/packages/app/src/components/DBRowSidePanel.tsx +++ b/packages/app/src/components/DBRowSidePanel.tsx @@ -506,9 +506,11 @@ const DBRowSidePanel = ({
)} > -
+
{ await sessionsPage.openFirstSession(); }); }); + + test( + 'clicking a session event opens the event detail panel with tabs, not another session replay', + { tag: ['@full-stack'] }, + async ({ page }) => { + await test.step('Navigate and open a session (with sidePanelTab=replay pre-set in URL to simulate search-page flow)', async () => { + // Pre-set sidePanelTab=replay in the URL to simulate navigating from a search page + // row detail panel that had the Session Replay tab open. Without isNestedPanel=true, + // the inner DBRowSidePanel would inherit this URL param and open to the Replay tab again. + await page.goto('/search'); + await sessionsPage.goto(); + await sessionsPage.selectDataSource(); + await expect(sessionsPage.getFirstSessionCard()).toBeVisible(); + // Inject sidePanelTab=replay into the URL before opening the session + const currentUrl = page.url(); + await page.goto( + currentUrl.includes('?') + ? `${currentUrl}&sidePanelTab=replay` + : `${currentUrl}?sidePanelTab=replay`, + ); + await expect(sessionsPage.getFirstSessionCard()).toBeVisible(); + await sessionsPage.openFirstSession(); + }); + + await test.step('Wait for session replay drawer and event rows to load', async () => { + await expect(sessionsPage.sessionSidePanel).toBeVisible(); + // Wait for the session event list to populate (routeChange/console.error events are seeded) + await expect(sessionsPage.getSessionEventRows().first()).toBeVisible({ + timeout: 15000, + }); + }); + + await test.step('Click a session event row', async () => { + await sessionsPage.clickFirstSessionEvent(); + }); + + await test.step('Event detail panel opens alongside the session replay — not replacing it', async () => { + // The row-side-panel must be visible (event detail drawer opened on top of session replay) + await expect(sessionsPage.rowSidePanel).toBeVisible(); + + // The original session replay panel must still be open (not replaced/closed) + await expect(sessionsPage.sessionSidePanel).toBeVisible(); + + // Only one session-side-panel must exist (not a second replay opened inside the detail panel) + await expect(page.getByTestId('session-side-panel')).toHaveCount(1); + + // The row-side-panel must show the event detail TabBar (Overview, Trace, etc.) + // This guards against the regression where the inner panel re-opened session replay + // instead of showing event details (which has no TabBar, just the replay player) + await expect( + sessionsPage.rowSidePanel.getByTestId('side-panel-tabs'), + ).toBeVisible(); + + // The inner panel must NOT be showing the Session Replay tab content. + // Without isNestedPanel=true (broken), the inner DBRowSidePanel reads sidePanelTab=replay + // from the URL (injected above) and renders the Session Replay tab content (side-panel-tab-replay). + // With isNestedPanel=true (fixed), the inner panel uses local state and ignores the URL, + // opening to its default tab (Trace/Overview) instead. + await expect( + sessionsPage.rowSidePanel.getByTestId('side-panel-tab-replay'), + ).toHaveCount(0); + }); + }, + ); }); diff --git a/packages/app/tests/e2e/page-objects/SessionsPage.ts b/packages/app/tests/e2e/page-objects/SessionsPage.ts index 33d7d7e9a8..c664b41044 100644 --- a/packages/app/tests/e2e/page-objects/SessionsPage.ts +++ b/packages/app/tests/e2e/page-objects/SessionsPage.ts @@ -68,6 +68,34 @@ export class SessionsPage { await this.getFirstSessionCard().click(); } + /** + * Get the session side panel (the replay drawer) + */ + get sessionSidePanel() { + return this.page.getByTestId('session-side-panel'); + } + + /** + * Get all session event rows inside the replay drawer + */ + getSessionEventRows() { + return this.page.locator('[data-testid^="session-event-row-"]'); + } + + /** + * Click the first session event row to open its detail panel + */ + async clickFirstSessionEvent() { + await this.getSessionEventRows().first().click(); + } + + /** + * Get the row side panel (event detail drawer opened from within session replay) + */ + get rowSidePanel() { + return this.page.getByTestId('row-side-panel'); + } + // Getters for assertions get form() { diff --git a/packages/app/tests/e2e/seed-clickhouse.ts b/packages/app/tests/e2e/seed-clickhouse.ts index ee90a4db1e..f938d1b962 100644 --- a/packages/app/tests/e2e/seed-clickhouse.ts +++ b/packages/app/tests/e2e/seed-clickhouse.ts @@ -321,6 +321,26 @@ function generateSessionTraces( `('${timestampNs}', '${traceId}', '${spanId}', '', '', '${spanName}', 'SPAN_KIND_INTERNAL', 'browser', {'rum.sessionId':'${sessionId}','service.name':'browser'}, '', '', {'component':'${component}','page.url':'https://example.com/dashboard','teamId':'${teamId}','teamName':'${teamName}','userEmail':'${userEmail}','userName':'${userName}'}, 0, '${statusCode}', '', [], [], [], [], [], [], [])`, ); } + + // Add visible events for the session event list: + // - A routeChange (navigation) event — shown in both Highlighted and All Events tabs + // - A console.error event — shown in both tabs + const navTimestampNs = (baseTime - 1000) * 1000000; + const navTraceId = `session-nav-${i}`; + const navSpanId = `session-nav-span-${i}`; + const userIndex = i % 5; + const userEmail = `test${userIndex}@example.com`; + const userName = `Test User ${userIndex}`; + rows.push( + `('${navTimestampNs}', '${navTraceId}', '${navSpanId}', '', '', 'routeChange', 'SPAN_KIND_INTERNAL', 'browser', {'rum.sessionId':'${sessionId}','service.name':'browser'}, '', '', {'component':'navigation','location.href':'https://example.com/dashboard','teamId':'test-team-id','teamName':'Test Team','userEmail':'${userEmail}','userName':'${userName}'}, 0, 'STATUS_CODE_OK', '', [], [], [], [], [], [], [])`, + ); + + const errTimestampNs = (baseTime - 2000) * 1000000; + const errTraceId = `session-err-${i}`; + const errSpanId = `session-err-span-${i}`; + rows.push( + `('${errTimestampNs}', '${errTraceId}', '${errSpanId}', '', '', 'console.error', 'SPAN_KIND_INTERNAL', 'browser', {'rum.sessionId':'${sessionId}','service.name':'browser'}, '', '', {'component':'error','message':'E2E test error ${i}','teamId':'test-team-id','teamName':'Test Team','userEmail':'${userEmail}','userName':'${userName}'}, 0, 'STATUS_CODE_ERROR', '', [], [], [], [], [], [], [])`, + ); } return rows.join(',\n'); diff --git a/yarn.lock b/yarn.lock index 5c1050e8d2..d840b83fc8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4366,7 +4366,7 @@ __metadata: eslint-plugin-playwright: "npm:^2.4.0" eslint-plugin-react-hook-form: "npm:^0.3.1" eslint-plugin-react-hooks: "npm:^7.0.1" - eslint-plugin-storybook: "npm:10.1.4" + eslint-plugin-storybook: "npm:^10.3.3" flat: "npm:^5.0.2" fuse.js: "npm:^6.6.2" http-proxy-middleware: "npm:^3.0.5" @@ -10340,6 +10340,19 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/project-service@npm:8.58.0": + version: 8.58.0 + resolution: "@typescript-eslint/project-service@npm:8.58.0" + dependencies: + "@typescript-eslint/tsconfig-utils": "npm:^8.58.0" + "@typescript-eslint/types": "npm:^8.58.0" + debug: "npm:^4.4.3" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/e6d0cb2f7708ccb31a2ff9eb35817d4999c26e1f1cd3c607539e21d0c73a234daa77c73ee1163bc4e8b139252d619823c444759f1ddabdd138cab4885e9c9794 + languageName: node + linkType: hard + "@typescript-eslint/scope-manager@npm:8.48.1": version: 8.48.1 resolution: "@typescript-eslint/scope-manager@npm:8.48.1" @@ -10360,6 +10373,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:8.58.0": + version: 8.58.0 + resolution: "@typescript-eslint/scope-manager@npm:8.58.0" + dependencies: + "@typescript-eslint/types": "npm:8.58.0" + "@typescript-eslint/visitor-keys": "npm:8.58.0" + checksum: 10c0/bd5c16780f22d62359af0f69909f38a15fa3c55e609124a7cd5c2a04322fe41e586d81066f3ad1dcc3c1eff24dbcb48b78d099626d611fbd680c20c005d48f1d + languageName: node + linkType: hard + "@typescript-eslint/tsconfig-utils@npm:8.48.1, @typescript-eslint/tsconfig-utils@npm:^8.48.1": version: 8.48.1 resolution: "@typescript-eslint/tsconfig-utils@npm:8.48.1" @@ -10378,6 +10401,15 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/tsconfig-utils@npm:8.58.0, @typescript-eslint/tsconfig-utils@npm:^8.58.0": + version: 8.58.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.58.0" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/0a07fe1a28b2513e625882bc8d4c4e0c5a105cdbcb987beae12fc66dbe71dc9638013e4d1fa8ad10d828a2acd5e3fed987c189c00d41fed0e880009f99adf1b2 + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:8.48.1": version: 8.48.1 resolution: "@typescript-eslint/type-utils@npm:8.48.1" @@ -10424,6 +10456,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:8.58.0, @typescript-eslint/types@npm:^8.58.0": + version: 8.58.0 + resolution: "@typescript-eslint/types@npm:8.58.0" + checksum: 10c0/f2fe1321758a04591c20d77caba956ae76b77cff0b976a0224b37077d80b1ebd826874d15ec79c3a3b7d57ee5679e5d10756db1b082bde3d51addbd3a8431d38 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:8.48.1": version: 8.48.1 resolution: "@typescript-eslint/typescript-estree@npm:8.48.1" @@ -10462,7 +10501,26 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.48.1, @typescript-eslint/utils@npm:^8.8.1": +"@typescript-eslint/typescript-estree@npm:8.58.0": + version: 8.58.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.58.0" + dependencies: + "@typescript-eslint/project-service": "npm:8.58.0" + "@typescript-eslint/tsconfig-utils": "npm:8.58.0" + "@typescript-eslint/types": "npm:8.58.0" + "@typescript-eslint/visitor-keys": "npm:8.58.0" + debug: "npm:^4.4.3" + minimatch: "npm:^10.2.2" + semver: "npm:^7.7.3" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.5.0" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/a8cb94cb765b27740a54f9b5378bd8f0dc49e301ceed99a0791dc9d1f61c2a54e3212f7ed9120c8c2df80104ad3117150cf5e7fe8a0b7eec3ed04969a79b103e + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:8.48.1": version: 8.48.1 resolution: "@typescript-eslint/utils@npm:8.48.1" dependencies: @@ -10492,6 +10550,21 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:^8.48.0": + version: 8.58.0 + resolution: "@typescript-eslint/utils@npm:8.58.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.58.0" + "@typescript-eslint/types": "npm:8.58.0" + "@typescript-eslint/typescript-estree": "npm:8.58.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/457e01a6e6d954dbfe13c49ece3cf8a55e5d8cf19ea9ae7086c0e205d89e3cdbb91153062ab440d2e78ad3f077b174adc42bfb1b6fc24299020a0733e7f9c11c + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:8.48.1": version: 8.48.1 resolution: "@typescript-eslint/visitor-keys@npm:8.48.1" @@ -10512,6 +10585,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:8.58.0": + version: 8.58.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.58.0" + dependencies: + "@typescript-eslint/types": "npm:8.58.0" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10c0/75f3c9c097a308cc6450822a0f81d44c8b79b524e99dd2c41ded347b12f148ab3bd459ce9cc6bd00f8f0725c5831baab6d2561596ead3394ab76dddbeb32cce1 + languageName: node + linkType: hard + "@uiw/codemirror-extensions-basic-setup@npm:4.23.3": version: 4.23.3 resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.3" @@ -15500,15 +15583,15 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-storybook@npm:10.1.4": - version: 10.1.4 - resolution: "eslint-plugin-storybook@npm:10.1.4" +"eslint-plugin-storybook@npm:^10.3.3": + version: 10.3.3 + resolution: "eslint-plugin-storybook@npm:10.3.3" dependencies: - "@typescript-eslint/utils": "npm:^8.8.1" + "@typescript-eslint/utils": "npm:^8.48.0" peerDependencies: eslint: ">=8" - storybook: ^10.1.4 - checksum: 10c0/d68a0244318a386877de12d22ce309725f9f7057002f5182fa50fc965e26ced7d051432d7495046f13ee5803634ce53ef9e58cfe2866c3251f2ff94f8ab50e74 + storybook: ^10.3.3 + checksum: 10c0/501a07db230aefa5bb76882fe7b0a3e9a5db87fc29bbcc96b25e880a2ee97a81ff871cf364cb09e9ed9b67bc7d6cd0541755fd0ac778d3b68124289a4fdecde4 languageName: node linkType: hard @@ -27148,7 +27231,7 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.4.0": +"ts-api-utils@npm:^2.4.0, ts-api-utils@npm:^2.5.0": version: 2.5.0 resolution: "ts-api-utils@npm:2.5.0" peerDependencies: From 37b547cd2edfd2d7106543d3d1aadfe649ba3050 Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Tue, 7 Apr 2026 14:17:26 -0600 Subject: [PATCH 2/6] add changeset --- .changeset/tender-monkeys-drop.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tender-monkeys-drop.md diff --git a/.changeset/tender-monkeys-drop.md b/.changeset/tender-monkeys-drop.md new file mode 100644 index 0000000000..9692bb8a4a --- /dev/null +++ b/.changeset/tender-monkeys-drop.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": patch +--- + +Fix bug when accessing session replay panel from search page From 83d4098159d57a718e4c8f4929e41e2910556f38 Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Tue, 7 Apr 2026 14:40:49 -0600 Subject: [PATCH 3/6] revert package change --- packages/app/package.json | 2 +- yarn.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 437652c4e1..1710c36a4e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -132,7 +132,7 @@ "eslint-plugin-playwright": "^2.4.0", "eslint-plugin-react-hook-form": "^0.3.1", "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-storybook": "^10.3.3", + "eslint-plugin-storybook": "10.1.4", "identity-obj-proxy": "^3.0.0", "jest": "^30.2.0", "jest-environment-jsdom": "^30.2.0", diff --git a/yarn.lock b/yarn.lock index d840b83fc8..f5e8d893f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4366,7 +4366,7 @@ __metadata: eslint-plugin-playwright: "npm:^2.4.0" eslint-plugin-react-hook-form: "npm:^0.3.1" eslint-plugin-react-hooks: "npm:^7.0.1" - eslint-plugin-storybook: "npm:^10.3.3" + eslint-plugin-storybook: "npm:10.1.4" flat: "npm:^5.0.2" fuse.js: "npm:^6.6.2" http-proxy-middleware: "npm:^3.0.5" @@ -10550,7 +10550,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:^8.48.0": +"@typescript-eslint/utils@npm:^8.8.1": version: 8.58.0 resolution: "@typescript-eslint/utils@npm:8.58.0" dependencies: @@ -15583,15 +15583,15 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-storybook@npm:^10.3.3": - version: 10.3.3 - resolution: "eslint-plugin-storybook@npm:10.3.3" +"eslint-plugin-storybook@npm:10.1.4": + version: 10.1.4 + resolution: "eslint-plugin-storybook@npm:10.1.4" dependencies: - "@typescript-eslint/utils": "npm:^8.48.0" + "@typescript-eslint/utils": "npm:^8.8.1" peerDependencies: eslint: ">=8" - storybook: ^10.3.3 - checksum: 10c0/501a07db230aefa5bb76882fe7b0a3e9a5db87fc29bbcc96b25e880a2ee97a81ff871cf364cb09e9ed9b67bc7d6cd0541755fd0ac778d3b68124289a4fdecde4 + storybook: ^10.1.4 + checksum: 10c0/d68a0244318a386877de12d22ce309725f9f7057002f5182fa50fc965e26ced7d051432d7495046f13ee5803634ce53ef9e58cfe2866c3251f2ff94f8ab50e74 languageName: node linkType: hard From 656e6582615b7fc24f37d88eaae22610c7b0db24 Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Tue, 7 Apr 2026 14:41:52 -0600 Subject: [PATCH 4/6] chore: revert yarn.lock to main --- yarn.lock | 87 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 85 deletions(-) diff --git a/yarn.lock b/yarn.lock index f5e8d893f8..5c1050e8d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10340,19 +10340,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.58.0": - version: 8.58.0 - resolution: "@typescript-eslint/project-service@npm:8.58.0" - dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.58.0" - "@typescript-eslint/types": "npm:^8.58.0" - debug: "npm:^4.4.3" - peerDependencies: - typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/e6d0cb2f7708ccb31a2ff9eb35817d4999c26e1f1cd3c607539e21d0c73a234daa77c73ee1163bc4e8b139252d619823c444759f1ddabdd138cab4885e9c9794 - languageName: node - linkType: hard - "@typescript-eslint/scope-manager@npm:8.48.1": version: 8.48.1 resolution: "@typescript-eslint/scope-manager@npm:8.48.1" @@ -10373,16 +10360,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.58.0": - version: 8.58.0 - resolution: "@typescript-eslint/scope-manager@npm:8.58.0" - dependencies: - "@typescript-eslint/types": "npm:8.58.0" - "@typescript-eslint/visitor-keys": "npm:8.58.0" - checksum: 10c0/bd5c16780f22d62359af0f69909f38a15fa3c55e609124a7cd5c2a04322fe41e586d81066f3ad1dcc3c1eff24dbcb48b78d099626d611fbd680c20c005d48f1d - languageName: node - linkType: hard - "@typescript-eslint/tsconfig-utils@npm:8.48.1, @typescript-eslint/tsconfig-utils@npm:^8.48.1": version: 8.48.1 resolution: "@typescript-eslint/tsconfig-utils@npm:8.48.1" @@ -10401,15 +10378,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.58.0, @typescript-eslint/tsconfig-utils@npm:^8.58.0": - version: 8.58.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.58.0" - peerDependencies: - typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/0a07fe1a28b2513e625882bc8d4c4e0c5a105cdbcb987beae12fc66dbe71dc9638013e4d1fa8ad10d828a2acd5e3fed987c189c00d41fed0e880009f99adf1b2 - languageName: node - linkType: hard - "@typescript-eslint/type-utils@npm:8.48.1": version: 8.48.1 resolution: "@typescript-eslint/type-utils@npm:8.48.1" @@ -10456,13 +10424,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.58.0, @typescript-eslint/types@npm:^8.58.0": - version: 8.58.0 - resolution: "@typescript-eslint/types@npm:8.58.0" - checksum: 10c0/f2fe1321758a04591c20d77caba956ae76b77cff0b976a0224b37077d80b1ebd826874d15ec79c3a3b7d57ee5679e5d10756db1b082bde3d51addbd3a8431d38 - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:8.48.1": version: 8.48.1 resolution: "@typescript-eslint/typescript-estree@npm:8.48.1" @@ -10501,26 +10462,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.58.0": - version: 8.58.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.58.0" - dependencies: - "@typescript-eslint/project-service": "npm:8.58.0" - "@typescript-eslint/tsconfig-utils": "npm:8.58.0" - "@typescript-eslint/types": "npm:8.58.0" - "@typescript-eslint/visitor-keys": "npm:8.58.0" - debug: "npm:^4.4.3" - minimatch: "npm:^10.2.2" - semver: "npm:^7.7.3" - tinyglobby: "npm:^0.2.15" - ts-api-utils: "npm:^2.5.0" - peerDependencies: - typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/a8cb94cb765b27740a54f9b5378bd8f0dc49e301ceed99a0791dc9d1f61c2a54e3212f7ed9120c8c2df80104ad3117150cf5e7fe8a0b7eec3ed04969a79b103e - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:8.48.1": +"@typescript-eslint/utils@npm:8.48.1, @typescript-eslint/utils@npm:^8.8.1": version: 8.48.1 resolution: "@typescript-eslint/utils@npm:8.48.1" dependencies: @@ -10550,21 +10492,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:^8.8.1": - version: 8.58.0 - resolution: "@typescript-eslint/utils@npm:8.58.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.9.1" - "@typescript-eslint/scope-manager": "npm:8.58.0" - "@typescript-eslint/types": "npm:8.58.0" - "@typescript-eslint/typescript-estree": "npm:8.58.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/457e01a6e6d954dbfe13c49ece3cf8a55e5d8cf19ea9ae7086c0e205d89e3cdbb91153062ab440d2e78ad3f077b174adc42bfb1b6fc24299020a0733e7f9c11c - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:8.48.1": version: 8.48.1 resolution: "@typescript-eslint/visitor-keys@npm:8.48.1" @@ -10585,16 +10512,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.58.0": - version: 8.58.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.58.0" - dependencies: - "@typescript-eslint/types": "npm:8.58.0" - eslint-visitor-keys: "npm:^5.0.0" - checksum: 10c0/75f3c9c097a308cc6450822a0f81d44c8b79b524e99dd2c41ded347b12f148ab3bd459ce9cc6bd00f8f0725c5831baab6d2561596ead3394ab76dddbeb32cce1 - languageName: node - linkType: hard - "@uiw/codemirror-extensions-basic-setup@npm:4.23.3": version: 4.23.3 resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.3" @@ -27231,7 +27148,7 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.4.0, ts-api-utils@npm:^2.5.0": +"ts-api-utils@npm:^2.4.0": version: 2.5.0 resolution: "ts-api-utils@npm:2.5.0" peerDependencies: From a6ec20affa73bdd113d1432baed8c7d097c7b3f5 Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Tue, 7 Apr 2026 15:01:29 -0600 Subject: [PATCH 5/6] claude feedback --- packages/app/tests/e2e/seed-clickhouse.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/app/tests/e2e/seed-clickhouse.ts b/packages/app/tests/e2e/seed-clickhouse.ts index f938d1b962..3388561139 100644 --- a/packages/app/tests/e2e/seed-clickhouse.ts +++ b/packages/app/tests/e2e/seed-clickhouse.ts @@ -325,21 +325,24 @@ function generateSessionTraces( // Add visible events for the session event list: // - A routeChange (navigation) event — shown in both Highlighted and All Events tabs // - A console.error event — shown in both tabs + const sessionUserIndex = i % 5; + const sessionUserEmail = `test${sessionUserIndex}@example.com`; + const sessionUserName = `Test User ${sessionUserIndex}`; + const sessionTeamId = 'test-team-id'; + const sessionTeamName = 'Test Team'; + const navTimestampNs = (baseTime - 1000) * 1000000; const navTraceId = `session-nav-${i}`; const navSpanId = `session-nav-span-${i}`; - const userIndex = i % 5; - const userEmail = `test${userIndex}@example.com`; - const userName = `Test User ${userIndex}`; rows.push( - `('${navTimestampNs}', '${navTraceId}', '${navSpanId}', '', '', 'routeChange', 'SPAN_KIND_INTERNAL', 'browser', {'rum.sessionId':'${sessionId}','service.name':'browser'}, '', '', {'component':'navigation','location.href':'https://example.com/dashboard','teamId':'test-team-id','teamName':'Test Team','userEmail':'${userEmail}','userName':'${userName}'}, 0, 'STATUS_CODE_OK', '', [], [], [], [], [], [], [])`, + `('${navTimestampNs}', '${navTraceId}', '${navSpanId}', '', '', 'routeChange', 'SPAN_KIND_INTERNAL', 'browser', {'rum.sessionId':'${sessionId}','service.name':'browser'}, '', '', {'component':'navigation','location.href':'https://example.com/dashboard','teamId':'${sessionTeamId}','teamName':'${sessionTeamName}','userEmail':'${sessionUserEmail}','userName':'${sessionUserName}'}, 0, 'STATUS_CODE_OK', '', [], [], [], [], [], [], [])`, ); const errTimestampNs = (baseTime - 2000) * 1000000; const errTraceId = `session-err-${i}`; const errSpanId = `session-err-span-${i}`; rows.push( - `('${errTimestampNs}', '${errTraceId}', '${errSpanId}', '', '', 'console.error', 'SPAN_KIND_INTERNAL', 'browser', {'rum.sessionId':'${sessionId}','service.name':'browser'}, '', '', {'component':'error','message':'E2E test error ${i}','teamId':'test-team-id','teamName':'Test Team','userEmail':'${userEmail}','userName':'${userName}'}, 0, 'STATUS_CODE_ERROR', '', [], [], [], [], [], [], [])`, + `('${errTimestampNs}', '${errTraceId}', '${errSpanId}', '', '', 'console.error', 'SPAN_KIND_INTERNAL', 'browser', {'rum.sessionId':'${sessionId}','service.name':'browser'}, '', '', {'component':'error','message':'E2E test error ${i}','teamId':'${sessionTeamId}','teamName':'${sessionTeamName}','userEmail':'${sessionUserEmail}','userName':'${sessionUserName}'}, 0, 'STATUS_CODE_ERROR', '', [], [], [], [], [], [], [])`, ); } From eb575679470fa0892af485fd9bb7c260c4c5dfee Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Wed, 8 Apr 2026 09:38:47 -0600 Subject: [PATCH 6/6] fix issue closing drawers, add test --- packages/app/src/components/DBRowSidePanel.tsx | 1 - packages/app/tests/e2e/features/sessions.spec.ts | 13 +++++++++++++ packages/app/tests/e2e/page-objects/SessionsPage.ts | 12 ++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/app/src/components/DBRowSidePanel.tsx b/packages/app/src/components/DBRowSidePanel.tsx index 97f18b5d61..6119936c85 100644 --- a/packages/app/src/components/DBRowSidePanel.tsx +++ b/packages/app/src/components/DBRowSidePanel.tsx @@ -592,7 +592,6 @@ export default function DBRowSidePanelErrorBoundary({ { if (!subDrawerOpen) { _onClose(); diff --git a/packages/app/tests/e2e/features/sessions.spec.ts b/packages/app/tests/e2e/features/sessions.spec.ts index 1de0837b13..aec7b09f9a 100644 --- a/packages/app/tests/e2e/features/sessions.spec.ts +++ b/packages/app/tests/e2e/features/sessions.spec.ts @@ -101,6 +101,19 @@ test.describe('Client Sessions Functionality', { tag: ['@sessions'] }, () => { sessionsPage.rowSidePanel.getByTestId('side-panel-tab-replay'), ).toHaveCount(0); }); + + await test.step('Clicking the overlay closes the event detail panel but keeps the session replay open', async () => { + // Without the fix, withOverlay={!isNestedPanel} removed the overlay on nested panels, + // so there was nothing to click to close the panel (it had to be ESC only). + // With the fix (withOverlay always true), clicking the Mantine overlay dismisses the inner panel. + await sessionsPage.clickTopmostDrawerOverlay(); + + // The event detail panel must close + await expect(sessionsPage.rowSidePanel).toBeHidden(); + + // The session replay drawer must still be open + await expect(sessionsPage.sessionSidePanel).toBeVisible(); + }); }, ); }); diff --git a/packages/app/tests/e2e/page-objects/SessionsPage.ts b/packages/app/tests/e2e/page-objects/SessionsPage.ts index c664b41044..274f563a10 100644 --- a/packages/app/tests/e2e/page-objects/SessionsPage.ts +++ b/packages/app/tests/e2e/page-objects/SessionsPage.ts @@ -96,6 +96,18 @@ export class SessionsPage { return this.page.getByTestId('row-side-panel'); } + /** + * Click the Mantine overlay of the topmost open drawer to close it. + * Mantine renders one overlay per open Drawer. The last one belongs to + * the innermost (topmost) drawer. + */ + async clickTopmostDrawerOverlay() { + // Mantine overlays are siblings of the drawer content inside the portal root. + // Use the last one since the inner panel's overlay is rendered on top. + const overlay = this.page.locator('.mantine-Drawer-overlay').last(); + await overlay.click({ position: { x: 10, y: 10 } }); + } + // Getters for assertions get form() {