-
Notifications
You must be signed in to change notification settings - Fork 5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Ensure has_marketing_consent user trait are correctly set and se…
…nt after participateInMetaMetrics is set to true (#29871) <!-- 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** It was observed in production metrics that the `has_marketing_consent` user trait was not set (either `null` or `undefined`) for 40-50% of users. Meanwhile, another ~40% of users had it set to false, and the remainder had it set to true. The problem was in the metametrics controller. The controller sets `state.previousUserTraits` in the `_buildUserTraitsObject` function, which gets called when there are state updates. That function also uses `state.previousUserTraits` to determine which values need to be returned, by comparing it to the new `currentTraits`. The problem is that if a trait is set while `participateInMetaMetrics` is false, then the trait will not be sent in a request to Segment, BUT the trait will be saved in `state.previousUserTraits`. Later, if `participateInMetaMetrics` is set to true, `_buildUserTraitsObject` will not return a trait currently matches `state.previousUserTraits` The `has_marketing_consent` trait is set to true when the user checks the associated checkbox on the metametrics screen of the onboarding flow, but then if the user clicks confirm/yes on that screen to opt-in to metametrics, the aforementioned problem is hit. To solve this, we just ensure that `state.previousUserTraits` is not saved if `participateInMetaMetrics` is false. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29871?quickstart=1) ## **Related issues** Fixes: MetaMask/MetaMask-planning#3932 ## **Manual testing steps** 1. Install a build from this branch 2. Open the background console and the network tab 3. Start onboarding. On the metametrics screen, click the checkbox and then "I agree" 4. The network tab should now include a request to segment with user traits, where `has_marketing_consent` is set to true -- 1. Follow the above steps on the v12.9.3 build (step 4 will fail) 2. Update the version of that install to the build from this branch 3. Log in. 4. The network tab should now include a request to segment with user traits, where `has_marketing_consent` is set to true -- 1. Install a build from this branch 2. Open the background console and the network tab 3. Start onboarding. On the metametrics screen, DO NOT click the checkbox and then click "No thanks" 4. The network tab should not include a request to segment 5. Complete onboarding and go to settings and toggle "Participate in MetaMetrics" to true 6. The network tab tab should now include a request to segment with user traits, where `has_marketing_consent` is set to false. If the marketing consent toggle is turned on, there should then be a network request where the user trait is true > [!IMPORTANT] > This requires manual test from QA. - Create a new user and click both "I agree" and "we'll use this data... " when user is on "help us improve Metamask" page - There should be 1 request to segment with user traits, where both has_marketing_consent and participate_in_metametrics set to true. - Proceed on onboarding - Head to privacy page in settings - Opt out "participate in metametrics" - There's no more request to segment after ## **Screenshots/Recordings** https://github.com/user-attachments/assets/0d335973-eb52-4f61-9709-2999efde4021 ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/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-extension/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. --------- Co-authored-by: dddddanica <[email protected]>
- Loading branch information
1 parent
ba5f5b1
commit c4aa6a9
Showing
14 changed files
with
240 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import { strict as assert } from 'assert'; | ||
import { Mockttp } from 'mockttp'; | ||
import { getEventPayloads, withFixtures } from '../../helpers'; | ||
import FixtureBuilder from '../../fixture-builder'; | ||
import { | ||
completeCreateNewWalletOnboardingFlow, | ||
createNewWalletOnboardingFlow, | ||
} from '../../page-objects/flows/onboarding.flow'; | ||
import { MOCK_META_METRICS_ID } from '../../constants'; | ||
import HeaderNavbar from '../../page-objects/pages/header-navbar'; | ||
import SettingsPage from '../../page-objects/pages/settings/settings-page'; | ||
import PrivacySettings from '../../page-objects/pages/settings/privacy-settings'; | ||
|
||
async function mockSegment(mockServer: Mockttp) { | ||
return [ | ||
await mockServer | ||
.forPost('https://api.segment.io/v1/batch') | ||
.withJsonBodyIncluding({ | ||
batch: [{ type: 'identify' }], | ||
}) | ||
.thenCallback(() => { | ||
return { | ||
statusCode: 200, | ||
}; | ||
}), | ||
]; | ||
} | ||
|
||
describe('Segment User Traits', function () { | ||
it('sends identify event when user opts in both metrics and data collection during onboarding', async function () { | ||
await withFixtures( | ||
{ | ||
fixtures: new FixtureBuilder({ onboarding: true }) | ||
.withMetaMetricsController({ | ||
metaMetricsId: MOCK_META_METRICS_ID, | ||
}) | ||
.build(), | ||
title: this.test?.fullTitle(), | ||
testSpecificMock: mockSegment, | ||
}, | ||
async ({ driver, mockedEndpoint: mockedEndpoints }) => { | ||
await createNewWalletOnboardingFlow({ | ||
driver, | ||
participateInMetaMetrics: true, | ||
dataCollectionForMarketing: true, | ||
}); | ||
const events = await getEventPayloads(driver, mockedEndpoints); | ||
assert.equal(events.length, 1); | ||
assert.deepStrictEqual(events[0].traits.is_metrics_opted_in, true); | ||
assert.deepStrictEqual(events[0].traits.has_marketing_consent, true); | ||
}, | ||
); | ||
}); | ||
|
||
it('sends identify event when user opts into metrics but not data collection during onboarding', async function () { | ||
await withFixtures( | ||
{ | ||
fixtures: new FixtureBuilder({ onboarding: true }) | ||
.withMetaMetricsController({ | ||
metaMetricsId: MOCK_META_METRICS_ID, | ||
}) | ||
.build(), | ||
title: this.test?.fullTitle(), | ||
testSpecificMock: mockSegment, | ||
}, | ||
async ({ driver, mockedEndpoint: mockedEndpoints }) => { | ||
await createNewWalletOnboardingFlow({ | ||
driver, | ||
participateInMetaMetrics: true, | ||
dataCollectionForMarketing: false, | ||
}); | ||
const events = await getEventPayloads(driver, mockedEndpoints); | ||
assert.equal(events.length, 1); | ||
assert.deepStrictEqual(events[0].traits.is_metrics_opted_in, true); | ||
assert.deepStrictEqual(events[0].traits.has_marketing_consent, false); | ||
}, | ||
); | ||
}); | ||
|
||
it('will not send identify event when user opts out of both metrics and data collection during onboarding', async function () { | ||
await withFixtures( | ||
{ | ||
fixtures: new FixtureBuilder({ onboarding: true }) | ||
.withMetaMetricsController({ | ||
metaMetricsId: MOCK_META_METRICS_ID, | ||
participateInMetaMetrics: true, | ||
}) | ||
.build(), | ||
title: this.test?.fullTitle(), | ||
testSpecificMock: mockSegment, | ||
}, | ||
async ({ driver, mockedEndpoint: mockedEndpoints }) => { | ||
await createNewWalletOnboardingFlow({ | ||
driver, | ||
participateInMetaMetrics: false, | ||
dataCollectionForMarketing: false, | ||
}); | ||
const events = await getEventPayloads(driver, mockedEndpoints); | ||
assert.equal(events.length, 0); | ||
}, | ||
); | ||
}); | ||
|
||
it('sends identify event when user enables metrics in privacy settings after opting out during onboarding', async function () { | ||
await withFixtures( | ||
{ | ||
fixtures: new FixtureBuilder({ onboarding: true }) | ||
.withMetaMetricsController({ | ||
metaMetricsId: MOCK_META_METRICS_ID, | ||
participateInMetaMetrics: false, | ||
}) | ||
.build(), | ||
title: this.test?.fullTitle(), | ||
testSpecificMock: mockSegment, | ||
}, | ||
async ({ driver, mockedEndpoint: mockedEndpoints }) => { | ||
let events = []; | ||
await completeCreateNewWalletOnboardingFlow({ | ||
driver, | ||
participateInMetaMetrics: false, | ||
}); | ||
events = await getEventPayloads(driver, mockedEndpoints); | ||
assert.equal(events.length, 0); | ||
await new HeaderNavbar(driver).openSettingsPage(); | ||
const settingsPage = new SettingsPage(driver); | ||
await settingsPage.check_pageIsLoaded(); | ||
await settingsPage.goToPrivacySettings(); | ||
|
||
const privacySettings = new PrivacySettings(driver); | ||
await privacySettings.check_pageIsLoaded(); | ||
await privacySettings.toggleParticipateInMetaMetrics(); | ||
events = await getEventPayloads(driver, mockedEndpoints); | ||
assert.equal(events.length, 1); | ||
assert.deepStrictEqual(events[0].traits.is_metrics_opted_in, true); | ||
assert.deepStrictEqual(events[0].traits.has_marketing_consent, false); | ||
}, | ||
); | ||
}); | ||
|
||
it('sends identify event when user opts in both metrics and data in privacy settings after opting out during onboarding', async function () { | ||
await withFixtures( | ||
{ | ||
fixtures: new FixtureBuilder({ onboarding: true }) | ||
.withMetaMetricsController({ | ||
metaMetricsId: MOCK_META_METRICS_ID, | ||
participateInMetaMetrics: false, | ||
}) | ||
.build(), | ||
title: this.test?.fullTitle(), | ||
testSpecificMock: mockSegment, | ||
}, | ||
async ({ driver, mockedEndpoint: mockedEndpoints }) => { | ||
let events = []; | ||
await completeCreateNewWalletOnboardingFlow({ | ||
driver, | ||
participateInMetaMetrics: false, | ||
}); | ||
events = await getEventPayloads(driver, mockedEndpoints); | ||
assert.equal(events.length, 0); | ||
await new HeaderNavbar(driver).openSettingsPage(); | ||
const settingsPage = new SettingsPage(driver); | ||
await settingsPage.check_pageIsLoaded(); | ||
await settingsPage.goToPrivacySettings(); | ||
|
||
const privacySettings = new PrivacySettings(driver); | ||
await privacySettings.check_pageIsLoaded(); | ||
await privacySettings.toggleParticipateInMetaMetrics(); | ||
await privacySettings.toggleDataCollectionForMarketing(); | ||
events = await getEventPayloads(driver, mockedEndpoints); | ||
assert.equal(events.length, 1); | ||
assert.deepStrictEqual(events[0].traits.is_metrics_opted_in, true); | ||
assert.deepStrictEqual(events[0].traits.has_marketing_consent, true); | ||
}, | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.