feat(insights): extend day-of-week filtering to stickiness, lifecycle, and funnel trends - #70588
Conversation
AND the DateRange daysOfWeek filter expression into the stickiness event WHERE clause. Days active now count activity on the selected days only. The actors (persons list) query reuses the same events query, and the compare-to-previous-period series resolves its own QueryDateRange subclass per series, so both paths inherit the filter. Generated-By: PostHog Code Task-Id: 750d696e-4875-460f-b35f-e7cd869e2af0
AND the DateRange daysOfWeek filter expression into the lifecycle event filter, so new/returning/resurrecting/dormant statuses are derived from activity on the selected days only. The actors query reuses the same events query, so the persons list matches the chart. Day-interval buckets on excluded days are deliberately kept (unlike trends): dormant counts land on the day after last activity, which can itself be an excluded day, so dropping those buckets would hide real dormant users. Generated-By: PostHog Code Task-Id: 750d696e-4875-460f-b35f-e7cd869e2af0
AND the DateRange daysOfWeek filter expression into the funnel event query WHERE clause, gated on the trends visualization so regular funnels and time-to-convert are untouched. The filter applies to every funnel event, so a conversion step landing on a deselected day counts as a drop-off. The actors query reuses the same inner event query. Also preserve daysOfWeek when the compare path clones the query for the previous period, so the comparison series stays filtered. Generated-By: PostHog Code Task-Id: 750d696e-4875-460f-b35f-e7cd869e2af0
|
Reviews (1): Last reviewed commit: "feat(insights): apply day-of-week filter..." | Re-trigger Greptile |
sampennington
left a comment
There was a problem hiding this comment.
🤖 sp-ship · 🚫 Blockers
Solid, well-tested extension of day-of-week filtering into stickiness, lifecycle, and funnel trends — it correctly reuses the existing QueryDateRange.day_of_week_filter_expr() helper, handles the None (unfiltered) contract on every path, follows project timezone, and covers entrances/conversions, empty-means-unfiltered, timezone boundaries, actors-match-chart, and previous-period propagation.
One thing worth blocking on: the funnel data-warehouse branch (_build_data_warehouse_table_query) carries its own copy of the filter application but no test exercises it — every funnel test uses EventsNode, so a regression in that branch would ship green (inline comment). Smaller follow-ups that didn't need their own thread: lifecycle has no compare/previous-period test and the "all seven days = unfiltered" case is only covered for stickiness (cheap parity gaps to close); and for string-typed DW timestamp columns toDayOfWeek will bucket by UTC rather than project timezone — consistent with the pre-existing date-range filter on the same alias, so not a regression, but worth a follow-up since day-of-week bucketing is more boundary-sensitive.
🤖 CI report✅ Backend coverage — all changed backend lines covered🧪 Backend test coveragePatch coverage — changed backend lines (products + core): All changed backend lines are covered ✅ Per-product line coverage (touched products)
Report-only. Patch coverage = changed backend lines covered vs |
The funnel-trends data-warehouse query path builds its own WHERE clause in _build_data_warehouse_table_query, separate from the events-table path, but no test exercised it — every existing day-of-week funnel test uses an EventsNode, and every data-warehouse funnel test uses the steps viz (which the filter deliberately skips). A regression in that branch would have shipped green. Adds a parameterized trends test over the existing funnels_data.csv fixture: Mondays-only keeps just the single Monday row (1 entrance) while an empty daysOfWeek leaves all six days unfiltered (6 entrances). Generated-By: PostHog Code Task-Id: cfea4603-b53e-41fb-8d43-de4b9f2f2988
There was a problem hiding this comment.
Contained backend extension wiring an existing, already-shipped day-of-week filter helper into three more query runners, with thorough parameterized tests (timezone boundaries, compare/previous-period, actors parity, viz-type gate); no schema, API, auth, billing, or CI surface touched, and no unresolved reviewer concerns on the current head.
- Author wrote 0% of the modified lines and has 10 merged PRs in these paths (familiarity MODERATE).
- 👍 on the PR from greptile-apps[bot], hex-security-app[bot].
Gate mechanics and policy version
| Gate | Result | |
|---|---|---|
| prerequisites | ✓ | all clear |
| deny-list | ✓ | no deny categories matched |
| size | ✓ | 24L, 4F substantive, 467L/8F incl. docs/generated/snapshots — within ceiling |
| tier | ✓ | T1-agent / T1d-complex (467L, 8F, two-areas, feat) |
| stamphog 2.0.0b3 | .stamphog/policy.yml @ ce5a82c · reviewed head 38400b9 |
Match the events-table path's None-handling style in the funnel data-warehouse branch (the where_exprs comprehension already filters None), and drop two comments that restated the adjacent code. Generated-By: PostHog Code Task-Id: 750d696e-4875-460f-b35f-e7cd869e2af0
New commits pushed (delta classified non_trivial_delta) — stamphog approval dismissed; re-review running automatically.
There was a problem hiding this comment.
Backend-only query-logic extension (wiring an existing daysOfWeek filter into three more insight runners); author is on the owning team, all substantive review threads (funnel data-warehouse test gap, warehouse timestamp field concern) were resolved with new tests or verified explanations, and no reviewer holds or in-flight reviews are outstanding.
- Author wrote 0% of the modified lines and has 10 merged PRs in these paths (familiarity MODERATE).
- 👍 on the PR from greptile-apps[bot], hex-security-app[bot].
Gate mechanics and policy version
| Gate | Result | |
|---|---|---|
| prerequisites | ✓ | all clear |
| deny-list | ✓ | no deny categories matched |
| size | ✓ | 22L, 4F substantive, 465L/8F incl. docs/generated/snapshots — within ceiling |
| tier | ✓ | T1-agent / T1d-complex (465L, 8F, two-areas, feat) |
| stamphog 2.0.0b3 | .stamphog/policy.yml @ 55899f0 · reviewed head c891aad |
Problem
The
daysOfWeekfield on the sharedDateRangeschema filters insights down to events that occurred on selected weekdays (ISO 1=Monday…7=Sunday), evaluated in the project timezone. It replaced the oldhideWeekendsdisplay toggle with a true query-level filter, but so far only trends applies it. Stickiness, lifecycle, and funnel trends ignore the field.Changes
Wires the existing
QueryDateRange.day_of_week_filter_exprhelper into three more runners, one commit each. Absent or emptydaysOfWeekstill means unfiltered (the helper returnsNone), so existing queries and HogQL snapshots are unchanged.where_clause. "Days active" now counts activity on the selected days only. The actors query reuses the same events query, and each compare series resolves its ownQueryDateRangesubclass, so the persons list and compare-to-previous are filtered too.event_filter, so new/returning/resurrecting/dormant statuses derive from selected-day activity only; the actors query inherits it.FunnelEventQuery's WHERE assembly (events and data-warehouse branches), gated onfunnelVizType == trends. Regular funnels and time-to-convert are deliberately untouched (dropping mid-sequence events there needs a product decision first). The compare path clones the query with a rebuiltDateRange, which silently droppeddaysOfWeek– it now carries the filter into the previous-period series.Note
Lifecycle keeps excluded-day buckets (trends drops them for day interval). In lifecycle, "dormant" lands on the day after last activity, which can itself be an excluded day (active Monday → dormant Tuesday under a Mondays-only filter). Dropping those buckets would hide real dormant counts, so all buckets stay; new/returning/resurrecting are simply zero on excluded days. A test locks this decision in.
Note
Funnel trends filters every funnel event, not just the entrance: a step-two event on a deselected day counts as a drop-off. This matches the "count only events on selected days" semantics and is asserted in a test.
Paths, retention, regular funnels, and the calendar heatmap remain out of scope, as does the frontend gate that exposes the day chips.
How did you test this code?
Ran the three touched test files locally against ClickHouse (
test_stickiness_query_runner.py,test_lifecycle_query_runner.py,test_funnel_trends.py), plustest_funnel.pyandtest_funnel_trends_actors.pyto guard the viz-type gate – all green, all HogQL snapshots unchanged. Data-warehouse-fixture tests couldn't run in the sandbox (no local object storage); they rely on CI here.New parameterized tests per runner, each catching a regression nothing covered before:
daysOfWeek= unfilteredDateRangewithoutdaysOfWeek)daysOfWeek(guards the gate)Automatic notifications
Docs update
Backend-only; the user-facing docs for day-of-week filtering apply once the frontend exposes it beyond trends.
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Authored by Claude (PostHog Code session) from a written brief. Skills invoked:
/writing-tests. Key decisions: gate the funnel filter onfunnelVizType == trendsinsideFunnelEventQueryrather than infunnel_trends.py, since the WHERE exprs are assembled in the shared event query; keep lifecycle's excluded-day buckets (dormant lands on excluded days); preservedaysOfWeekin the funnels previous-period query clone, which was found to drop it.Created with PostHog Code