Skip to content

fix(admin): add register→org onboarding funnel step - #2883

Merged
riderx merged 2 commits into
mainfrom
fix/admin-onboarding-funnel-register-to-org
Aug 6, 2026
Merged

fix(admin): add register→org onboarding funnel step#2883
riderx merged 2 commits into
mainfrom
fix/admin-onboarding-funnel-register-to-org

Conversation

@riderx

@riderx riderx commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary (AI generated)

  • Add Register → Org as the first Onboarding Funnel stage (self-serve only, created_via_invite = false)
  • Filter org cohorts by non-invite creators
  • Add Invite Join Trend daily chart with:
    • Invite registrations (created_via_invite = true)
    • Org joins after invite register (role_bindings.reason = 'Accepted invitation' + invite-created users)
    • Org joins from existing accounts (same binding reason + non-invite users)
  • Update admin users dashboard UI, copy, and admin-stats coverage

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"
  • Open Admin → Users: confirm Register → Org funnel step
  • Confirm Invite Join Trend shows the three daily series
  • Confirm invite-created users do not inflate self-serve registration counts

Generated with AI


Note

Medium Risk
Changes how admin onboarding metrics are computed (SQL cohort filters on created_via_invite and role_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. getAdminOnboardingFunnel counts non-invite registrations, limits org cohorts to orgs whose created_by user has created_via_invite = false, and exposes total_registrations, org_conversion_rate, and per-day new_registrations on 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 from role_bindings with reason Accepted 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.ts seeds 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.

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>
@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The 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.

Changes

Onboarding Funnel

Layer / File(s) Summary
Backend registration metrics
supabase/functions/_backend/utils/pg.ts, supabase/functions/_backend/plugin_runtime/utils/pg.ts
Funnel and trend queries now include self-serve registrations, exclude invited users, calculate organization conversion from registrations, and return zero-valued fallback fields.
Dashboard funnel presentation
src/pages/admin/dashboard/users.vue, messages/en.json
The dashboard displays registrations as the initial funnel stage, adds registration-to-organization conversion, uses daily registration data, and updates labels and grid sizing.
Funnel fixture and assertion coverage
tests/admin-stats.test.ts
Tests add registered and invited-user fixtures, validate registration metrics, and update onboarding-history ownership and cleanup.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested labels: codex

Suggested reviewers: wcaleniewolny

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly identifies the main change: adding the Register-to-Org onboarding funnel step.
Description check ✅ Passed The description clearly explains the objectives and includes relevant test coverage, but it omits the repository checklist and screenshots for frontend changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@coderabbitai coderabbitai Bot added the codex label Aug 6, 2026
@codspeed-hq

codspeed-hq Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing fix/admin-onboarding-funnel-register-to-org (1e68fd4) with main (8301383)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Track invite signups plus org joins from invite-created vs existing accounts on a dedicated admin chart.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
7.4% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@cubic-dev-ai cubic-dev-ai 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.

1 issue found and verified against the latest diff

Confidence score: 3/5

  • In tests/admin-stats.test.ts, the trial_plan_breakdown query appears to include orgs created via onboarding invite (ONBOARDING_INVITE_ORG) because it lacks the created_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

Comment thread tests/admin-stats.test.ts
created_at: ONBOARDING_LATE_SUBSCRIPTION_CREATED_AT,
},
{
id: ONBOARDING_INVITE_ORG_ID,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

@cubic-dev-ai cubic-dev-ai 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.

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 when end_date includes 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 from end_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 on granted_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),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>
Suggested change
(${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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

@riderx
riderx merged commit 248ef1b into main Aug 6, 2026
87 of 94 checks passed
@riderx
riderx deleted the fix/admin-onboarding-funnel-register-to-org branch August 6, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant