Skip to content

Commit d2ccb55

Browse files
authored
fix: add user onboarded version [TO-597] (#27573)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> Add onboarding version in onboarding state which track the app version user used when they created the wallet It will helps in sentry debugging knowing which version build the wallet is created ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it changes the `setAccountType` action shape and extends persisted onboarding Redux state, which could break any unupdated dispatchers/selectors or state rehydration expectations. > > **Overview** > Adds `onboardingVersion` (e.g., `"7.0.0 (1234)"`) to onboarding Redux state and wires it into `SET_ACCOUNT_TYPE`/`CLEAR_ACCOUNT_TYPE` so the app records which build a wallet was created/imported on. > > Updates the onboarding screen to compute the version from `react-native-device-info` and include it when dispatching `setAccountType` for create/import/social-login flows, with tests adjusted/added to assert the new field is stored and cleared. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit ce15ac5. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 27cc72a commit d2ccb55

6 files changed

Lines changed: 119 additions & 10 deletions

File tree

app/actions/onboarding/index.test.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,32 @@ describe('Onboarding actions', () => {
4646

4747
describe('setAccountType', () => {
4848
it('creates an action to set accountType', () => {
49-
expect(setAccountType(AccountType.Metamask)).toEqual({
49+
const onboardingVersion = '7.0.0 (1234)';
50+
51+
expect(
52+
setAccountType({
53+
accountType: AccountType.Metamask,
54+
onboardingVersion,
55+
}),
56+
).toEqual({
5057
type: SET_ACCOUNT_TYPE,
5158
accountType: AccountType.Metamask,
59+
onboardingVersion,
5260
});
5361
});
5462

5563
it('creates an action with social login account type', () => {
56-
expect(setAccountType(AccountType.MetamaskGoogle)).toEqual({
64+
const onboardingVersion = '7.0.0 (1234)';
65+
66+
expect(
67+
setAccountType({
68+
accountType: AccountType.MetamaskGoogle,
69+
onboardingVersion,
70+
}),
71+
).toEqual({
5772
type: SET_ACCOUNT_TYPE,
5873
accountType: AccountType.MetamaskGoogle,
74+
onboardingVersion,
5975
});
6076
});
6177
});

app/actions/onboarding/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface SetCompletedOnboardingAction {
2424
interface SetAccountTypeAction {
2525
type: typeof SET_ACCOUNT_TYPE;
2626
accountType: AccountType;
27+
onboardingVersion: string;
2728
}
2829

2930
interface ClearAccountTypeAction {
@@ -61,10 +62,14 @@ export function setCompletedOnboarding(
6162
};
6263
}
6364

64-
export function setAccountType(accountType: AccountType): SetAccountTypeAction {
65+
export function setAccountType(params: {
66+
accountType: AccountType;
67+
onboardingVersion: string;
68+
}): SetAccountTypeAction {
6569
return {
6670
type: SET_ACCOUNT_TYPE,
67-
accountType,
71+
accountType: params.accountType,
72+
onboardingVersion: params.onboardingVersion,
6873
};
6974
}
7075

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ jest.mock('react-native-elevated-view', () => ({
4141
default: jest.requireActual('react-native').View,
4242
}));
4343

44+
const MOCK_APP_VERSION = '7.0.0';
45+
const MOCK_BUILD_NUMBER = '1234';
46+
const MOCK_ONBOARDING_VERSION = `${MOCK_APP_VERSION} (${MOCK_BUILD_NUMBER})`;
47+
48+
jest.mock('react-native-device-info', () => ({
49+
getVersion: jest.fn(() => MOCK_APP_VERSION),
50+
getBuildNumber: jest.fn(() => MOCK_BUILD_NUMBER),
51+
}));
52+
4453
import React from 'react';
4554
import {
4655
InteractionManager,
@@ -65,6 +74,7 @@ import { OAuthError, OAuthErrorType } from '../../../core/OAuthService/error';
6574
import { captureException } from '@sentry/react-native';
6675
import Logger from '../../../util/Logger';
6776
import { MIGRATION_ERROR_HAPPENED } from '../../../constants/storage';
77+
import { AccountType } from '../../../constants/onboarding';
6878

6979
// Mock netinfo - using existing mock
7080
jest.mock('@react-native-community/netinfo');
@@ -636,6 +646,38 @@ describe('Onboarding', () => {
636646
);
637647
});
638648

649+
it('stores the onboarding version on the first create wallet press', async () => {
650+
mockSeedlessOnboardingEnabled.mockReturnValue(false);
651+
(StorageWrapper.getItem as jest.Mock).mockResolvedValue(null);
652+
653+
const { getByTestId, store } = renderScreen(
654+
Onboarding,
655+
{ name: 'Onboarding' },
656+
{
657+
state: mockInitialState,
658+
},
659+
);
660+
661+
const createWalletButton = getByTestId(
662+
OnboardingSelectorIDs.NEW_WALLET_BUTTON,
663+
);
664+
665+
await act(async () => {
666+
fireEvent.press(createWalletButton);
667+
await flushPromises();
668+
await flushPromises();
669+
});
670+
671+
await waitFor(() => {
672+
expect(store.getState().onboarding).toEqual(
673+
expect.objectContaining({
674+
accountType: AccountType.Metamask,
675+
onboardingVersion: MOCK_ONBOARDING_VERSION,
676+
}),
677+
);
678+
});
679+
});
680+
639681
it('navigates to offline error sheet when there is no internet', async () => {
640682
mockSeedlessOnboardingEnabled.mockReturnValue(true);
641683
(StorageWrapper.getItem as jest.Mock).mockResolvedValue(null);

app/components/Views/Onboarding/index.tsx

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, {
22
useState,
33
useEffect,
4+
useMemo,
45
useRef,
56
useCallback,
67
useContext,
@@ -104,6 +105,7 @@ import {
104105
} from '@metamask/design-system-react-native';
105106
import { useTailwind } from '@metamask/design-system-twrnc-preset';
106107

108+
import { getBuildNumber, getVersion } from 'react-native-device-info';
107109
interface OnboardingState {
108110
warningModalVisible: boolean;
109111
loading: boolean;
@@ -132,6 +134,11 @@ interface OnboardingRouteParams {
132134

133135
const Onboarding = () => {
134136
const navigation = useNavigation();
137+
const onboardingVersion = useMemo(
138+
() => `${getVersion()} (${getBuildNumber()})`,
139+
[],
140+
);
141+
135142
const route =
136143
useRoute<RouteProp<{ params: OnboardingRouteParams }, 'params'>>();
137144
const dispatch = useDispatch();
@@ -357,15 +364,27 @@ const Onboarding = () => {
357364
[PREVIOUS_SCREEN]: ONBOARDING,
358365
onboardingTraceCtx: onboardingTraceCtx.current,
359366
});
360-
dispatch(setAccountType(AccountType.Metamask));
367+
dispatch(
368+
setAccountType({
369+
accountType: AccountType.Metamask,
370+
onboardingVersion,
371+
}),
372+
);
361373
track(MetaMetricsEvents.WALLET_SETUP_STARTED, {
362374
account_type: AccountType.Metamask,
363375
});
364376
};
365377

366378
handleExistingUser(action);
367379
endTrace({ name: TraceName.OnboardingCreateWallet });
368-
}, [metrics, navigation, track, handleExistingUser, dispatch]);
380+
}, [
381+
metrics,
382+
navigation,
383+
track,
384+
handleExistingUser,
385+
dispatch,
386+
onboardingVersion,
387+
]);
369388

370389
const onPressImport = useCallback(async (): Promise<void> => {
371390
if (SEEDLESS_ONBOARDING_ENABLED) {
@@ -392,13 +411,25 @@ const Onboarding = () => {
392411
onboardingTraceCtx: onboardingTraceCtx.current,
393412
},
394413
);
395-
dispatch(setAccountType(AccountType.Imported));
414+
dispatch(
415+
setAccountType({
416+
accountType: AccountType.Imported,
417+
onboardingVersion,
418+
}),
419+
);
396420
track(MetaMetricsEvents.WALLET_IMPORT_STARTED, {
397421
account_type: AccountType.Imported,
398422
});
399423
};
400424
handleExistingUser(action);
401-
}, [metrics, navigation, track, handleExistingUser, dispatch]);
425+
}, [
426+
metrics,
427+
navigation,
428+
track,
429+
handleExistingUser,
430+
dispatch,
431+
onboardingVersion,
432+
]);
402433

403434
const handlePostSocialLogin = useCallback(
404435
(
@@ -421,7 +452,7 @@ const Onboarding = () => {
421452
}
422453

423454
const accountType = getSocialAccountType(provider, result.existingUser);
424-
dispatch(setAccountType(accountType));
455+
dispatch(setAccountType({ accountType, onboardingVersion }));
425456

426457
track(MetaMetricsEvents.SOCIAL_LOGIN_COMPLETED, {
427458
account_type: accountType,
@@ -493,7 +524,7 @@ const Onboarding = () => {
493524
});
494525
}
495526
},
496-
[navigation, track, dispatch],
527+
[navigation, track, dispatch, onboardingVersion],
497528
);
498529

499530
const handleOAuthLoginError = useCallback(

app/reducers/onboarding/index.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,33 @@ describe('onboardingReducer', () => {
4747
});
4848

4949
it('handles the SET_ACCOUNT_TYPE action', () => {
50+
const onboardingVersion = '7.0.0 (1234)';
51+
5052
const action = {
5153
type: SET_ACCOUNT_TYPE,
5254
accountType: AccountType.MetamaskGoogle,
55+
onboardingVersion,
5356
} as const;
57+
5458
const state = onboardingReducer(initialState, action);
59+
5560
expect(state.accountType).toBe(AccountType.MetamaskGoogle);
61+
expect(state.onboardingVersion).toBe(onboardingVersion);
5662
});
5763

5864
it('handles the CLEAR_ACCOUNT_TYPE action', () => {
65+
const onboardingVersion = '7.0.0 (1234)';
66+
5967
const stateWithAccountType = {
6068
...initialState,
6169
accountType: AccountType.MetamaskGoogle,
70+
onboardingVersion,
6271
};
72+
6373
const action = { type: CLEAR_ACCOUNT_TYPE } as const;
6474
const state = onboardingReducer(stateWithAccountType, action);
75+
6576
expect(state.accountType).toBeUndefined();
77+
expect(state.onboardingVersion).toBeUndefined();
6678
});
6779
});

app/reducers/onboarding/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface OnboardingState {
1515
events: [ITrackingEvent][];
1616
completedOnboarding: boolean;
1717
accountType?: AccountType;
18+
onboardingVersion?: string;
1819
}
1920

2021
export const initialOnboardingState: OnboardingState = {
@@ -51,11 +52,13 @@ const onboardingReducer = (
5152
return {
5253
...state,
5354
accountType: action.accountType,
55+
onboardingVersion: action.onboardingVersion,
5456
};
5557
case CLEAR_ACCOUNT_TYPE:
5658
return {
5759
...state,
5860
accountType: undefined,
61+
onboardingVersion: undefined,
5962
};
6063
default:
6164
return state;

0 commit comments

Comments
 (0)