Skip to content

feat(profiles): Mixpanel-style profiles page — perf, filters, search - #371

Open
ayushjhanwar-png wants to merge 12 commits into
mainfrom
feat/profiles-mixpanel-perf
Open

feat(profiles): Mixpanel-style profiles page — perf, filters, search#371
ayushjhanwar-png wants to merge 12 commits into
mainfrom
feat/profiles-mixpanel-perf

Conversation

@ayushjhanwar-png

@ayushjhanwar-png ayushjhanwar-png commented Jul 13, 2026

Copy link
Copy Markdown

What

Profiles-page overhaul for large projects (dashreels ≈ 80M profiles). Perf, Mixpanel-style behavioral/property filters, and search fixes.

Performance

  • List: ~20s → ~1.6s. getProfileList prunes to the recent monthly partition(s) + do_not_merge_across_partitions_select_final. Falls back to the full range only if the window doesn't fill the page.
  • Search: exact profile-id is a PK point lookup (~instant); fuzzy name/email ILIKE is the fallback scan.
  • Count: uniq(id) (approx distinct users) instead of counting every ReplacingMergeTree version.

Filters (v1, Mixpanel-style)

  • "Users who did event X [where event.property = Y] in a date range" — subquery over the raw events table. Raw events (not profile_event_summary_mv) is deliberate: the MV filters profile_id != device_id and would undercount anon/pre-identify users.
  • No event selected → filters apply as profile-property filters on the profiles table.
  • Date range moved inside the filter card next to the event selector, and only shows for a real event selection.
  • The ['*'] "All Events" wildcard is stripped → means "no filter" instead of an event literally named * (which returned 0 profiles).

Count

  • Reflects the active filter (behavioral → distinct profiles who did the event; property → filtered uniq(id)).
  • Mirrors the list's id-first search, so an id-search count matches the row shown (was returning 0).
  • Prominent top-left, flush with the title/card, with a skeleton while (re)fetching.

Columns / UI

  • Mixpanel columns: Name, Email, Distinct ID, Last seen, Country, Region, City (dropped Referrer/OS/Browser/Model).
  • Removed the Identified / Anonymous / Power-users tabs; loader shows during filter refetch.

Notes / follow-ups

  • Behavioral filter scans raw events (~7s on large projects) — accepted; correct over fast-but-wrong. Future fast path = cohorts / short Redis cache once identity (device_id/profile_id) resolution lands.
  • Requires an API restart to take effect.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added “last seen” controls for profiles, including a range picker and a default “last 15 days” window.
    • Enabled event-based, time-bounded filtering on the identified profiles view, including an event-count (did event N times) operator filter.
  • Improvements
    • Refreshed the profiles table columns (including Email, Distinct ID, and updated location fields) and made “Last seen” sorting interactive.
    • Updated loading behavior to better reflect active data fetching.
  • Documentation / Configuration
    • Added documentation and configuration for the v2 behavioral filter routing, including new environment variables.

Profiles page overhaul for large projects (dashreels: ~80M profiles).

Performance
- getProfileList: recent-month partition window + do_not_merge_across_partitions
  _select_final → ~20s to ~1.6s. Falls back to full range if the window
  doesn't fill the page.
- Exact profile-id search is a PK point lookup (~instant); fuzzy name/email
  ILIKE is the fallback scan.
- Count uses uniq(id) (approx distinct users) instead of counting every
  ReplacingMergeTree version.

Filters (v1, Mixpanel-style)
- "Users who did event X [where event.property = Y] in a date range" — subquery
  over the raw events table (correct for anon/pre-identify; the MV would
  undercount). No event selected → filters apply as profile-property filters.
- Date range lives inside the filter card next to the event selector and only
  shows for a real event selection.
- The `['*']` "All Events" wildcard is stripped so it means "no filter" instead
  of an event literally named `*` (which returned 0 profiles).

Count
- Reflects the active filter (behavioral → distinct profiles who did the event;
  property → filtered uniq(id)) and mirrors the list's id-first search so an
  id-search count matches the row shown.
- Prominent top-left, flush with the title/card, skeleton while (re)fetching.

Columns / UI
- Mixpanel columns: Name, Email, Distinct ID, Last seen, Country, Region, City
  (dropped Referrer/OS/Browser/Model).
- Removed the Identified/Anonymous/Power-users tabs; loader shows during filter
  refetch.
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bb2738aa-5803-45b0-8935-52af40834fe0

