Skip to content

Commit

Permalink
fix: "data collection for marketing" from PR #9687 (#9905)
Browse files Browse the repository at this point in the history
## **Description**

### Onboarding phase

- prevent metametrics API to be called multiple times for identical
actions:
  - group traits addition
  - refactor events into a consolidated one
- remove useless trackEvent in places where it would never be triggered
anyway (when metrics are disabled)
- add condition to prevent useless data processing when metrics are
disabled
- move async work in `InteractionManager.runAfterInteractions` as hook
doesn't do it by default on traits addition. Move the event too even if
handled already to ensure consistency with traits addition.
- number of events reduced from 11 to 7 when marketing is opted-in.
- making checkbox text touchable to check the box
- add unit tests and update snapshot

### Settings screen

- extract a section component to make the section code testable as
otherwise you have to mock everything and it conflicts with code under
test
but keep MetaMetrics toggle and marketing toggle together as they have
been designed to work together closely.
- add traits when toggling marketing on/off
- rework MetaMetrics and Marketing switches interaction logic
- unit test on all the section and switches interactions

> [!NOTE]  
> This is a rework of #9687.
> I had to extract the settings section as a component for testing to be
possible.
> I could have split it further into MetaMEtrics seciton and Marketing
data section.
> but this would have required more work to make sure both components
keep their linked behaviours when toggling switches.
> however, this is a possible improvement. I consider we should work on
this on a full settings sections refactoring, see issue #9979


## **Related issues**

Fixes: #9862 

## **Manual testing steps**

- Same as for #9687
- No UI change.
- all changes on event tracking and identify are unit tested.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

#### Onboaring phase

When not checking marketing checkbox
```
 INFO  TRACK event saved {"event": "Terms of Use Shown", "properties": {}, "type": "track"}
 INFO  IDENTIFY event saved {"traits": {"Batch account balance requests": "ON", "Enable OpenSea API": "ON", "NFT Autodetection": "OFF", "Theme": "light", "applicationVersion": "7.23.0", "currentBuildNumber": "1333", "deviceBrand": "Apple", "operatingSystemVersion": "17.5", "platform": "ios", "security_providers": "blockaid", "token_detection_enable": "ON"}, "type": "identify", "userId": "ff0d83a8-9c1e-4897-b1b1-1b5e5e23ed42"}
 INFO  TRACK event saved {"event": "Wallet Setup Started", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"analytics_option_selected": "Metrics Opt In", "updated_after_onboarding": false}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"analytics_option_selected": "Metrics Opt In", "updated_after_onboarding": false}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Welcome Message Viewed", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Onboarding Started", "properties": {}, "type": "track"}
 INFO  Sent 1 events
 INFO  Sent 7 events
```
and Mixpanel view
<img width="300" alt="image"
src="https://github.com/MetaMask/metamask-mobile/assets/4677568/62edda42-bbe4-45e1-8a69-239ed6f00596">


When checking marketing checkbox
```
 INFO  TRACK event saved {"event": "Terms of Use Shown", "properties": {}, "type": "track"}
 INFO  IDENTIFY event saved {"traits": {"Batch account balance requests": "ON", "Enable OpenSea API": "ON", "NFT Autodetection": "OFF", "Theme": "light", "applicationVersion": "7.23.0", "currentBuildNumber": "1333", "deviceBrand": "Apple", "operatingSystemVersion": "17.5", "platform": "ios", "security_providers": "blockaid", "token_detection_enable": "ON"}, "type": "identify", "userId": "d1b09ff0-b550-4d7f-a894-1cdc13609473"}
 INFO  TRACK event saved {"event": "Wallet Setup Started", "properties": {}, "type": "track"}
 INFO  IDENTIFY event saved {"traits": {"has_marketing_consent": true, "is_metrics_opted_in": true}, "type": "identify", "userId": "d1b09ff0-b550-4d7f-a894-1cdc13609473"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"has_marketing_consent": true, "is_metrics_opted_in": true, "location": "onboarding_metametrics"}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"has_marketing_consent": true, "is_metrics_opted_in": true, "location": "onboarding_metametrics"}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"analytics_option_selected": "Metrics Opt In", "updated_after_onboarding": false}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"analytics_option_selected": "Metrics Opt In", "updated_after_onboarding": false}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Welcome Message Viewed", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Onboarding Started", "properties": {}, "type": "track"}
 INFO  Sent 1 events
 INFO  Sent 11 events
```
and Mixpanel view:
<img width="300" alt="image"
src="https://github.com/MetaMask/metamask-mobile/assets/4677568/82af1510-73db-40a2-b7e1-4c047c9f1fa3">

