fix(funnel): stop the breakdown limit from dropping lower funnel steps - #497
fix(funnel): stop the breakdown limit from dropping lower funnel steps#497ayushjhanwar-png wants to merge 1 commit into
Conversation
With a breakdown and more distinct values than `limit`, every step of every breakdown row reports an identical count at 100%, so a funnel looks like it converts perfectly end to end. toSeries bails out of the whole reduce once the accumulator holds `limit` keys. The funnel query is ordered by `level DESC`, so the first rows in are the deepest level: the limit is reached while only max-level rows have been seen, and every remaining row is discarded — including the lower-level rows of the series already accepted. Each series is left holding a single row, and fillFunnel accumulates that one count into every step, which also makes totalSessions equal to it and every percent 100%. The limit is meant to cap how many series are returned, so only reject keys that are new. Observed on a report with 185 distinct breakdown values against a limit of 50: a path reported 128/128/128/128/128 and now reports 323/256/151/150/128, matching the underlying ClickHouse rows. Reports with fewer breakdown values than their limit were never affected. Adds funnel.service.test.ts; two of its four cases fail without this change.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthrough
ChangesFunnel series limiting
Priority: ➖ Normal Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to Limited funnel breakdowns now retain all levels for accepted series while continuing to cap new series, preventing incorrect flat 100% conversion funnels. The targeted regression coverage supports merge readiness. Suggested reviewers: 🚥 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 |
The bug
When a funnel has a breakdown and more distinct breakdown values than
limit, every step of every breakdown row reports an identical count at 100% — the funnel looks like it converts perfectly end to end.Observed on a report with 185 distinct paths against a limit of 50:
The underlying rows for that path:
128is the deepest-level count, repeated across every step.Cause
toSeriesbails out of the entire reduce once the accumulator holdslimitkeys:The funnel query ends with
ORDER BY level DESC, so rows arrive grouped by depth — all max-level rows first, then the next level down, and so on. The limit is therefore reached while only max-level rows have been seen, and every subsequent row is discarded — including the lower-level rows belonging to series already accepted.Each series is left holding one row.
fillFunnelaccumulates bottom-up, so that single count propagates into every step, andtotalSessions(taken from the level-1 entry) becomes equal to it — making every percent exactly 100%:Reports with fewer breakdown values than their limit are unaffected, which is why this only shows up once a breakdown dimension grows past the limit.
Fix
The limit caps how many series are returned, so it should only reject keys that are new:
The number of series returned is unchanged — still capped at
limit.Verification
Simulated against this branch's
toSeries(includingnormalizeBreakdownValue) andfillFunnel, with rows in the exactlevel DESCorder the query produces — 3 breakdown values,limit: 2:Series count stays capped at 2 in both cases.
The same change was verified end to end on a downstream deployment against live data: the corrected numbers match the ClickHouse rows exactly.
Tests
Adds
packages/db/src/services/funnel.service.test.tscovering:Two of the four fail without this change. Note: these were executed against a fork whose
toSeriesis identical to this file's; I wasn't able to install this repo's dependencies locally to run the suite here, so CI is the real check on them.Also worth a look (not changed here)
Which series survive the limit is "first
limitkeys encountered inlevel DESCorder" — biased toward series that converted deepest rather than the largest. Results are sorted by total afterwards, so ordering looks right, but a high-traffic breakdown value that never converts deeply can still be dropped. Selecting the top-N by total before slicing would be more predictable.Summary by CodeRabbit