📥 Commits

Reviewing files that changed from the base of the PR and between 23e838f and 155fff2.

📒 Files selected for processing (7)
  • .env.example
  • apps/start/src/components/profiles/event-count-filter.tsx
  • apps/start/src/components/profiles/last-seen-picker.tsx
  • apps/start/src/components/profiles/last-seen-range.tsx
  • apps/start/src/components/profiles/table/columns.tsx
  • apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.identified.tsx
  • packages/db/src/services/profile.service.ts
💤 Files with no reviewable changes (1)
  • apps/start/src/components/profiles/table/columns.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • .env.example
  • apps/start/src/components/profiles/last-seen-range.tsx
  • apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.identified.tsx
  • apps/start/src/components/profiles/event-count-filter.tsx
  • apps/start/src/components/profiles/last-seen-picker.tsx
  • packages/db/src/services/profile.service.ts

📝 Walkthrough

Walkthrough

Profile listing now supports behavioral, property, search, event-count, and last-seen filters through tRPC and ClickHouse. The identified profiles view exposes these controls, updates table fields and loading behavior, and simplifies the surrounding route layout.

Changes

Profile behavioral filtering and presentation

Layer / File(s) Summary
Profile query contracts and ClickHouse filtering
packages/trpc/src/routers/profile.ts, packages/db/src/services/profile.service.ts, packages/db/src/clickhouse/client.ts, .env.example
Profile list and count queries accept behavioral, event-count, property, search, timezone-aware date, and last-seen inputs, with guarded v2 routing and events-table fallback execution.
Last-seen and event-count controls
apps/start/src/hooks/use-profiles-sort.ts, apps/start/src/components/profiles/last-seen-*, apps/start/src/components/profiles/event-count-filter.tsx
URL-backed sort, date bounds, and event-count state are edited through picker, range, and count controls.
Identified profiles filtering UI
apps/start/src/routes/...identified.tsx, apps/start/src/components/events/filters/events-filters.tsx
The route derives filter state, passes it to the profile query, displays the count, and renders event, count, and last-seen controls.
Profile table fields and loading
apps/start/src/components/profiles/table/*, apps/start/src/components/column-created-at.tsx
The table adds identity and location columns, supports clickable last-seen sorting and exact timestamps, and uses fetching state for loading behavior.
Profiles route shell and behavioral documentation
apps/start/src/routes/...profiles._tabs.tsx, docs/profiles-v2-behavioral-filter.md
The parent route renders nested content without tabs, while documentation describes v2 routing, timestamp handling, count semantics, audience behavior, and configuration.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant IdentifiedProfiles
  participant EventsFilters
  participant profileRouterList
  participant getProfileList
  participant ClickHouse
  IdentifiedProfiles->>EventsFilters: render event, count, and range controls
  EventsFilters-->>IdentifiedProfiles: selected filters and date bounds
  IdentifiedProfiles->>profileRouterList: submit profile list options
  profileRouterList->>getProfileList: validate and forward options
  getProfileList->>ClickHouse: execute behavioral or property-filtered query
  ClickHouse-->>getProfileList: profiles and count
  getProfileList-->>IdentifiedProfiles: update table and count
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main profiles-page overhaul: performance improvements, filters, and search enhancements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/profiles-mixpanel-perf

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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 `@packages/db/src/services/profile.service.ts`:
- Around line 312-316: Fix SQL injection in the fuzzy search branches of the
profile query and getProfileListCount by escaping the user-provided search value
before constructing the ILIKE pattern. Reuse the escaped LIKE pattern
consistently across email, first_name, and last_name conditions, while
preserving the existing fuzzy-search behavior.
- Around line 196-199: Update the column selection in eventFilterClauses so bare
f.name values resolve only through a fixed allowlist map of permitted event
columns; reject or skip unknown names instead of interpolating them directly
into SQL. Preserve the existing properties.* handling and IN-list construction,
while ensuring the mapped column is used in ${col} IN (...).
- Around line 183-185: Update the date-filter logic around formatClickhouseDate
in the profile query builder to validate startDate and endDate before formatting
them, ignoring malformed values so invalid inputs fall back to the default range
instead of throwing. Preserve the existing BETWEEN clause when both dates are
valid.
🪄 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: 12f50cbe-a44a-4b1d-866f-43a3e06f95e8

📥 Commits

Reviewing files that changed from the base of the PR and between 04c1976 and 90569c8.

📒 Files selected for processing (7)
  • apps/start/src/components/events/filters/events-filters.tsx
  • apps/start/src/components/profiles/table/columns.tsx
  • apps/start/src/components/profiles/table/index.tsx
  • apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.identified.tsx
  • apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.tsx
  • packages/db/src/services/profile.service.ts
  • packages/trpc/src/routers/profile.ts

Comment thread packages/db/src/services/profile.service.ts
Comment thread packages/db/src/services/profile.service.ts Outdated
Comment thread packages/db/src/services/profile.service.ts
…ingle date control

- Sortable "Last seen" (created_at) column header — click to flip asc/desc
  (default desc). URL state via useProfilesSort.
- Optional "Last seen" window (hour-precise) reusing the app's calendar
  date-range popover with an opt-in withTime flag (From/To time inputs). Null by
  default so nothing is filtered until set; Clear resets to all-time.
- Single date control: removed the Today/Yesterday/7D/30D/3M/Custom segmented
  picker; the Last-seen window now bounds both the profile list (created_at) and
  the behavioral event subquery.
- Timezone fix: the picker gives a naive wall-clock the user reads in the org
  timezone. Interpret it there via toDateTime(str, tz) (tz from
  getOrganizationByProjectIdCached) instead of new Date().toISOString(), which
  used the server TZ and shifted results. created_at stays raw so partition
  pruning holds.

Perf (dashreels, 98.7M profiles): bounded tz-aware range ~0.4-1.4s, prunes to
the month partition(s); unbounded ASC ~10s (full FINAL dedup) — the date range
is the fast path.

withTime defaults off, so the only other CustomDateRangePopover consumer
(time-window-picker) is unchanged. Profiles page only.
Replace the last-seen window control with a dedicated Mixpanel-style picker
(LastSeenPicker):
- Fixed / Since mode sidebar.
- Editable Starts/Ends datetime fields — free-type "Jun 4, 2026, 01:00 PM"
  (AM/PM guaranteed via hh:mm a regardless of locale), commit on blur/Enter,
  invalid reverts. Two-way synced with the calendar; changing the day keeps the
  typed time.
- Dual-month calendar with range highlight.
- "Enable time ranges" toggle: off = day-granular, on = hour-precise.

Self-contained to profiles — reverted the withTime addition to the shared
custom-date-range-popover (now byte-identical to origin/main); the only other
consumer (time-window-picker) is unchanged.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/profiles/last-seen-picker.tsx`:
- Around line 59-71: Update the persisted date initialization and
synchronization in the picker component to parse startDate and endDate with
date-fns using the existing DB_FORMAT and a current-date reference, matching the
approach in last-seen-range.tsx. Replace both new Date(startDate) and new
Date(endDate) usages while preserving the existing undefined handling and mode
logic.
🪄 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: 092be05c-d41e-4af0-8200-b6e511986192

📥 Commits

Reviewing files that changed from the base of the PR and between 90569c8 and e845422.

📒 Files selected for processing (7)
  • apps/start/src/components/profiles/last-seen-picker.tsx
  • apps/start/src/components/profiles/last-seen-range.tsx
  • apps/start/src/components/profiles/table/columns.tsx
  • apps/start/src/hooks/use-profiles-sort.ts
  • apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.identified.tsx
  • packages/db/src/services/profile.service.ts
  • packages/trpc/src/routers/profile.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/start/src/components/profiles/table/columns.tsx
  • packages/db/src/services/profile.service.ts

Comment thread apps/start/src/components/profiles/last-seen-picker.tsx Outdated
… operators

Route the Profiles "who did event X where property=Y OP N times" filter to
profile_event_property_summary_v2 for allowlisted projects (~350ms vs multi-
second raw events scan). Non-allowlisted projects fall back to raw events.

- Two-step: rank in v2 by maxMerge(last_event_time), hydrate the 50-row page
  via getProfiles (no FINAL — 2.1s -> 190ms).
- Exact-window clamp on last_event_time so "Last seen" is strictly within the
  picked window (fixes the UTC-day 05:29 edge artefact).
- UTC-day alignment on the event_date bound (fixes ~29% undercount at the
  window's trailing UTC day).
- "did event OP N times": eq/ne/gt/gte/lt/lte/between/notBetween on
  countMerge (v2) / count() (fallback), list + count consistent.
- Guard: v2 only for exactly one properties.* filter (name-only and multi-key
  route to events). OR across events/values on the fast path.
- Page shows all profiles (anon + identified) so base and behavioural count
  share one population.
- UI: "Profiles who did" label, operator control, 15-day default window,
  exact-timestamp Last seen.
- CLICKHOUSE_MAX_EXECUTION_TIME cap (default 60s) on the app CH client.

Env: PROFILES_BEHAVIORAL_V2_PROJECTS, PROFILES_BEHAVIORAL_V2_START_DATE,
CLICKHOUSE_MAX_EXECUTION_TIME. Doc: docs/profiles-v2-behavioral-filter.md.
…date parse

Review fixes on the profiles behavioural filter:

- SECURITY (critical): escape `search` in the fuzzy ILIKE clause (list + count)
  with sqlstring.escape — was interpolated raw, allowing SQL injection.
- SECURITY (major): reject non-identifier bare event-filter names in
  eventFilterClauses before interpolating (column names can't be string-escaped).
- search + behavioural now applies the exact-window clamp + count threshold via
  buildBehavioralV2Subquery, so results match the count (was silently dropped).
- last-seen-picker: parse persisted yyyy-MM-dd HH:mm:ss with date-fns instead of
  new Date() (Safari returned Invalid Date, leaving the picker empty).
- drop the unrelated CLICKHOUSE_MAX_EXECUTION_TIME cap (separate concern).
A name/email/id search must find a profile regardless of when it was last
seen, but the created_at window (incl. the 15-day default) was still applied
during search — so searching only matched profiles seen in that window. Skip
the window whenever a search term is present, in both the list and the count
(so they stay consistent). Behavioural time bounds are unaffected.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
docs/profiles-v2-behavioral-filter.md (1)

77-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a language to the fenced block (markdownlint MD040).

-```
+```sql
 HAVING maxMerge(last_event_time) BETWEEN <startInstant> AND <endInstant>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/profiles-v2-behavioral-filter.md around lines 77 - 79, Update the
fenced SQL example containing the HAVING maxMerge(last_event_time) query to
specify the sql language identifier after the opening fence, preserving the
query content unchanged.


</details>

<!-- cr-comment:v1:c3d449d92d369a840bc5faa9 -->

_Source: Linters/SAST tools_

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

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 @.env.example:

  • Around line 55-63: Update the behavioral filter v2 routing description in
    .env.example to state that exactly one properties.* filter is required, not at
    most one, and align the reported latency with the ~350 ms figure documented in
    docs/profiles-v2-behavioral-filter.md and the implementation comment.

In @apps/start/src/hooks/use-profiles-sort.ts:

  • Around line 58-65: Constrain the countVal and countVal2 query-state values to
    non-negative integers instead of using unrestricted parseAsInteger. Update the
    useQueryState definitions (or normalize both operands before constructing
    eventCount) so negative URL values cannot produce negative
    eventCount.value/value2 or bypass the minimum event-count requirement.

In @packages/db/src/services/profile.service.ts:

  • Around line 469-513: Propagate the explicit last-seen window through all
    behavioral list and count paths instead of applying it only in fallback
    handling. Update getBehavioralV2ProfileList and its hydration query to accept
    and enforce lastSeenStart/lastSeenEnd, and update getProfileListCount’s
    behavioral branches to use the same window predicates; ensure allowlisted and
    fallback results and counts remain consistent.
  • Around line 864-917: Update the behavioral count branch around
    canRouteBehavioralToV2 and the events fallback to apply the same search-based
    id/fuzzy predicate used by the list before counting profiles. Ensure both the V2
    and events SQL paths AND this predicate with their existing behavioral
    conditions, grouping, and thresholds so counts match filtered rows.
  • Around line 295-310: Validate PROFILES_BEHAVIORAL_V2_START_DATE immediately
    after construction and make canRouteBehavioralToV2 return false when the
    configured date is invalid. Ensure malformed environment values fail closed
    rather than allowing the queryStart coverage comparison to pass.

Nitpick comments:
In @docs/profiles-v2-behavioral-filter.md:

  • Around line 77-79: Update the fenced SQL example containing the HAVING
    maxMerge(last_event_time) query to specify the sql language identifier after the
    opening fence, preserving the query content unchanged.

</details>

<details>
<summary>🪄 Autofix (Beta)</summary>

Fix all unresolved CodeRabbit comments on this PR:

- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro Plus

**Run ID**: `55a1ccde-6575-46be-ab05-ba1ef9770e12`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e84542224f40eb86b354a56a1d93275ce2c98283 and 23e838f1c8d3edf5fb015f7f7164af0493f2f7c0.

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `.env.example`
* `apps/start/src/components/column-created-at.tsx`
* `apps/start/src/components/events/filters/events-filters.tsx`
* `apps/start/src/components/profiles/event-count-filter.tsx`
* `apps/start/src/components/profiles/last-seen-picker.tsx`
* `apps/start/src/components/profiles/last-seen-range.tsx`
* `apps/start/src/components/profiles/table/columns.tsx`
* `apps/start/src/hooks/use-profiles-sort.ts`
* `apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.identified.tsx`
* `docs/profiles-v2-behavioral-filter.md`
* `packages/db/src/clickhouse/client.ts`
* `packages/db/src/services/profile.service.ts`
* `packages/trpc/src/routers/profile.ts`

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (5)</summary>

* apps/start/src/components/profiles/last-seen-range.tsx
* apps/start/src/routes/_app.$organizationId.$projectId.profiles._tabs.identified.tsx
* packages/trpc/src/routers/profile.ts
* apps/start/src/components/profiles/last-seen-picker.tsx
* apps/start/src/components/profiles/table/columns.tsx

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread .env.example Outdated
Comment thread apps/start/src/hooks/use-profiles-sort.ts
Comment thread packages/db/src/services/profile.service.ts Outdated
Comment thread packages/db/src/services/profile.service.ts
Comment thread packages/db/src/services/profile.service.ts Outdated
…t parity

Addresses the cloud + CodeRabbit review findings on the profiles filter:

- All 15 filter operators (doesNotContain/startsWith/endsWith/regex/gt/lt/
  gte/lte/isNull/isNotNull/…) via a shared operatorClause, in the v2, events
  and profile-property builders. Was: everything unhandled fell through to IN
  (e.g. doesNotContain returned the opposite audience).
- profile.* filters (profile.id / profile.properties.*) now apply to the outer
  profiles query instead of being dropped — "did event X AND profile.id=Y" now
  actually filters by id (was returning all profiles).
- Count/list parity: behavioural list no longer gates profiles.created_at
  (first-seen) by the window — the event subquery bounds time — so the count
  and rows agree (was 152 vs 8). Count now mirrors the list (behavioural
  membership + profile.* filters + search + window) via a shared subquery.
- Pagination: offset = cursor (client already sends the row offset); removes
  the (page-1)*take*take that made page 2+ empty.
- START_DATE guard: validate/anchor to UTC so a blank/garbled env falls back
  instead of silently disabling the coverage gate; parse queryStart as UTC too.
- Nits: count input is a plain typed field (no spinner, no decimals → no 400);
  LastSeenPicker restores the time toggle on reopen; drop unused imports; fix a
  stale transformProfile comment.
…me + faster

The events-fallback behavioural list sorted `profiles FINAL` by
profiles.created_at (profile-record write time) — so "Last seen" showed stale
record dates (e.g. Dec 2025) instead of when the profile did the event, and it
fed the whole doer id-set into `profiles FINAL` (~20s on high-volume events).

Give it the same two-step as v2: rank profiles by max(created_at) of the event
INSIDE `events`, LIMIT one page, then hydrate. Now:
- "Last seen" = the real last-event time on every no-search/no-profile-filter
  behavioural path (v2 or events), bounded exactly to the window.
- No huge id-set into profiles FINAL — ranks+limits in events, hydrates 50.
…env doc

- eventFilterClauses / profileAttrFilterClauses: allowlist bare column names
  (EVENT_FILTER_COLUMNS / PROFILE_FILTER_COLUMNS) instead of a loose identifier
  regex — blocks injection AND referencing arbitrary columns.
- Clamp countVal/countVal2 to non-negative ints in the route so a hand-edited
  URL (?countVal=-1) can't 400 on the tRPC min(0) schema.
- Fix stale .env.example guard doc: EXACTLY ONE properties.* filter, ~350ms.
… is picked

- No event ("All Events") → window defaults to ALL TIME (don't hide older
  profiles on the plain list).
- Pick an event (e.g. showOpen) → auto-defaults to the last 15 days (a
  "who did X" question is naturally time-bound); user can still change it.
- An explicit range always wins.
- Date control moved inline into the condition card, right next to the event +
  count filter (Mixpanel-style); empty label reflects the effective default
  ("All time" vs "Last 15 days").
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant