Skip to content

Commit 29a24c9

Browse files
adrigugcursoragent
andcommitted
feat(analytics): track Onboarding Completed on success screen
Fire the Onboarding Completed MetaMetrics event when the user taps Done on the onboarding success screen, with schema-aligned properties including implementation_type and onboarding_type. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 87e58f8 commit 29a24c9

8 files changed

Lines changed: 361 additions & 21 deletions

File tree

app/components/Views/OnboardingSuccess/index.test.tsx

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,33 @@ import renderWithProvider from '../../../util/test/renderWithProvider';
1010
import { OnboardingSuccessSelectorIDs } from './OnboardingSuccess.testIds';
1111
import { fireEvent, waitFor } from '@testing-library/react-native';
1212
import Routes from '../../../constants/navigation/Routes';
13-
import { ONBOARDING_SUCCESS_FLOW } from '../../../constants/onboarding';
13+
import { ONBOARDING_SUCCESS_FLOW , AccountType } from '../../../constants/onboarding';
1414
import Engine from '../../../core/Engine/Engine';
1515
import { strings } from '../../../../locales/i18n';
16-
import { useSelector } from 'react-redux';
1716
import Logger from '../../../util/Logger';
1817
import {
1918
SET_WALLET_HOME_ONBOARDING_STEPS_ELIGIBLE,
2019
setWalletHomeOnboardingStepsEligible,
2120
} from '../../../actions/onboarding';
21+
import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding';
22+
import { selectOnboardingAccountType } from '../../../selectors/onboarding';
23+
import { selectBasicFunctionalityEnabled } from '../../../selectors/settings';
24+
25+
jest.mock('../../../util/metrics/TrackOnboarding/trackOnboarding');
26+
27+
jest.mock('../../../selectors/onboarding', () => ({
28+
...jest.requireActual('../../../selectors/onboarding'),
29+
selectOnboardingAccountType: jest.fn(),
30+
}));
31+
32+
jest.mock('../../../selectors/settings', () => ({
33+
...jest.requireActual('../../../selectors/settings'),
34+
selectBasicFunctionalityEnabled: jest.fn(),
35+
}));
36+
37+
const mockTrackOnboarding = trackOnboarding as jest.MockedFunction<
38+
typeof trackOnboarding
39+
>;
2240

2341
jest.mock('../../../core/Engine/Engine', () => ({
2442
context: {
@@ -97,13 +115,16 @@ const mockDispatch = jest.fn();
97115

98116
jest.mock('react-redux', () => ({
99117
...jest.requireActual('react-redux'),
100-
useSelector: jest.fn(),
101118
useDispatch: () => mockDispatch,
102119
}));
103120

104121
describe('OnboardingSuccessComponent', () => {
105122
beforeEach(() => {
106123
jest.clearAllMocks();
124+
jest
125+
.mocked(selectOnboardingAccountType)
126+
.mockReturnValue(AccountType.Imported);
127+
jest.mocked(selectBasicFunctionalityEnabled).mockReturnValue(true);
107128
});
108129

109130
it('renders correctly when successFlow is BACKED_UP_SRP', () => {
@@ -152,6 +173,20 @@ describe('OnboardingSuccessComponent', () => {
152173
const button = getByTestId(OnboardingSuccessSelectorIDs.DONE_BUTTON);
153174
fireEvent.press(button);
154175

176+
expect(mockTrackOnboarding).toHaveBeenCalledWith(
177+
expect.objectContaining({
178+
name: 'Onboarding Completed',
179+
properties: expect.objectContaining({
180+
wallet_setup_type: 'import',
181+
new_wallet: false,
182+
account_type: 'imported',
183+
is_basic_functionality_enabled: true,
184+
implementation_type: 'native',
185+
onboarding_type: 'seed_phrase',
186+
}),
187+
}),
188+
expect.any(Function),
189+
);
155190
expect(mockDiscoverAccounts).toHaveBeenCalled();
156191
expect(mockDispatch).toHaveBeenCalledWith(
157192
setWalletHomeOnboardingStepsEligible(true, {
@@ -196,6 +231,7 @@ describe('OnboardingSuccessComponent', () => {
196231
);
197232
fireEvent.press(getByTestId(OnboardingSuccessSelectorIDs.DONE_BUTTON));
198233

234+
expect(mockTrackOnboarding).not.toHaveBeenCalled();
199235
expect(
200236
mockDispatch.mock.calls.some(
201237
(call) => call[0]?.type === SET_WALLET_HOME_ONBOARDING_STEPS_ELIGIBLE,
@@ -298,8 +334,6 @@ describe('OnboardingSuccessComponent', () => {
298334

299335
describe('OnboardingSuccess', () => {
300336
beforeEach(() => {
301-
// Reset mocks before each test
302-
(useSelector as jest.Mock).mockReset();
303337
mockDiscoverAccounts.mockReset();
304338
mockRouteParams = {};
305339
});

app/components/Views/OnboardingSuccess/index.tsx

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useCallback, useLayoutEffect } from 'react';
22
import { Platform } from 'react-native';
3-
import { useDispatch } from 'react-redux';
3+
import { useDispatch, useSelector } from 'react-redux';
44
import { SafeAreaView } from 'react-native-safe-area-context';
55
import {
66
CommonActions,
@@ -14,8 +14,17 @@ import { OnboardingSuccessSelectorIDs } from './OnboardingSuccess.testIds';
1414

1515
import OnboardingSuccessEndAnimation from './OnboardingSuccessEndAnimation/index';
1616
import { ONBOARDING_SUCCESS_FLOW } from '../../../constants/onboarding';
17-
import { setWalletHomeOnboardingStepsEligible } from '../../../actions/onboarding';
17+
import {
18+
saveOnboardingEvent as saveEvent,
19+
setWalletHomeOnboardingStepsEligible,
20+
} from '../../../actions/onboarding';
1821
import { shouldMarkWalletHomeOnboardingStepsEligible } from '../../../util/onboarding/walletHomeOnboardingStepsEligibility';
22+
import { MetaMetricsEvents } from '../../../core/Analytics';
23+
import { AnalyticsEventBuilder } from '../../../util/analytics/AnalyticsEventBuilder';
24+
import { getOnboardingCompletedAnalyticsPropsFromSuccessFlow } from '../../../util/analytics/onboardingCompletedAnalytics';
25+
import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding';
26+
import { selectOnboardingAccountType } from '../../../selectors/onboarding';
27+
import { selectBasicFunctionalityEnabled } from '../../../selectors/settings';
1928

2029
import Engine from '../../../core/Engine/Engine';
2130
import { discoverAccounts } from '../../../multichain-accounts/discovery';
@@ -59,6 +68,10 @@ export const OnboardingSuccessComponent: React.FC<OnboardingSuccessProps> = ({
5968
}) => {
6069
const navigation = useNavigation();
6170
const dispatch = useDispatch();
71+
const accountType = useSelector(selectOnboardingAccountType);
72+
const isBasicFunctionalityEnabled = useSelector(
73+
selectBasicFunctionalityEnabled,
74+
);
6275

6376
const tw = useTailwind();
6477

@@ -74,30 +87,40 @@ export const OnboardingSuccessComponent: React.FC<OnboardingSuccessProps> = ({
7487

7588
const handleOnDone = useCallback(() => {
7689
if (shouldMarkWalletHomeOnboardingStepsEligible(successFlow)) {
90+
const onboardingCompletedProperties =
91+
getOnboardingCompletedAnalyticsPropsFromSuccessFlow(successFlow, {
92+
accountType,
93+
isBasicFunctionalityEnabled,
94+
});
95+
96+
trackOnboarding(
97+
AnalyticsEventBuilder.createEventBuilder(
98+
MetaMetricsEvents.ONBOARDING_COMPLETED,
99+
)
100+
.addProperties(onboardingCompletedProperties)
101+
.build(),
102+
(event) => dispatch(saveEvent([event])),
103+
);
104+
77105
dispatch(
78106
setWalletHomeOnboardingStepsEligible(true, {
79107
skipInitialBalanceWait: true,
80108
}),
81109
);
82110
}
83111

84-
const runDiscoverAccounts = async () => {
85-
try {
86-
await discoverAccounts(
87-
Engine.context.KeyringController.state.keyrings[0].metadata.id,
88-
);
89-
} catch (error) {
90-
Logger.error(
91-
error as Error,
92-
'OnboardingSuccess: discoverAccounts failed',
93-
);
94-
}
95-
};
96-
void runDiscoverAccounts();
112+
discoverAccounts(
113+
Engine.context.KeyringController.state.keyrings[0].metadata.id,
114+
).catch((error: unknown) => {
115+
Logger.error(
116+
error as Error,
117+
'OnboardingSuccess: discoverAccounts failed',
118+
);
119+
});
97120
queueMicrotask(() => {
98121
onDone();
99122
});
100-
}, [dispatch, onDone, successFlow]);
123+
}, [accountType, dispatch, isBasicFunctionalityEnabled, onDone, successFlow]);
101124

102125
const getTitleString = () => {
103126
if (successFlow === ONBOARDING_SUCCESS_FLOW.SETTINGS_BACKUP) {

app/core/Analytics/MetaMetrics.events.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ enum EVENT_NAME {
172172
ONBOARDING_TOUR_STEP_COMPLETED = 'Onboarding Tour Step Completed',
173173
ONBOARDING_TOUR_STEP_REVISITED = 'Onboarding Tour Step Completed',
174174
ONBOARDING_TOUR_COMPLETED = 'Onboarding Tour Completed',
175+
ONBOARDING_COMPLETED = 'Onboarding Completed',
175176

176177
// Wallet Setup
177178
WALLET_SETUP_STARTED = 'Wallet Setup Started',
@@ -1002,6 +1003,7 @@ const events = {
10021003
EVENT_NAME.ONBOARDING_TOUR_STEP_REVISITED,
10031004
),
10041005
ONBOARDING_TOUR_COMPLETED: generateOpt(EVENT_NAME.ONBOARDING_TOUR_COMPLETED),
1006+
ONBOARDING_COMPLETED: generateOpt(EVENT_NAME.ONBOARDING_COMPLETED),
10051007
WALLET_SETUP_STARTED: generateOpt(EVENT_NAME.WALLET_SETUP_STARTED),
10061008
WALLET_IMPORT_STARTED: generateOpt(EVENT_NAME.WALLET_IMPORT_STARTED),
10071009
WALLET_IMPORT_ATTEMPTED: generateOpt(EVENT_NAME.WALLET_IMPORT_ATTEMPTED),
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import {
2+
getOnboardingCompletedAnalyticsProps,
3+
getOnboardingCompletedAnalyticsPropsFromSuccessFlow,
4+
getWalletSetupPropsFromSuccessFlow,
5+
normalizeOnboardingCompletedAccountType,
6+
OnboardingCompletedAccountType,
7+
ONBOARDING_IMPLEMENTATION_TYPE_NATIVE,
8+
ONBOARDING_TYPE_SEED_PHRASE,
9+
ONBOARDING_TYPE_SOCIAL_LOGIN,
10+
} from './onboardingCompletedAnalytics';
11+
import {
12+
AccountType,
13+
ONBOARDING_SUCCESS_FLOW,
14+
} from '../../constants/onboarding';
15+
16+
describe('normalizeOnboardingCompletedAccountType', () => {
17+
it('maps social account types to schema account types', () => {
18+
expect(normalizeOnboardingCompletedAccountType('metamask_google')).toBe(
19+
OnboardingCompletedAccountType.Metamask,
20+
);
21+
expect(normalizeOnboardingCompletedAccountType('imported_apple')).toBe(
22+
OnboardingCompletedAccountType.Imported,
23+
);
24+
});
25+
26+
it('preserves hardware wallet account types', () => {
27+
expect(normalizeOnboardingCompletedAccountType('Ledger')).toBe(
28+
OnboardingCompletedAccountType.Ledger,
29+
);
30+
expect(normalizeOnboardingCompletedAccountType('QR Hardware')).toBe(
31+
OnboardingCompletedAccountType.QrHardware,
32+
);
33+
});
34+
});
35+
36+
describe('getWalletSetupPropsFromSuccessFlow', () => {
37+
it('returns import props for seed phrase import flow', () => {
38+
expect(
39+
getWalletSetupPropsFromSuccessFlow(
40+
ONBOARDING_SUCCESS_FLOW.IMPORT_FROM_SEED_PHRASE,
41+
),
42+
).toEqual({
43+
wallet_setup_type: 'import',
44+
new_wallet: false,
45+
isSocialLogin: false,
46+
});
47+
});
48+
49+
it('returns social login props for seedless onboarding flow', () => {
50+
expect(
51+
getWalletSetupPropsFromSuccessFlow(
52+
ONBOARDING_SUCCESS_FLOW.SEEDLESS_ONBOARDING,
53+
),
54+
).toEqual({
55+
wallet_setup_type: 'new',
56+
new_wallet: true,
57+
isSocialLogin: true,
58+
});
59+
});
60+
});
61+
62+
describe('getOnboardingCompletedAnalyticsProps', () => {
63+
it('adds native implementation and seed phrase onboarding type', () => {
64+
expect(
65+
getOnboardingCompletedAnalyticsProps(
66+
{
67+
wallet_setup_type: 'new',
68+
new_wallet: true,
69+
account_type: AccountType.Metamask,
70+
is_basic_functionality_enabled: true,
71+
},
72+
false,
73+
),
74+
).toEqual({
75+
wallet_setup_type: 'new',
76+
new_wallet: true,
77+
account_type: OnboardingCompletedAccountType.Metamask,
78+
is_basic_functionality_enabled: true,
79+
implementation_type: ONBOARDING_IMPLEMENTATION_TYPE_NATIVE,
80+
onboarding_type: ONBOARDING_TYPE_SEED_PHRASE,
81+
});
82+
});
83+
84+
it('adds social login onboarding type when requested', () => {
85+
expect(
86+
getOnboardingCompletedAnalyticsProps(
87+
{
88+
wallet_setup_type: 'new',
89+
new_wallet: true,
90+
account_type: AccountType.MetamaskGoogle,
91+
},
92+
true,
93+
),
94+
).toEqual({
95+
wallet_setup_type: 'new',
96+
new_wallet: true,
97+
account_type: OnboardingCompletedAccountType.Metamask,
98+
implementation_type: ONBOARDING_IMPLEMENTATION_TYPE_NATIVE,
99+
onboarding_type: ONBOARDING_TYPE_SOCIAL_LOGIN,
100+
});
101+
});
102+
});
103+
104+
describe('getOnboardingCompletedAnalyticsPropsFromSuccessFlow', () => {
105+
it('builds import onboarding completed props', () => {
106+
expect(
107+
getOnboardingCompletedAnalyticsPropsFromSuccessFlow(
108+
ONBOARDING_SUCCESS_FLOW.IMPORT_FROM_SEED_PHRASE,
109+
{
110+
accountType: AccountType.Imported,
111+
isBasicFunctionalityEnabled: true,
112+
},
113+
),
114+
).toEqual({
115+
wallet_setup_type: 'import',
116+
new_wallet: false,
117+
account_type: OnboardingCompletedAccountType.Imported,
118+
is_basic_functionality_enabled: true,
119+
implementation_type: ONBOARDING_IMPLEMENTATION_TYPE_NATIVE,
120+
onboarding_type: ONBOARDING_TYPE_SEED_PHRASE,
121+
});
122+
});
123+
});

0 commit comments

Comments
 (0)