#### Settings screen

Missing marketing setting events and user traits:

```
 INFO  IDENTIFY event saved {"traits": {"Batch account balance requests": "ON", "Enable OpenSea API": "ON", "NFT Autodetection": "OFF", "Theme": "light", "applicationVersion": "7.23.0", "currentBuildNumber": "1333", "deviceBrand": "Apple", "operatingSystemVersion": "17.5", "platform": "ios", "security_providers": "blockaid", "token_detection_enable": "ON"}, "type": "identify", "userId": "5e9545ed-84b3-4223-801e-d70fee12e10d"}
 INFO  TRACK event saved {"event": "Views Security & Privacy", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"analytics_option_selected": "Metrics Opt in", "updated_after_onboarding": true}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"analytics_option_selected": "Metrics Opt in", "updated_after_onboarding": true}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Views Security & Privacy", "properties": {}, "type": "track"}
 INFO  Sent 5 events
 INFO  Sent 1 events
```


### **After**

#### Onboaring phase

When not checking marketing checkbox
```
 INFO  TRACK event saved {"event": "Terms of Use Shown", "properties": {}, "type": "track"}
 INFO  IDENTIFY event saved {"traits": {"Batch account balance requests": "ON", "Enable OpenSea API": "ON", "NFT Autodetection": "OFF", "Theme": "light", "applicationVersion": "7.23.0", "currentBuildNumber": "1333", "deviceBrand": "Apple", "is_metrics_opted_in": true, "operatingSystemVersion": "17.5", "platform": "ios", "security_providers": "blockaid", "token_detection_enable": "ON"}, "type": "identify", "userId": "19f4001c-68a6-4b62-9f1d-d8f6ff92718e"}
 INFO  TRACK event saved {"event": "Wallet Setup Started", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"is_metrics_opted_in": true, "location": "onboarding_metametrics", "updated_after_onboarding": false}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"is_metrics_opted_in": true, "location": "onboarding_metametrics", "updated_after_onboarding": false}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Welcome Message Viewed", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Onboarding Started", "properties": {}, "type": "track"}
 INFO  Sent 1 events
 INFO  Sent 7 events
```
and Mixpanel view:
<img width="300" alt="image"
src="https://github.com/MetaMask/metamask-mobile/assets/4677568/d619d77f-e486-4e3c-b151-9151248683ae">


When checking marketing checkbox
```
 INFO  TRACK event saved {"event": "Terms of Use Shown", "properties": {}, "type": "track"}
 INFO  IDENTIFY event saved {"traits": {"Batch account balance requests": "ON", "Enable OpenSea API": "ON", "NFT Autodetection": "OFF", "Theme": "light", "applicationVersion": "7.23.0", "currentBuildNumber": "1333", "deviceBrand": "Apple", "has_marketing_consent": true, "is_metrics_opted_in": true, "operatingSystemVersion": "17.5", "platform": "ios", "security_providers": "blockaid", "token_detection_enable": "ON"}, "type": "identify", "userId": "fb576f56-3e5e-4df8-9a40-55d16ae5b16e"}
 INFO  TRACK event saved {"event": "Wallet Setup Started", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"has_marketing_consent": true, "is_metrics_opted_in": true, "location": "onboarding_metametrics", "updated_after_onboarding": false}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"has_marketing_consent": true, "is_metrics_opted_in": true, "location": "onboarding_metametrics", "updated_after_onboarding": false}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Welcome Message Viewed", "properties": {}, "type": "track"}
 INFO  TRACK event saved {"event": "Onboarding Started", "properties": {}, "type": "track"}
 INFO  Sent 1 events
 INFO  Sent 7 events
```
and mixpanel view:
<img width="300" alt="image"
src="https://github.com/MetaMask/metamask-mobile/assets/4677568/e8f21057-afd0-489f-94e3-95290f84c8a6">

#### Settings screen

