Skip to content

Commit fad4a12

Browse files
refactor(analytics): migrate Batch 3-17: mobile-platform (#26679)
## **Description** Phase 3 analytics migration (Batch 3-17): migrate Sample Feature's components and hooks from `useMetrics`/`MetricsEventBuilder` to the new `useAnalytics`/`AnalyticsEventBuilder` analytics system. **Reason**: Deprecate MetaMetrics in favour of the shared analytics utility and AnalyticsController. **Changes**: `SamplePetNamesForm`, `SampleCounterPane`, and `SampleFeatureDevSettingsEntryPoint` now use `useAnalytics()` from `app/components/hooks/useAnalytics/useAnalytics` and `createEventBuilder` from the hook instead of `useMetrics()` and `MetricsEventBuilder`; test mocks updated to mock the `useAnalytics` hook with a chainable builder instead of `useMetrics`. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MCWP-302 (Batch 3-17) ## **Manual testing steps** ```gherkin Feature: Sample Feature analytics Scenario: user triggers a Sample Feature flow event Given app is open and user is in a Sample Feature flow When user performs an action that triggers analytics (e.g. increment counter, add pet name, navigate to sample feature) Then the event is tracked on Mixpanel ``` ## **Screenshots/Recordings** N/A – analytics migration, no UI change. ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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] > **Low Risk** > Low risk: this is a refactor of analytics event building/tracking in a non-production sample feature, with corresponding unit test updates. Main risk is mis-tracked or missing events due to the new builder/mocking semantics. > > **Overview** > Updates SampleFeature event tracking to use `useAnalytics()` and its `createEventBuilder` instead of `useMetrics()`/`MetricsEventBuilder` in `SampleCounterPane`, `SamplePetNamesForm`, and the dev-settings entry point. > > Adjusts analytics event definitions (`analytics/events.ts`) and rewrites unit tests to mock/verify the new `useAnalytics` hook and builder chaining, plus updates SampleFeature docs/e2e docs to refer to **Analytics** (not MetaMetrics). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 2dce2e4. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 8a48496 commit fad4a12

8 files changed

Lines changed: 45 additions & 109 deletions

File tree

app/features/SampleFeature/README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ This feature demonstrates:
4141
- Form validation and error handling
4242
- Navigation patterns within the app
4343
- UI component library usage and styling
44-
- MetaMetrics tracking implementation
44+
- Analytics tracking implementation
4545
- Performance tracing patterns and monitoring
4646
- Comprehensive unit testing
4747
- End-to-end testing
@@ -66,7 +66,7 @@ The SampleFeature's structure mirrors the main `/app` directory. This helps devs
6666

6767
```
6868
app/features/SampleFeature/
69-
├── analytics/ # MetaMetrics event definitions
69+
├── analytics/ # Analytics event definitions
7070
│ └── events.ts
7171
├── components/ # React components
7272
│ ├── hooks/ # Custom React hooks
@@ -109,7 +109,7 @@ A simple counter implementation demonstrating:
109109
- Redux Toolkit state management
110110
- Custom hooks (`useSampleCounter`)
111111
- Action dispatching
112-
- MetaMetrics event tracking
112+
- Analytics event tracking
113113

114114
**Key Files:**
115115

@@ -208,14 +208,13 @@ The feature extensively uses the MetaMask component library:
208208
- Follows MetaMask design system
209209
- Responsive layout with KeyboardAwareScrollView
210210

211-
## MetaMetrics Tracking
211+
## Analytics Tracking
212212

213213
Events are defined in `analytics/events.ts` following the event builder pattern:
214214

215215
- Centralized event definitions in a dedicated analytics module
216216
- Event builder helper functions for consistent event creation
217-
- Type-safe event generation using the `generateOpt` utility
218-
- Integration with the global MetaMetrics system
217+
- Integration with the `useAnalytics` hook and `AnalyticsEventBuilder`
219218
- Separation of event definition from event dispatching
220219

221220
### Privacy Considerations
@@ -424,7 +423,7 @@ The SampleFeature demonstrates comprehensive testing patterns and best practices
424423
- Module mocking for native dependencies
425424
- Redux store mocking with `configureStore`
426425
- Navigation mocking for testing navigation flows
427-
- MetaMetrics tracking mock verification
426+
- Analytics tracking mock verification
428427
- Controller mocking for isolated component tests
429428

430429
**Best Practices Demonstrated:**

app/features/SampleFeature/analytics/events.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
22
generateOpt,
3-
EVENT_NAME as METRICS_EVENT_NAME,
3+
EVENT_NAME as ANALYTICS_EVENT_NAME,
44
} from '../../../core/Analytics/MetaMetrics.events';
55

66
// Feature-specific event names (match EVENT_NAME style: SCREAMING_SNAKE_CASE keys, Initial Case string values with spaces)
@@ -13,7 +13,7 @@ export enum EVENT_NAME {
1313

1414
// Helper to create events (type-casting to global event name type if needed)
1515
const createEvent = (name: EVENT_NAME) =>
16-
generateOpt(name as unknown as METRICS_EVENT_NAME);
16+
generateOpt(name as unknown as ANALYTICS_EVENT_NAME);
1717

1818
// Export your events for use in the feature
1919
export const SAMPLE_FEATURE_EVENTS = {

app/features/SampleFeature/components/views/SampleCounterPane/SampleCounterPane.test.tsx

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,15 @@ import { fireEvent, waitFor } from '@testing-library/react-native';
33
import renderWithProvider from '../../../../../util/test/renderWithProvider';
44
import { SampleCounterPane } from './SampleCounterPane';
55
import { strings } from '../../../../../../locales/i18n';
6-
import useMetrics from '../../../../../components/hooks/useMetrics/useMetrics';
76
import { SAMPLE_FEATURE_EVENTS } from '../../../analytics/events';
8-
import { MetricsEventBuilder } from '../../../../../core/Analytics/MetricsEventBuilder.ts';
7+
import { useAnalytics } from '../../../../../components/hooks/useAnalytics/useAnalytics';
98

10-
/**
11-
* Mock implementation for react-native Linking module
12-
* Required for testing components that use deep linking functionality
13-
*/
149
jest.mock('react-native/Libraries/Linking/Linking', () => ({
1510
addEventListener: jest.fn(() => ({ remove: jest.fn() })),
1611
}));
1712

18-
/**
19-
* Mock implementation of the increment function
20-
* Used to verify counter increment functionality
21-
*/
2213
const mockIncrement = jest.fn();
2314

24-
/**
25-
* Mock implementation of the useSampleCounter hook
26-
* Provides a controlled test environment for the counter functionality
27-
*/
2815
jest.mock('../../hooks/useSampleCounter/useSampleCounter', () => ({
2916
__esModule: true,
3017
useSampleCounter: () => ({
@@ -33,32 +20,11 @@ jest.mock('../../hooks/useSampleCounter/useSampleCounter', () => ({
3320
}),
3421
}));
3522

36-
jest.mock('../../../../../components/hooks/useMetrics/useMetrics');
23+
const mockAnalytics = jest.mocked(useAnalytics)();
3724

38-
const mockTrackEvent = jest.fn();
39-
40-
/**
41-
* Test suite for SampleCounterPane component
42-
*
43-
* @group Components
44-
* @group SampleCounterPane
45-
*/
4625
describe('SampleCounterPane', () => {
4726
beforeEach(() => {
4827
jest.clearAllMocks();
49-
(useMetrics as jest.MockedFn<typeof useMetrics>).mockReturnValue({
50-
trackEvent: mockTrackEvent,
51-
createEventBuilder: MetricsEventBuilder.createEventBuilder,
52-
enable: jest.fn(),
53-
addTraitsToUser: jest.fn(),
54-
createDataDeletionTask: jest.fn(),
55-
checkDataDeleteStatus: jest.fn(),
56-
getDeleteRegulationCreationDate: jest.fn(),
57-
getDeleteRegulationId: jest.fn(),
58-
isDataRecorded: jest.fn(),
59-
isEnabled: jest.fn(),
60-
getMetaMetricsId: jest.fn(),
61-
});
6228
});
6329

6430
/**
@@ -107,10 +73,9 @@ describe('SampleCounterPane', () => {
10773
fireEvent.press(getByTestId('sample-counter-pane-increment-button'));
10874

10975
await waitFor(() => {
110-
expect(mockTrackEvent).toHaveBeenCalledWith(
111-
expect.objectContaining({
112-
name: SAMPLE_FEATURE_EVENTS.COUNTER_INCREMENTED.category,
113-
}),
76+
expect(mockAnalytics.trackEvent).toHaveBeenCalled();
77+
expect(mockAnalytics.createEventBuilder).toHaveBeenCalledWith(
78+
SAMPLE_FEATURE_EVENTS.COUNTER_INCREMENTED,
11479
);
11580
});
11681
});

app/features/SampleFeature/components/views/SampleCounterPane/SampleCounterPane.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import { useSampleCounter } from '../../hooks/useSampleCounter/useSampleCounter'
1010
import { strings } from '../../../../../../locales/i18n';
1111
import Card from '../../../../../component-library/components/Cards/Card';
1212
import { useStyles } from '../../../../../component-library/hooks';
13-
import useMetrics from '../../../../../components/hooks/useMetrics/useMetrics';
14-
import { MetricsEventBuilder } from '../../../../../core/Analytics/MetricsEventBuilder';
13+
import { useAnalytics } from '../../../../../components/hooks/useAnalytics/useAnalytics';
1514
import { SAMPLE_FEATURE_EVENTS } from '../../../analytics/events';
1615

1716
/**
@@ -43,13 +42,11 @@ import { SAMPLE_FEATURE_EVENTS } from '../../../analytics/events';
4342
export function SampleCounterPane() {
4443
const { styles } = useStyles(styleSheet, {});
4544
const counter = useSampleCounter();
46-
const { trackEvent } = useMetrics();
45+
const { trackEvent, createEventBuilder } = useAnalytics();
4746

4847
const incrementCount = () => {
4948
trackEvent(
50-
MetricsEventBuilder.createEventBuilder(
51-
SAMPLE_FEATURE_EVENTS.COUNTER_INCREMENTED,
52-
).build(),
49+
createEventBuilder(SAMPLE_FEATURE_EVENTS.COUNTER_INCREMENTED).build(),
5350
);
5451
counter.incrementCount();
5552
};

app/features/SampleFeature/components/views/SampleFeatureDevSettingsEntryPoint/SampleFeatureDevSettingsEntryPoint.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import Button, {
1414
import { MetaMetricsEvents } from '../../../../../core/Analytics';
1515
import { useNavigation } from '@react-navigation/native';
1616
import styleSheet from './SampleFeatureDevSettingsEntryPoint.styles';
17-
import { useMetrics } from '../../../../../components/hooks/useMetrics';
17+
import { useAnalytics } from '../../../../../components/hooks/useAnalytics/useAnalytics';
1818

1919
function NavigateToSampleFeature() {
2020
const theme = useTheme();
2121
const { styles } = useStyles(styleSheet, { theme });
22-
const { trackEvent, createEventBuilder } = useMetrics();
22+
const { trackEvent, createEventBuilder } = useAnalytics();
2323
const navigation = useNavigation();
2424

2525
const onPressNavigate = () => {

app/features/SampleFeature/components/views/SamplePetNames/SamplePetNamesForm.test.tsx

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ jest.mock('react-native/Libraries/Linking/Linking', () => ({
1010

1111
import { SamplePetNamesForm } from './SamplePetNamesForm';
1212
import Engine from '../../../../../core/Engine';
13-
import useMetrics from '../../../../../components/hooks/useMetrics/useMetrics';
1413
import { SAMPLE_FEATURE_EVENTS } from '../../../analytics/events';
15-
import { MetricsEventBuilder } from '../../../../../core/Analytics/MetricsEventBuilder.ts';
14+
import { useAnalytics } from '../../../../../components/hooks/useAnalytics/useAnalytics';
1615

1716
jest.mock('../../../../../core/Engine', () => ({
1817
context: {
@@ -22,44 +21,25 @@ jest.mock('../../../../../core/Engine', () => ({
2221
},
2322
}));
2423

25-
// Mock the useSamplePetNames hook
2624
jest.mock('../../hooks/useSamplePetNames', () => ({
2725
useSamplePetNames: jest.fn(),
2826
}));
2927

30-
jest.mock('../../../../../components/hooks/useMetrics/useMetrics');
31-
3228
import { useSamplePetNames } from '../../hooks/useSamplePetNames';
3329

30+
const mockAnalytics = jest.mocked(useAnalytics)();
31+
3432
const mockUseSamplePetNames = useSamplePetNames as jest.MockedFunction<
3533
typeof useSamplePetNames
3634
>;
3735

38-
const mockTrackEvent = jest.fn();
39-
4036
describe('SamplePetNamesForm', () => {
4137
let alertSpy: jest.SpyInstance;
4238

4339
beforeEach(() => {
4440
jest.clearAllMocks();
45-
// Default mock return value - no existing pet names
4641
mockUseSamplePetNames.mockReturnValue({ petNames: [] });
47-
// Create spy on Alert.alert
4842
alertSpy = jest.spyOn(Alert, 'alert').mockImplementation(jest.fn());
49-
50-
(useMetrics as jest.MockedFn<typeof useMetrics>).mockReturnValue({
51-
trackEvent: mockTrackEvent,
52-
createEventBuilder: MetricsEventBuilder.createEventBuilder,
53-
enable: jest.fn(),
54-
addTraitsToUser: jest.fn(),
55-
createDataDeletionTask: jest.fn(),
56-
checkDataDeleteStatus: jest.fn(),
57-
getDeleteRegulationCreationDate: jest.fn(),
58-
getDeleteRegulationId: jest.fn(),
59-
isDataRecorded: jest.fn(),
60-
isEnabled: jest.fn(),
61-
getMetaMetricsId: jest.fn(),
62-
});
6343
});
6444

6545
afterEach(() => {
@@ -130,15 +110,16 @@ describe('SamplePetNamesForm', () => {
130110

131111
// Assert
132112
await waitFor(() => {
133-
expect(mockTrackEvent).toHaveBeenCalledWith(
134-
expect.objectContaining({
135-
name: SAMPLE_FEATURE_EVENTS.PETNAME_ADDED.category,
136-
properties: {
137-
totalPetNames: 0,
138-
chainId: '0x1',
139-
},
140-
}),
113+
expect(mockAnalytics.trackEvent).toHaveBeenCalled();
114+
expect(mockAnalytics.createEventBuilder).toHaveBeenCalledWith(
115+
SAMPLE_FEATURE_EVENTS.PETNAME_ADDED,
141116
);
117+
const builder = jest.mocked(mockAnalytics.createEventBuilder).mock
118+
.results[0].value;
119+
expect(builder.addProperties).toHaveBeenCalledWith({
120+
totalPetNames: 0,
121+
chainId: '0x1',
122+
});
142123
});
143124
});
144125

@@ -246,15 +227,16 @@ describe('SamplePetNamesForm', () => {
246227

247228
// Assert
248229
await waitFor(() => {
249-
expect(mockTrackEvent).toHaveBeenCalledWith(
250-
expect.objectContaining({
251-
name: SAMPLE_FEATURE_EVENTS.PETNAME_UPDATED.category,
252-
properties: {
253-
totalPetNames: 1,
254-
chainId: '0x1',
255-
},
256-
}),
230+
expect(mockAnalytics.trackEvent).toHaveBeenCalled();
231+
expect(mockAnalytics.createEventBuilder).toHaveBeenCalledWith(
232+
SAMPLE_FEATURE_EVENTS.PETNAME_UPDATED,
257233
);
234+
const builder = jest.mocked(mockAnalytics.createEventBuilder).mock
235+
.results[0].value;
236+
expect(builder.addProperties).toHaveBeenCalledWith({
237+
totalPetNames: 1,
238+
chainId: '0x1',
239+
});
258240
});
259241
});
260242
});

app/features/SampleFeature/components/views/SamplePetNames/SamplePetNamesForm.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import styleSheet from './SamplePetNamesForm.styles';
1010
import { strings } from '../../../../../../locales/i18n';
1111
import { SamplePetNamesFormContentProps } from './SamplePetNamesForm.types';
1212
import { useSamplePetNamesForm } from '../../hooks/useSamplePetNamesForm';
13-
import useMetrics from '../../../../../components/hooks/useMetrics/useMetrics';
14-
import { MetricsEventBuilder } from '../../../../../core/Analytics/MetricsEventBuilder';
13+
import { useAnalytics } from '../../../../../components/hooks/useAnalytics/useAnalytics';
1514
import { SAMPLE_FEATURE_EVENTS } from '../../../analytics/events';
1615
import trackErrorAsAnalytics from '../../../../../util/metrics/TrackError/trackErrorAsAnalytics';
1716
import { useSamplePetNames } from '../../hooks/useSamplePetNames';
@@ -57,7 +56,7 @@ export function SamplePetNamesForm({
5756

5857
const { onSubmit, isValid, name, setName, setAddress, address } =
5958
useSamplePetNamesForm(chainId, initialAddress, initialName);
60-
const { trackEvent } = useMetrics();
59+
const { trackEvent, createEventBuilder } = useAnalytics();
6160
const { petNames } = useSamplePetNames(chainId);
6261

6362
const submit = () => {
@@ -71,9 +70,7 @@ export function SamplePetNamesForm({
7170
if (isEditMode) {
7271
// User pressed existing pet name - direct update
7372
trackEvent(
74-
MetricsEventBuilder.createEventBuilder(
75-
SAMPLE_FEATURE_EVENTS.PETNAME_UPDATED,
76-
)
73+
createEventBuilder(SAMPLE_FEATURE_EVENTS.PETNAME_UPDATED)
7774
.addProperties({
7875
totalPetNames: petNames.length,
7976
chainId: chainId as string,
@@ -101,9 +98,7 @@ export function SamplePetNamesForm({
10198
text: strings('sample_feature.pet_name.update'),
10299
onPress: () => {
103100
trackEvent(
104-
MetricsEventBuilder.createEventBuilder(
105-
SAMPLE_FEATURE_EVENTS.PETNAME_UPDATED,
106-
)
101+
createEventBuilder(SAMPLE_FEATURE_EVENTS.PETNAME_UPDATED)
107102
.addProperties({
108103
totalPetNames: petNames.length,
109104
chainId: chainId as string,
@@ -119,9 +114,7 @@ export function SamplePetNamesForm({
119114
} else {
120115
// No duplicate - add new pet name
121116
trackEvent(
122-
MetricsEventBuilder.createEventBuilder(
123-
SAMPLE_FEATURE_EVENTS.PETNAME_ADDED,
124-
)
117+
createEventBuilder(SAMPLE_FEATURE_EVENTS.PETNAME_ADDED)
125118
.addProperties({
126119
totalPetNames: petNames.length,
127120
chainId: chainId as string,

app/features/SampleFeature/e2e/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The Sample Feature is a development/testing feature that demonstrates:
1111
- Form validation and error handling
1212
- Navigation patterns within the app
1313
- UI component library usage and styling
14-
- Metametrics tracking
14+
- Analytics tracking
1515
- Unit testing
1616
- End-to-end testing
1717

0 commit comments

Comments
 (0)