feat(replay): shareable link to an exact moment + tab - #381
feat(replay): shareable link to an exact moment + tab#381ayushjhanwar-png wants to merge 8 commits into
Conversation
Prevents OOM on Aiven CH (43GB per-query cap) hit by the device_first_seen aggregation + two large anti-joins. Aggregation + sort buffers spill to disk if they exceed limits. Correctness unchanged. Tested against prod CH: 12k candidates in 14s (same as baseline).
07-06 sequential single-INSERT at threads=6 took 7h 17m for a 157M-event day. The old 5h ceiling would abort any full-day run that hits normal load. 10h gives cushion for peak-traffic days while still bounding runaway queries.
) * Revert "perf(db): extend property-MV router to breakdown-by-property charts (redo) (#359)" This reverts commit 8213214. * Revert "safety: gate property-MV route on explicit project allowlist (#358)" This reverts commit 5f9a286. * Revert "perf(db): route event-property-filter charts to profile_event_property_summary_mv (#356)" This reverts commit a1d884f.
* feat(db): session-replay blob archival (ClickHouse → Azure Blob) Adds an archive-replay-chunks CLI that copies session_replay_chunks to Azure Blob as Parquet — ClickHouse writes directly via azureBlobStorage(), so replays are kept forever while ClickHouse holds only a rolling hot window. - Watermark via set-difference on replay_archive_index (resumable, gapless) - Settled-day (today-2) filter + future-dated (clock-skew) partition guard - Adaptive session-hash bucketing ceil(GiB/5) so big days fit in memory; cityHash64(session_id) keeps each session's chunks in one file - Memory guardrails on every export (a whole-day sort OOMs at ~44 GiB) - Verify blob==CH count before writing the index; profile_id joined from events - Idempotent (overwrite) + ReplacingMergeTree index Requires the replay_archive_index table and AZURE_BLOB_CONNECTION_STRING. Design: docs/session-replay-blob-archive.md * fix(db): address CodeRabbit review on replay archival - planDays: mark a day done only when index chunk-sum == source rows, so a partially-written index (populate failed mid-run) is retried, not skipped (was: presence-only detection → silent gaps). [critical] - planDays: filter system.parts by database = currentDatabase() and skip empty (0-row) partitions. - blobPathExpr: toString(project_id) inside concat (defensive). - Add code-migration 23-add-replay-archive-index (ReplacingMergeTree, ORDER BY (project_id, session_id, dt), no TTL) + TABLE_NAMES entry — makes the index reproducible instead of hand-created. - Docs: align to the shipped design (Parquet + CH-native azureBlobStorage + K8s CronJob, not Databricks/Postgres/blocks); mark Blob serving as a planned follow-up; fix fenced-code languages. * fix(db): overwrite blob on re-export (azure_truncate_on_insert) Without it, re-exporting a day's bucket to its existing path throws 'Object ... already exists' — so the partial-day retry/self-heal path (Level 1) would crash instead of overwriting. Verified on dev: truncate insert keeps the file at the correct row count (no duplication).
Removes the customAlerts scheduler entry so the 15-min chart threshold/anomaly cron stops firing. Handler code is intact so any in-flight scheduled job still executes cleanly; the bootCron() flow clears all existing schedulers before re-adding only the listed ones, so the entry is removed from Redis on next worker-cron deploy. Refs #214
…ackfill (#380) Whole-day INSERTs against `events` cause ~2x scan over-read because the sort key is (project_id, toDate(created_at), created_at, name). Without project_id in WHERE, cross-project boundary granules within each part contain rows from two projects with unrelated date ranges — CH reads the whole granule and filters at row level. Adding project_id to WHERE lets the primary key jump directly to that project's contiguous block within each part, and within it toDate pruning is precise. Measured on 07-06→07-09 whole-day runs: 315M reads for 157M actual events (~2x). Expected with per-project split: ~1.05x (only start/ end boundary granules). New --projects flag: pnpm migrate:deploy:code -- 22 --cluster \ --start="2026-06-30 00:00:00" --end="2026-07-01 00:00:00" \ --projects=dashreels,shortreels,frameo,studio,growth-tool --no-record Fires N sequential INSERTs (one per project). Step 0 prints per-project expected event count; Step 2 logs per-project INSERT elapsed. Whole-day mode (without --projects) is preserved for legacy invocations. Failed project doesn't abort — logs the error and continues with next project. Failed projects can be re-run individually via --projects=<name>.
Adds a link icon to the replay player controls (next to fullscreen) that copies a deep link to the current playback position and active tab: ?session=<id>&t=<seconds>&tab=<window_id> Opening the link on the Session Replays page pre-selects that tab and seeks to that exact timestamp once the player is ready (reuses the existing smart-seek, so far jumps into long recordings work). - CopyLinkButton reads current time + window_id from replay context/props - SeekBridge performs the one-shot deep-link seek on player-ready - ReplayShell accepts initialWindowId / initialSeekMs; the view reads ?t=/?tab= and only applies them to the deep-linked session
|
Warning Review limit reached
Next review available in: 45 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughSession replay URLs now preserve the selected session, playback timestamp, and window. Replay playback initializes from those parameters, and the toolbar can copy the current replay position as a shareable link. ChangesReplay deep links
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant SessionReplaysView
participant ReplayShell
participant ReplayContent
participant SeekBridge
participant Clipboard
participant Toast
SessionReplaysView->>ReplayShell: pass initialWindowId and initialSeekMs
ReplayShell->>ReplayContent: pass initialSeekMs for active window
ReplayContent->>SeekBridge: provide initialSeekMs
SeekBridge->>SeekBridge: seek once after startTime is ready
ReplayShell->>Clipboard: copy session, time, and tab URL
Clipboard-->>Toast: report successful copy
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/start/src/components/session-replays/session-replays-view.tsx`:
- Around line 89-101: Update the initialSeekRef handling in the session replay
view so its deep-link state is cleared when selectedSessionId changes. Ensure
returning to the original session cannot reuse the previously consumed t or tab
values, while preserving the one-time initialization behavior for the initially
selected session.
In `@apps/start/src/components/sessions/replay/index.tsx`:
- Around line 175-178: Update the replay link copy flow around
navigator.clipboard.writeText to await its promise and only show the “Replay
link copied” success toast after it resolves; handle rejected writes so failures
do not display a success toast.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 92b23830-d589-47db-81c3-0aaf314c3555
📒 Files selected for processing (2)
apps/start/src/components/session-replays/session-replays-view.tsxapps/start/src/components/sessions/replay/index.tsx
- CopyLinkButton: await clipboard write; only toast success on resolve, toast an error on rejection (focus lost / permission denied). - session-replays-view: clear the deep-link ref once the user navigates to a different session, so returning to the shared session doesn't re-seek.
What
A link icon in the replay player controls (next to the fullscreen button, Mixpanel-style) that copies a shareable deep link to the current moment and current tab:
Opening that link on the Session Replays page auto-selects the tab and seeks to that exact timestamp once the player is ready.
Why
People sharing replays want to point a teammate at a specific moment ("watch what happens at 3:55 on tab 2"), not the whole recording — same as Mixpanel/PostHog.
How
CopyLinkButton(in the player controls) reads the current playback offset + activewindow_idfrom the replay context/props → writes?session=&t=&tab=to the clipboard → toasts "Opens at MM:SS".SeekBridgedoes a one-shot deep-link seek when the player becomes ready (uses the existing smart-seek, so a far jump into a long recording still works).ReplayShellacceptsinitialWindowId/initialSeekMs; the view reads?t=/?tab=(nuqs) and applies them only to the deep-linked session (clicking another replay doesn't wrongly re-seek).Notes
sonnertoast +formatDuration).tis in seconds (readable URLs);tabis the stablewindow_id.Test
Session Replays → play a replay → scrub / switch tab → click the 🔗 → paste in a new tab → lands on that moment + tab.
🤖 Generated with Claude Code
Summary by CodeRabbit