-
Notifications
You must be signed in to change notification settings - Fork 545
[WIP]: feat(onboarding): funnel analytics + variant tagging for the new flow #7960
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/onboarding-next-quest-cards-7739
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import Utils from './utils' | ||
|
|
||
| export type OnboardingVariant = 'control' | 'single_page' | ||
|
|
||
| // Which onboarding flow the user is in, from the onboarding_quickstart_flow | ||
| // flag the new flow is gated behind (#7738). | ||
| export const getOnboardingVariant = (): OnboardingVariant => | ||
| Utils.getFlagsmithHasFeature('onboarding_quickstart_flow') | ||
| ? 'single_page' | ||
| : 'control' | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,6 +16,8 @@ import { useOnboardingFlag } from 'components/pages/onboarding/hooks/useOnboardi | |||||||||||||||||||||||||
| import { useOnboardingConnection } from 'components/pages/onboarding/hooks/useOnboardingConnection' | ||||||||||||||||||||||||||
| import { useUpdateOrganisationMutation } from 'common/services/useOrganisation' | ||||||||||||||||||||||||||
| import { useUpdateProjectMutation } from 'common/services/useProject' | ||||||||||||||||||||||||||
| import API from 'project/api' | ||||||||||||||||||||||||||
| import Constants from 'common/constants' | ||||||||||||||||||||||||||
| import './OnboardingFlow.scss' | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // The single-page onboarding flow, rendered at /getting-started when | ||||||||||||||||||||||||||
|
|
@@ -139,6 +141,37 @@ const OnboardingFlow: FC = () => { | |||||||||||||||||||||||||
| history.push(`${base}/features?feature=${flagId}&tab=${tab}`) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const diagnosticIds = { | ||||||||||||||||||||||||||
| environment_id: environment?.id, | ||||||||||||||||||||||||||
| organisation_id: organisationId, | ||||||||||||||||||||||||||
| project_id: projectId, | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| const trackSnippetCopied = (snippet: 'install' | 'wire') => | ||||||||||||||||||||||||||
| API.trackEvent({ | ||||||||||||||||||||||||||
| ...Constants.events.ONBOARDING_SNIPPET_COPIED, | ||||||||||||||||||||||||||
| extra: { ...diagnosticIds, snippet }, | ||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||
|
Comment on lines
+149
to
+153
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Extract the inline union type. As per coding guidelines, ♻️ Proposed fix+type OnboardingSnippet = 'install' | 'wire'
+
- const trackSnippetCopied = (snippet: 'install' | 'wire') =>
+ const trackSnippetCopied = (snippet: OnboardingSnippet) =>📝 Committable suggestion
Suggested change
Source: Coding guidelines |
||||||||||||||||||||||||||
| const handleCopyInstall = () => { | ||||||||||||||||||||||||||
| trackSnippetCopied('install') | ||||||||||||||||||||||||||
| setInstallCopied(true) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| const handleCopyWire = () => { | ||||||||||||||||||||||||||
| trackSnippetCopied('wire') | ||||||||||||||||||||||||||
| setSnippetCopied(true) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| const handleCopyPrompt = () => | ||||||||||||||||||||||||||
| API.trackEvent({ | ||||||||||||||||||||||||||
| ...Constants.events.ONBOARDING_AI_CONNECT, | ||||||||||||||||||||||||||
| extra: diagnosticIds, | ||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||
| const handleToggleFlag = (enabled: boolean) => { | ||||||||||||||||||||||||||
| API.trackEvent({ | ||||||||||||||||||||||||||
| ...Constants.events.ONBOARDING_FLAG_TOGGLED, | ||||||||||||||||||||||||||
| extra: { ...diagnosticIds, enabled }, | ||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||
| toggleFlag(enabled) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (status === 'creating') { | ||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||
| <div className='onboarding-flow mx-auto text-center'> | ||||||||||||||||||||||||||
|
|
@@ -177,8 +210,9 @@ const OnboardingFlow: FC = () => { | |||||||||||||||||||||||||
| <OnboardingConnectPanel | ||||||||||||||||||||||||||
| environmentKey={environmentKey} | ||||||||||||||||||||||||||
| featureName={featureName} | ||||||||||||||||||||||||||
| onCopyInstall={() => setInstallCopied(true)} | ||||||||||||||||||||||||||
| onCopyWire={() => setSnippetCopied(true)} | ||||||||||||||||||||||||||
| onCopyInstall={handleCopyInstall} | ||||||||||||||||||||||||||
| onCopyWire={handleCopyWire} | ||||||||||||||||||||||||||
| onCopyPrompt={handleCopyPrompt} | ||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||
| <OnboardingTerminal | ||||||||||||||||||||||||||
| featureName={featureName} | ||||||||||||||||||||||||||
|
|
@@ -196,7 +230,7 @@ const OnboardingFlow: FC = () => { | |||||||||||||||||||||||||
| tags: flagTags, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| ]} | ||||||||||||||||||||||||||
| onToggle={(_flag, next) => toggleFlag(next)} | ||||||||||||||||||||||||||
| onToggle={(_flag, next) => handleToggleFlag(next)} | ||||||||||||||||||||||||||
| togglingFlag={isToggling ? featureName : null} | ||||||||||||||||||||||||||
| togglesReady={flagStateReady} | ||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,8 @@ import { | |
| } from 'common/types/responses' | ||
| import { SmartDefaults } from './useSmartDefaults' | ||
| import { createOrganisationViaAccountStore } from './createOrganisationViaAccountStore' | ||
| import API from 'project/api' | ||
| import Constants from 'common/constants' | ||
|
|
||
| type Store = ReturnType<typeof getStore> | ||
|
|
||
|
|
@@ -93,6 +95,10 @@ async function ensureProject( | |
| }), | ||
| ) | ||
| .unwrap() | ||
| // Auto-created setup is a milestone reached, not a user action: fire First | ||
| // Project created, not the generic Project created (reserved for projects a | ||
| // user makes by hand). First is safe here - we only get here with no project. | ||
| API.trackEvent(Constants.events.CREATE_FIRST_PROJECT) | ||
|
Comment on lines
+98
to
+101
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win Guard milestone tracking so analytics failures can't abort onboarding. Neither ♻️ Suggested fix- API.trackEvent(Constants.events.CREATE_FIRST_PROJECT)
+ try {
+ API.trackEvent(Constants.events.CREATE_FIRST_PROJECT)
+ } catch {
+ // Analytics is best-effort - never block onboarding on tracking failures.
+ } if (isFirstFeature) {
- API.trackEvent(Constants.events.CREATE_FIRST_FEATURE)
+ try {
+ API.trackEvent(Constants.events.CREATE_FIRST_FEATURE)
+ } catch {
+ // Analytics is best-effort - never block onboarding on tracking failures.
+ }
}Also applies to: 192-196 |
||
| await store | ||
| .dispatch( | ||
| environmentService.endpoints.createEnvironment.initiate({ | ||
|
|
@@ -170,7 +176,8 @@ async function ensureFlag( | |
| if (existing) { | ||
| return existing | ||
| } | ||
| return store | ||
| const isFirstFeature = !flags?.results?.length | ||
| const created = await store | ||
| .dispatch( | ||
| projectFlagService.endpoints.createProjectFlag.initiate({ | ||
| body: { | ||
|
|
@@ -182,6 +189,11 @@ async function ensureFlag( | |
| }), | ||
| ) | ||
| .unwrap() | ||
| // Milestone, not action (see the project create above): First only. | ||
| if (isFirstFeature) { | ||
| API.trackEvent(Constants.events.CREATE_FIRST_FEATURE) | ||
| } | ||
| return created | ||
| } | ||
|
|
||
| // Attach the "Onboarding" tag to the demo flag (find-or-create), so the flags | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,6 +12,7 @@ import { AccountModel, User } from 'common/types/responses' | |||||||||||||||||||||||||
| import AccountStore from 'common/stores/account-store' | ||||||||||||||||||||||||||
| import flagsmith from '@flagsmith/flagsmith' | ||||||||||||||||||||||||||
| import Utils from 'common/utils/utils' | ||||||||||||||||||||||||||
| import { getOnboardingVariant } from 'common/utils/getOnboardingVariant' | ||||||||||||||||||||||||||
| import loadChat, { identifyChatUser } from 'common/loadChat' | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const API = { | ||||||||||||||||||||||||||
|
|
@@ -247,7 +248,12 @@ const API = { | |||||||||||||||||||||||||
| role: selectedRole, | ||||||||||||||||||||||||||
| tasks: user.onboarding?.tasks?.map((t) => t.name) || [], | ||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||
| API.flagsmithIdentify() | ||||||||||||||||||||||||||
| // Pin the onboarding variant as a user property once the flag has | ||||||||||||||||||||||||||
| // resolved, so every event (incl. autocapture pageviews) is attributable | ||||||||||||||||||||||||||
| // to control vs single_page in one funnel (#7738). | ||||||||||||||||||||||||||
| API.flagsmithIdentify()?.then(() => | ||||||||||||||||||||||||||
| API.trackTraits({ onboarding_variant: getOnboardingVariant() }), | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
Comment on lines
+251
to
+256
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Add
🛡️ Suggested fix API.flagsmithIdentify()?.then(() =>
API.trackTraits({ onboarding_variant: getOnboardingVariant() }),
- )
+ ).catch((err) => console.error('Error tracking onboarding_variant', err))📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||
| console.error('Error identifying', err) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Use the alias import path instead of a relative import.
As per coding guidelines,
frontend/**/*.{js,jsx,ts,tsx}should "Use onlycommon/,components/, andproject/import paths; do not use relative imports."♻️ Proposed fix
📝 Committable suggestion
Source: Coding guidelines