fix(admin): add register→org onboarding funnel step - #2883
Conversation
Count self-serve registrations (excluding team invites) as the first funnel stage and filter org cohorts by non-invite creators. Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_635cd69e-1f72-4cb8-b343-62479edd806c) |
📝 WalkthroughWalkthroughThe admin onboarding funnel now tracks self-serve registrations, organization conversion, and daily registrations. Backend queries exclude invited users from funnel metrics. The dashboard, translations, and admin statistics tests use the new registration-based data. ChangesOnboarding Funnel
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
Track invite signups plus org joins from invite-created vs existing accounts on a dedicated admin chart. Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_ccf70184-cba4-4369-a91a-4d84572ee5f7) |
|
There was a problem hiding this comment.
1 issue found and verified against the latest diff
Confidence score: 3/5
- In
tests/admin-stats.test.ts, thetrial_plan_breakdownquery appears to include orgs created via onboarding invite (ONBOARDING_INVITE_ORG) because it lacks thecreated_via_i...filter used by the funnel, which can skew admin trial metrics in the 2026-02-01..02-02 window; align the query filters (or explicitly document intended divergence) and add an assertion to lock behavior.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tests/admin-stats.test.ts">
<violation number="1" location="tests/admin-stats.test.ts:475">
P2: Adding ONBOARDING_INVITE_ORG (created 2026-02-01 10:00 with a trial_at stripe row) lands it in the 2026-02-01..02-02 window of the trial_plan_breakdown query, which (unlike the funnel) does NOT filter by created_via_invite, so that existing test now counts 4 orgs but still asserts total===3 and will fail in CI. Either exclude invite-created orgs in getAdminTrialPlanBreakdown (consistent with the PR's funnel intent) or update the trial_plan_breakdown test expectation/seeded created_at.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| created_at: ONBOARDING_LATE_SUBSCRIPTION_CREATED_AT, | ||
| }, | ||
| { | ||
| id: ONBOARDING_INVITE_ORG_ID, |
There was a problem hiding this comment.
P2: Adding ONBOARDING_INVITE_ORG (created 2026-02-01 10:00 with a trial_at stripe row) lands it in the 2026-02-01..02-02 window of the trial_plan_breakdown query, which (unlike the funnel) does NOT filter by created_via_invite, so that existing test now counts 4 orgs but still asserts total===3 and will fail in CI. Either exclude invite-created orgs in getAdminTrialPlanBreakdown (consistent with the PR's funnel intent) or update the trial_plan_breakdown test expectation/seeded created_at.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/admin-stats.test.ts, line 475:
<comment>Adding ONBOARDING_INVITE_ORG (created 2026-02-01 10:00 with a trial_at stripe row) lands it in the 2026-02-01..02-02 window of the trial_plan_breakdown query, which (unlike the funnel) does NOT filter by created_via_invite, so that existing test now counts 4 orgs but still asserts total===3 and will fail in CI. Either exclude invite-created orgs in getAdminTrialPlanBreakdown (consistent with the PR's funnel intent) or update the trial_plan_breakdown test expectation/seeded created_at.</comment>
<file context>
@@ -410,6 +471,14 @@ beforeAll(async () => {
created_at: ONBOARDING_LATE_SUBSCRIPTION_CREATED_AT,
},
+ {
+ id: ONBOARDING_INVITE_ORG_ID,
+ name: `Admin Stats Onboarding Invite ${ONBOARDING_INVITE_ORG_ID.slice(0, 8)}`,
+ created_by: ONBOARDING_INVITE_USER_ID,
</file context>
There was a problem hiding this comment.
2 issues found across 5 files (changes from recent commits).
Confidence score: 3/5
- In
supabase/functions/_backend/utils/pg.ts, the Invite Join Trend query can drop the final calendar day whenend_dateincludes a time component, so dashboard rolling ranges may undercount recent invites/joins and mislead trend reads — adjust the series end-date logic to include the full last day (instead of deriving fromend_date - interval '1 microsecond'). - In
supabase/functions/_backend/plugin_runtime/utils/pg.ts, the new role-binding aggregation for Admin Users onboarding is unindexed, which raises query latency and primary DB load as membership volume grows — add the suggested partial index ongranted_at(for relevant rows) to de-risk scaling regressions.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="supabase/functions/_backend/utils/pg.ts">
<violation number="1" location="supabase/functions/_backend/utils/pg.ts:3357">
P2: The Invite Join Trend omits the final calendar day for any range whose `end_date` is not midnight, including the dashboard’s rolling range ending now. Deriving the series end date from `end_date - interval '1 microsecond'` would preserve the exclusive timestamp boundary while including the day containing events before it.</violation>
</file>
<file name="supabase/functions/_backend/plugin_runtime/utils/pg.ts">
<violation number="1" location="supabase/functions/_backend/plugin_runtime/utils/pg.ts:3745">
P2: Admin Users onboarding loads now add an unindexed role-binding aggregation, which can make the dashboard increasingly slow and increase primary-DB load as memberships grow. A partial index covering `granted_at` for user/org rows whose reason is `Accepted invitation` would keep this daily chart query bounded to the requested range.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| WITH date_series AS ( | ||
| SELECT generate_series( | ||
| ${start_date}::timestamptz::date, | ||
| (${end_date}::timestamptz::date - 1), |
There was a problem hiding this comment.
P2: The Invite Join Trend omits the final calendar day for any range whose end_date is not midnight, including the dashboard’s rolling range ending now. Deriving the series end date from end_date - interval '1 microsecond' would preserve the exclusive timestamp boundary while including the day containing events before it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/utils/pg.ts, line 3357:
<comment>The Invite Join Trend omits the final calendar day for any range whose `end_date` is not midnight, including the dashboard’s rolling range ending now. Deriving the series end date from `end_date - interval '1 microsecond'` would preserve the exclusive timestamp boundary while including the day containing events before it.</comment>
<file context>
@@ -3340,9 +3350,51 @@ export async function getAdminOnboardingFunnel(
+ WITH date_series AS (
+ SELECT generate_series(
+ ${start_date}::timestamptz::date,
+ (${end_date}::timestamptz::date - 1),
+ '1 day'::interval
+ )::date as date
</file context>
| (${end_date}::timestamptz::date - 1), | |
| (${end_date}::timestamptz - interval '1 microsecond')::date, |
| rb.granted_at::date as date, | ||
| COUNT(*) FILTER (WHERE u.created_via_invite = true)::int as org_joins_invite_register, | ||
| COUNT(*) FILTER (WHERE COALESCE(u.created_via_invite, false) = false)::int as org_joins_existing_account | ||
| FROM public.role_bindings rb |
There was a problem hiding this comment.
P2: Admin Users onboarding loads now add an unindexed role-binding aggregation, which can make the dashboard increasingly slow and increase primary-DB load as memberships grow. A partial index covering granted_at for user/org rows whose reason is Accepted invitation would keep this daily chart query bounded to the requested range.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/plugin_runtime/utils/pg.ts, line 3745:
<comment>Admin Users onboarding loads now add an unindexed role-binding aggregation, which can make the dashboard increasingly slow and increase primary-DB load as memberships grow. A partial index covering `granted_at` for user/org rows whose reason is `Accepted invitation` would keep this daily chart query bounded to the requested range.</comment>
<file context>
@@ -3711,9 +3721,51 @@ export async function getAdminOnboardingFunnel(
+ rb.granted_at::date as date,
+ COUNT(*) FILTER (WHERE u.created_via_invite = true)::int as org_joins_invite_register,
+ COUNT(*) FILTER (WHERE COALESCE(u.created_via_invite, false) = false)::int as org_joins_existing_account
+ FROM public.role_bindings rb
+ INNER JOIN public.users u ON u.id = rb.principal_id
+ WHERE rb.reason = 'Accepted invitation'
</file context>


Summary (AI generated)
created_via_invite = false)created_via_invite = true)role_bindings.reason = 'Accepted invitation'+ invite-created users)Motivation (AI generated)
The funnel missed register→org conversion for self-serve users, and invite-driven growth (new invite signups vs existing-account accepts) was not visible day by day.
Business Impact (AI generated)
Admins can separate organic self-serve activation from team-invite acquisition and see whether invitees actually join orgs.
Test Plan (AI generated)
bun run supabase:with-env -- bunx vitest run tests/admin-stats.test.ts -t "onboarding|uploaded bundle"Generated with AI
Note
Medium Risk
Changes how admin onboarding metrics are computed (SQL cohort filters on
created_via_inviteandrole_bindings), so historical funnel numbers will differ from before; impact is limited to internal admin analytics.Overview
Self-serve onboarding funnel now starts at user registration instead of org creation.
getAdminOnboardingFunnelcounts non-invite registrations, limits org cohorts to orgs whosecreated_byuser hascreated_via_invite = false, and exposestotal_registrations,org_conversion_rate, and per-daynew_registrationson the funnel trend (replacing global-stats registration overlay on the admin Users dashboard).A new Invite Join Trend chart and API fields (
invite_trend, invite totals) track daily invite signups and org joins fromrole_bindingswith reasonAccepted invitation, split between invite-created users and existing accounts.Copy and funnel UI add a Register → Org conversion step and a 7-column conversion grid;
tests/admin-stats.test.tsseeds invite/register users and asserts the new metrics.Reviewed by Cursor Bugbot for commit 1e68fd4. Bugbot is set up for automated code reviews on this repo. Configure here.