```
 INFO  IDENTIFY event saved {"traits": {"Batch account balance requests": "ON", "Enable OpenSea API": "ON", "NFT Autodetection": "OFF", "Theme": "light", "applicationVersion": "7.23.0", "currentBuildNumber": "1333", "deviceBrand": "Apple", "is_metrics_opted_in": true, "operatingSystemVersion": "17.5", "platform": "ios", "security_providers": "blockaid", "token_detection_enable": "ON"}, "type": "identify", "userId": "5e9545ed-84b3-4223-801e-d70fee12e10d"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"is_metrics_opted_in": true, "updated_after_onboarding": true}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"is_metrics_opted_in": true, "updated_after_onboarding": true}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {}, "type": "track"}
 INFO  Sent 4 events
 INFO  IDENTIFY event saved {"traits": {"has_marketing_consent": true}, "type": "identify", "userId": "5e9545ed-84b3-4223-801e-d70fee12e10d"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"has_marketing_consent": true, "location": "settings"}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {"has_marketing_consent": true, "location": "settings"}, "type": "track"}
 INFO  TRACK event saved {"event": "Analytics Preference Selected", "properties": {}, "type": "track"}
 INFO  Sent 4 events
```
and mixpanel view:
<img width="300" alt="image"
src="https://github.com/MetaMask/metamask-mobile/assets/4677568/6e9ccdc2-96c3-459b-a6e1-61b06a21608b">

<img width="300" alt="image"
src="https://github.com/MetaMask/metamask-mobile/assets/4677568/972eb915-e3d9-4a86-879a-a7d28df9286c">


## **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.
  • Loading branch information
NicolasMassart authored Jun 13, 2024
1 parent d6ab1f2 commit 74b172b
Show file tree
Hide file tree
Showing 8 changed files with 1,279 additions and 347 deletions.
226 changes: 51 additions & 175 deletions app/components/UI/OptinMetrics/__snapshots__/index.test.tsx.snap

Large diffs are not rendered by default.

