Operator onboarding events
Funnel analytics for the operator onboarding flow (/operator/onboarding), emitted from the web frontend and landing in the ClickHouse vf.events table.
Pipeline
OnboardingFlow.tsx
→ track(event, properties) frontend/src/lib/analytics.ts
→ POST /api/track (sendBeacon) survives page unload
→ captureEvent() api/src/analytics.ts
→ chInsert("events", …) api/src/clickhouse.ts
→ vf.events ClickHouse (partitioned by env + month)track() is fire-and-forget: it never throws and never blocks the UI. The server resolves actor identity (actor_type, actor_id, distinct_id) and the active feature-flag snapshot from the session cookie — the client only sends the event name, properties, session_id, and page_path.
For an onboarding operator every row carries:
| Column | Value |
|---|---|
actor_type | operator |
actor_id | the operator's numeric id |
distinct_id | o<id> |
event_name | one of the names below |
page_path | /operator/onboarding |
properties | JSON, per the tables below |
Event catalog
The flow is a 2-step wizard (Step 1 = company info, Step 2 = brand profile), preceded by the /operator/signin page. Events are fired from OperatorSignIn.tsx (sign-in) and OnboardingFlow.tsx (the wizard).
operator_onboarding_signin_viewed
Fired once per browser session when the operator sign-in page (/operator/signin) mounts — the true top of the funnel, before the operator is authenticated. Guarded by a useRef so a re-render does not double-count it. Note: this row has no actor_id (the operator isn't signed in yet); join the funnel on session_id.
| Property | Type | Notes |
|---|---|---|
has_email_prefill | boolean | true when arriving via the ?email= recovery link. |
operator_onboarding_started
Fired once per browser session when the onboarding flow first mounts with an authenticated operator. Guarded by a useRef so a re-render or a resumed session does not double-count it.
| Property | Type | Notes |
|---|---|---|
resume_step | 1 | 2 | Step the operator resumed on (2 if legal_name is saved). |
operator_onboarding_step_viewed
Fired every time a step is rendered — on first mount (for the resume step) and on each forward transition. Use it to measure per-step reach and drop-off.
| Property | Type | Notes |
|---|---|---|
step | 1 | 2 | The step now visible to the operator. |
operator_onboarding_step_submitted
Fired when a step's "Continue" action resolves — both on success and on a persistence error. The success flag is the funnel's primary conversion signal per step; error captures the parsed API message on failure.
| Property | Type | Notes |
|---|---|---|
step | 1 | 2 | Step being submitted. |
success | boolean | true if the API write succeeded. |
error | string | Present only when success: false — parsed message. |
operator_onboarding_asset_uploaded
Fired when a Step 2 logo/cover background upload resolves — on success and on failure. Uploads start the moment a file is picked (not on Save), so this event measures the reliability of the Sanity (with S3 fallback) image pipeline independently of the step-submit conversion.
| Property | Type | Notes |
|---|---|---|
kind | "logo" | "cover" | Which asset was uploaded. |
success | boolean | true if the upload stored. |
error | string | Present only when success: false. |
operator_onboarding_completed
Fired immediately after Step 2 persists successfully, just before the redirect to /operator/campaigns. Marks a fully onboarded operator.
| Property | Type | Notes |
|---|---|---|
| — | — | No props. |
Funnel shape
operator_onboarding_signin_viewed
→ operator_onboarding_started
→ operator_onboarding_step_viewed { step: 1 }
→ operator_onboarding_step_submitted { step: 1, success: true }
→ operator_onboarding_step_viewed { step: 2 }
→ (operator_onboarding_asset_uploaded { kind: logo|cover, success })
→ operator_onboarding_step_submitted { step: 2, success: true }
→ operator_onboarding_completedThe Grafana view of this funnel lives in the operations repo at clusters/prod/monitoring/dashboards/verifluence-operator-onboarding.json (UID vf-operator-onboarding). See docs/engineering/monitoring/dashboards.md.
A resumed session starts at step: 2; a Step 1 failure repeats step_submitted { step: 1, success: false } until it succeeds.
Useful queries
-- Onboarding funnel (last 30 days), one row per stage
SELECT event_name, count() AS n
FROM vf.events
WHERE env = 'prod'
AND event_name LIKE 'operator_onboarding_%'
AND occurred_at >= now() - INTERVAL 30 DAY
GROUP BY event_name
ORDER BY n DESC;
-- Step completion rate (started → completed)
SELECT
countIf(event_name = 'operator_onboarding_started') AS started,
countIf(event_name = 'operator_onboarding_completed') AS completed,
round(completed / started, 3) AS conversion
FROM vf.events
WHERE env = 'prod'
AND event_name LIKE 'operator_onboarding_%'
AND occurred_at >= now() - INTERVAL 30 DAY;
-- Step submit failures, with the surfaced error
SELECT
JSONExtractInt(properties, 'step') AS step,
JSONExtractString(properties, 'error') AS error,
count() AS n
FROM vf.events
WHERE env = 'prod'
AND event_name = 'operator_onboarding_step_submitted'
AND JSONExtractBool(properties, 'success') = 0
AND occurred_at >= now() - INTERVAL 30 DAY
GROUP BY step, error
ORDER BY n DESC;Adding a new onboarding event
- Call
track("operator_onboarding_<name>", { … })at the call site inOnboardingFlow.tsx(or a step component). - Add a row to the Event catalog above with its properties.
- No schema change is needed —
vf.eventsstorespropertiesas JSON, so new events and props flow through without a migration.