Skip to content

Commit 7df1453

Browse files
committed
chore(runway): cherry-pick feat: add metrics opt In event (#27846)
<!-- 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** * Add Metrics Opt In event in Onboarding, Optinmetrics and MetaMetricsAndDataCollectionSection screen <!-- 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? --> ## **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: METRICS_OPT_IN analytics on user opt-in Scenario: User opts in from onboarding MetaMetrics screen Given the user is on the onboarding MetaMetrics / data collection screen with basic usage enabled by default When the user continues without turning off basic usage Then the app completes onboarding as before and analytics pipelines receive a "Metrics Opt In" event with onboarding location and expected properties in addition to "Analytics Preference Selected" Scenario: User enables MetaMetrics from Settings Given the user is logged in and MetaMetrics is currently off When the user opens Settings > Security & privacy and turns the MetaMetrics switch on Then the app opts in successfully and emits "Metrics Opt In" with settings location and updated_after_onboarding before the preference-selected event Scenario: User enables marketing which requires MetaMetrics Given MetaMetrics is off and marketing data collection is off When the user turns marketing data collection on (which enables MetaMetrics) Then MetaMetrics turns on and "Metrics Opt In" is recorded before the subsequent preference events ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <img width="702" height="94" alt="Screenshot 2026-03-24 at 3 35 19 PM" src="https://github.com/user-attachments/assets/e5177be9-8c93-413b-ae76-8e10c3e50352" /> <img width="703" height="50" alt="Screenshot 2026-03-24 at 3 36 30 PM" src="https://github.com/user-attachments/assets/6da0d4cb-f660-49b9-818e-bb45fe1e413e" /> <img width="695" height="91" alt="Screenshot 2026-03-24 at 3 40 10 PM" src="https://github.com/user-attachments/assets/229e4a2d-80c3-469e-b1a9-639df7068c17" /> <!-- [screenshots/recordings] --> ## **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 analytics-only change that adds an additional tracking call when users enable metrics (onboarding, social login, and settings). Main risk is event ordering/duplication affecting downstream dashboards rather than app behavior. > > **Overview** > Adds a new `MetaMetricsEvents.METRICS_OPT_IN` event and emits it whenever users enable metrics, including the onboarding opt-in screen (`location: onboarding_metametrics`), social login onboarding flow (`location: onboarding_social_login`), and the settings MetaMetrics toggle (`location: settings` / `onboarding_default_settings`). > > Updates tests to assert the new opt-in event is sent (and in settings/onboarding cases is sent *before* `ANALYTICS_PREFERENCE_SELECTED`), including verifying `updated_after_onboarding` and optional `account_type` properties. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9968f73. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent b4f3655 commit 7df1453

7 files changed

Lines changed: 124 additions & 18 deletions

File tree

app/components/UI/OptinMetrics/index.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,16 @@ describe('OptinMetrics', () => {
146146
await waitFor(() => {
147147
expect(mockAnalytics.trackEvent).toHaveBeenNthCalledWith(
148148
1,
149+
expect.objectContaining({
150+
name: MetaMetricsEvents.METRICS_OPT_IN.category,
151+
properties: expect.objectContaining({
152+
location: 'onboarding_metametrics',
153+
updated_after_onboarding: false,
154+
}),
155+
}),
156+
);
157+
expect(mockAnalytics.trackEvent).toHaveBeenNthCalledWith(
158+
2,
149159
expect.objectContaining({
150160
name: 'Analytics Preference Selected',
151161
properties: expect.objectContaining({
@@ -177,6 +187,16 @@ describe('OptinMetrics', () => {
177187
await waitFor(() => {
178188
expect(mockAnalytics.trackEvent).toHaveBeenNthCalledWith(
179189
1,
190+
expect.objectContaining({
191+
name: MetaMetricsEvents.METRICS_OPT_IN.category,
192+
properties: expect.objectContaining({
193+
location: 'onboarding_metametrics',
194+
updated_after_onboarding: false,
195+
}),
196+
}),
197+
);
198+
expect(mockAnalytics.trackEvent).toHaveBeenNthCalledWith(
199+
2,
180200
expect.objectContaining({
181201
name: 'Analytics Preference Selected',
182202
properties: expect.objectContaining({
@@ -212,6 +232,16 @@ describe('OptinMetrics', () => {
212232
);
213233

214234
await waitFor(() => {
235+
expect(mockAnalytics.trackEvent).toHaveBeenCalledWith(
236+
expect.objectContaining({
237+
name: MetaMetricsEvents.METRICS_OPT_IN.category,
238+
properties: expect.objectContaining({
239+
location: 'onboarding_metametrics',
240+
updated_after_onboarding: false,
241+
account_type: AccountType.Imported,
242+
}),
243+
}),
244+
);
215245
expect(mockAnalytics.trackEvent).toHaveBeenCalledWith(
216246
expect.objectContaining({
217247
name: 'Analytics Preference Selected',

app/components/UI/OptinMetrics/index.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -168,19 +168,21 @@ const OptinMetrics = () => {
168168

169169
dispatch(setDataCollectionForMarketing(isMarketingChecked));
170170

171-
// Track opt-out event if user opted out of metrics
172-
if (!isBasicUsageChecked) {
173-
metrics.trackEvent(
174-
metrics
175-
.createEventBuilder(MetaMetricsEvents.METRICS_OPT_OUT)
176-
.addProperties({
177-
updated_after_onboarding: false,
178-
location: 'onboarding_metametrics',
179-
...(accountType && { account_type: accountType }),
180-
})
181-
.build(),
182-
);
183-
}
171+
// Track opt-in / opt-out for metrics
172+
metrics.trackEvent(
173+
metrics
174+
.createEventBuilder(
175+
isBasicUsageChecked
176+
? MetaMetricsEvents.METRICS_OPT_IN
177+
: MetaMetricsEvents.METRICS_OPT_OUT,
178+
)
179+
.addProperties({
180+
updated_after_onboarding: false,
181+
location: 'onboarding_metametrics',
182+
...(accountType && { account_type: accountType }),
183+
})
184+
.build(),
185+
);
184186

185187
metrics.trackEvent(
186188
metrics

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import { captureException } from '@sentry/react-native';
7575
import Logger from '../../../util/Logger';
7676
import { MIGRATION_ERROR_HAPPENED } from '../../../constants/storage';
7777
import { AccountType } from '../../../constants/onboarding';
78+
import { MetaMetricsEvents } from '../../../core/Analytics';
7879

7980
// Mock netinfo - using existing mock
8081
jest.mock('@react-native-community/netinfo');
@@ -1990,6 +1991,13 @@ describe('Onboarding', () => {
19901991

19911992
await waitFor(() => {
19921993
expect(mockAnalytics.optIn).toHaveBeenCalled();
1994+
expect(
1995+
mockCreateEventBuilder.mock.calls.some(
1996+
(call) =>
1997+
(call[0] as { category: string }).category ===
1998+
MetaMetricsEvents.METRICS_OPT_IN.category,
1999+
),
2000+
).toBe(true);
19932001
});
19942002
});
19952003
});

app/components/Views/Onboarding/index.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,14 +733,25 @@ const Onboarding = () => {
733733
discardBufferedTraces();
734734
await setupSentry();
735735

736+
const accountType = getSocialAccountType(provider, !createWallet);
737+
metrics.trackEvent(
738+
metrics
739+
.createEventBuilder(MetaMetricsEvents.METRICS_OPT_IN)
740+
.addProperties({
741+
updated_after_onboarding: false,
742+
location: 'onboarding_social_login',
743+
account_type: accountType,
744+
})
745+
.build(),
746+
);
747+
736748
// use new trace instead of buffered trace for social login
737749
onboardingTraceCtx.current = trace({
738750
name: TraceName.OnboardingJourneyOverall,
739751
op: TraceOperation.OnboardingUserJourney,
740752
tags: getTraceTags(store.getState()),
741753
});
742754

743-
const accountType = getSocialAccountType(provider, !createWallet);
744755
if (createWallet) {
745756
track(MetaMetricsEvents.WALLET_SETUP_STARTED, {
746757
account_type: accountType,

app/components/Views/Settings/SecuritySettings/Sections/MetaMetricsAndDataCollectionSection/MetaMetricsAndDataCollectionSection.test.tsx

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,18 @@ describe('MetaMetricsAndDataCollectionSection', () => {
377377
deviceProp: 'Device value',
378378
userProp: 'User value',
379379
});
380-
expect(mockAnalytics.trackEvent).toHaveBeenCalledWith(
380+
expect(mockAnalytics.trackEvent).toHaveBeenNthCalledWith(
381+
1,
382+
expect.objectContaining({
383+
name: MetaMetricsEvents.METRICS_OPT_IN.category,
384+
properties: expect.objectContaining({
385+
updated_after_onboarding: true,
386+
location: 'settings',
387+
}),
388+
}),
389+
);
390+
expect(mockAnalytics.trackEvent).toHaveBeenNthCalledWith(
391+
2,
381392
expect.objectContaining({
382393
name: MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED.category,
383394
properties: expect.objectContaining({
@@ -407,7 +418,18 @@ describe('MetaMetricsAndDataCollectionSection', () => {
407418
fireEvent(metaMetricsSwitch, 'valueChange', true);
408419

409420
await waitFor(() => {
410-
expect(mockAnalytics.trackEvent).toHaveBeenCalledWith(
421+
expect(mockAnalytics.trackEvent).toHaveBeenNthCalledWith(
422+
1,
423+
expect.objectContaining({
424+
name: MetaMetricsEvents.METRICS_OPT_IN.category,
425+
properties: expect.objectContaining({
426+
updated_after_onboarding: true,
427+
location: 'onboarding_default_settings',
428+
}),
429+
}),
430+
);
431+
expect(mockAnalytics.trackEvent).toHaveBeenNthCalledWith(
432+
2,
411433
expect.objectContaining({
412434
name: MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED.category,
413435
properties: expect.objectContaining({
@@ -467,6 +489,16 @@ describe('MetaMetricsAndDataCollectionSection', () => {
467489
fireEvent(metaMetricsSwitch, 'valueChange', true);
468490

469491
await waitFor(() => {
492+
expect(mockAnalytics.trackEvent).toHaveBeenCalledWith(
493+
expect.objectContaining({
494+
name: MetaMetricsEvents.METRICS_OPT_IN.category,
495+
properties: expect.objectContaining({
496+
updated_after_onboarding: true,
497+
location: 'settings',
498+
account_type: AccountType.MetamaskGoogle,
499+
}),
500+
}),
501+
);
470502
expect(mockAnalytics.trackEvent).toHaveBeenCalledWith(
471503
expect.objectContaining({
472504
name: MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED.category,
@@ -808,6 +840,16 @@ describe('MetaMetricsAndDataCollectionSection', () => {
808840
});
809841
expect(mockAnalytics.trackEvent).toHaveBeenNthCalledWith(
810842
1,
843+
expect.objectContaining({
844+
name: MetaMetricsEvents.METRICS_OPT_IN.category,
845+
properties: expect.objectContaining({
846+
location: 'settings',
847+
updated_after_onboarding: true,
848+
}),
849+
}),
850+
);
851+
expect(mockAnalytics.trackEvent).toHaveBeenNthCalledWith(
852+
2,
811853
expect.objectContaining({
812854
name: MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED.category,
813855
properties: expect.objectContaining({
@@ -827,8 +869,8 @@ describe('MetaMetricsAndDataCollectionSection', () => {
827869
},
828870
);
829871
expect(mockAnalytics.trackEvent).toHaveBeenNthCalledWith(
830-
// if MetaMetrics is initially disabled, trackEvent is called twice and this is 2nd call
831-
!metaMetricsInitiallyEnabled ? 2 : 1,
872+
// if MetaMetrics is initially disabled, marketing consent is the 3rd trackEvent
873+
!metaMetricsInitiallyEnabled ? 3 : 1,
832874
expect.objectContaining({
833875
name: MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED.category,
834876
properties: expect.objectContaining({

app/components/Views/Settings/SecuritySettings/Sections/MetaMetricsAndDataCollectionSection/MetaMetricsAndDataCollectionSection.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,17 @@ const MetaMetricsAndDataCollectionSection: React.FC<
114114
setAnalyticsEnabled(true);
115115

116116
analytics.identify(consolidatedTraits);
117+
analytics.trackEvent(
118+
AnalyticsEventBuilder.createEventBuilder(
119+
MetaMetricsEvents.METRICS_OPT_IN,
120+
)
121+
.addProperties({
122+
updated_after_onboarding: true,
123+
location: analyticsLocation,
124+
...(accountType && { account_type: accountType }),
125+
})
126+
.build(),
127+
);
117128
analytics.trackEvent(
118129
AnalyticsEventBuilder.createEventBuilder(
119130
MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED,

app/core/Analytics/MetaMetrics.events.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ enum EVENT_NAME {
124124

125125
// Analytics
126126
ANALYTICS_PREFERENCE_SELECTED = 'Analytics Preference Selected',
127+
METRICS_OPT_IN = 'Metrics Opt In',
127128
METRICS_OPT_OUT = 'Metrics Opt Out',
128129
ANALYTICS_REQUEST_DATA_DELETION = 'Delete MetaMetrics Data Request Submitted',
129130
EXPERIMENT_VIEWED = 'Experiment Viewed',
@@ -829,6 +830,7 @@ const events = {
829830
ANALYTICS_PREFERENCE_SELECTED: generateOpt(
830831
EVENT_NAME.ANALYTICS_PREFERENCE_SELECTED,
831832
),
833+
METRICS_OPT_IN: generateOpt(EVENT_NAME.METRICS_OPT_IN),
832834
METRICS_OPT_OUT: generateOpt(EVENT_NAME.METRICS_OPT_OUT),
833835
ANALYTICS_REQUEST_DATA_DELETION: generateOpt(
834836
EVENT_NAME.ANALYTICS_REQUEST_DATA_DELETION,

0 commit comments

Comments
 (0)