43 changes: 24 additions & 19 deletions app/components/UI/OptinMetrics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
BackHandler,
Alert,
InteractionManager,
TouchableOpacity,
} from 'react-native';
import PropTypes from 'prop-types';
import { baseStyles, fontStyles } from '../../../styles/common';
Expand Down Expand Up @@ -317,8 +318,17 @@ class OptinMetrics extends PureComponent {
await metrics.enable();
InteractionManager.runAfterInteractions(async () => {
// add traits to user for identification

// trait indicating if user opts in for data collection for marketing
let dataCollectionForMarketingTraits;
if (this.props.isDataCollectionForMarketingEnabled) {
dataCollectionForMarketingTraits = { has_marketing_consent: true };
}

// consolidate device and user settings traits
const consolidatedTraits = {
...dataCollectionForMarketingTraits,
is_metrics_opted_in: true,
...generateDeviceAnalyticsMetaData(),
...generateUserSettingsAnalyticsMetaData(),
};
Expand All @@ -345,24 +355,11 @@ class OptinMetrics extends PureComponent {

this.props.clearOnboardingEvents();

if (this.props.isDataCollectionForMarketingEnabled) {
const traits = {
is_metrics_opted_in: true,
has_marketing_consent: Boolean(
this.props.setDataCollectionForMarketing,
),
};

metrics.addTraitsToUser(traits);
metrics.trackEvent(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, {
...traits,
location: 'onboarding_metametrics',
});
}

// track event for user opting in
// track event for user opting in on metrics and data collection for marketing
metrics.trackEvent(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, {
analytics_option_selected: 'Metrics Opt In',
...dataCollectionForMarketingTraits,
is_metrics_opted_in: true,
location: 'onboarding_metametrics',
updated_after_onboarding: false,
});
});
Expand Down Expand Up @@ -598,7 +595,15 @@ class OptinMetrics extends PureComponent {
: this.renderLegacyAction(action, i),
)}
{isPastPrivacyPolicyDate ? (
<View style={styles.checkbox}>
<TouchableOpacity
style={styles.checkbox}
onPress={() =>
setDataCollectionForMarketing(
!isDataCollectionForMarketingEnabled,
)
}
activeOpacity={1}
>
<Checkbox
isChecked={isDataCollectionForMarketingEnabled}
accessibilityRole={'checkbox'}
Expand All @@ -612,7 +617,7 @@ class OptinMetrics extends PureComponent {
<Text style={styles.content}>
{strings('privacy_policy.checkbox')}
</Text>
</View>
</TouchableOpacity>
) : null}
{this.renderPrivacyPolicy()}
</View>
Expand Down
111 changes: 110 additions & 1 deletion app/components/UI/OptinMetrics/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,122 @@
import OptinMetrics from './';
import { renderScreen } from '../../../util/test/renderWithProvider';
import { MetaMetrics, MetaMetricsEvents } from '../../../core/Analytics';
import { fireEvent, screen, waitFor } from '@testing-library/react-native';
import { strings } from '../../../../locales/i18n';

const { InteractionManager } = jest.requireActual('react-native');

InteractionManager.runAfterInteractions = jest.fn(async (callback) =>
callback(),
);

jest.mock('../../../core/Analytics/MetaMetrics');

const mockMetrics = {
trackEvent: jest.fn().mockImplementation(() => Promise.resolve()),
trackAnonymousEvent: jest.fn(),
enable: jest.fn(() => Promise.resolve()),
addTraitsToUser: jest.fn(() => Promise.resolve()),
isEnabled: jest.fn(() => true),
};

(MetaMetrics.getInstance as jest.Mock).mockReturnValue(mockMetrics);

jest.mock(
'../../../util/metrics/UserSettingsAnalyticsMetaData/generateUserProfileAnalyticsMetaData',
() => jest.fn().mockReturnValue({ userProp: 'User value' }),
);

jest.mock(
'../../../util/metrics/DeviceAnalyticsMetaData/generateDeviceAnalyticsMetaData',
() => jest.fn().mockReturnValue({ deviceProp: 'Device value' }),
);

jest.mock('../../../reducers/legalNotices', () => ({
isPastPrivacyPolicyDate: jest.fn().mockReturnValue(true),
}));

describe('OptinMetrics', () => {
it('should render correctly', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('render matches snapshot', () => {
const { toJSON } = renderScreen(
OptinMetrics,
{ name: 'OptinMetrics' },
{ state: {} },
);
expect(toJSON()).toMatchSnapshot();
});

describe('sets traits and sends metric event on confirm', () => {
it('without marketing consent', async () => {
renderScreen(OptinMetrics, { name: 'OptinMetrics' }, { state: {} });
fireEvent.press(
screen.getByRole('button', {
name: strings('privacy_policy.cta_i_agree'),
}),
);
await waitFor(() => {
expect(mockMetrics.trackEvent).toHaveBeenNthCalledWith(
1,
MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED,
{
is_metrics_opted_in: true,
location: 'onboarding_metametrics',
updated_after_onboarding: false,
},
true,
);
expect(mockMetrics.addTraitsToUser).toHaveBeenNthCalledWith(1, {
deviceProp: 'Device value',
userProp: 'User value',
is_metrics_opted_in: true,
});
});
});

it('with marketing consent', async () => {
renderScreen(OptinMetrics, { name: 'OptinMetrics' }, { state: {} });
fireEvent.press(screen.getByText(strings('privacy_policy.checkbox')));
fireEvent.press(
screen.getByRole('button', {
name: strings('privacy_policy.cta_i_agree'),
}),
);
await waitFor(() => {
expect(mockMetrics.trackEvent).toHaveBeenNthCalledWith(
1,
MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED,
{
has_marketing_consent: true,
is_metrics_opted_in: true,
location: 'onboarding_metametrics',
updated_after_onboarding: false,
},
true,
);
expect(mockMetrics.addTraitsToUser).toHaveBeenNthCalledWith(1, {
deviceProp: 'Device value',
userProp: 'User value',
is_metrics_opted_in: true,
has_marketing_consent: true,
});
});
});
});

it('does not call metrics on cancel', async () => {
renderScreen(OptinMetrics, { name: 'OptinMetrics' }, { state: {} });
fireEvent.press(
screen.getByRole('button', {
name: strings('privacy_policy.cta_no_thanks'),
}),
);
await waitFor(() => {
expect(mockMetrics.trackEvent).not.toHaveBeenCalled();
expect(mockMetrics.addTraitsToUser).not.toHaveBeenCalled();
});
});
});
Loading

0 comments on commit 74b172b

Please sign in to comment.