Skip to content

Commit

Permalink
Metametrics (#6171)
Browse files Browse the repository at this point in the history
* Add metametrics provider and util.

* Add backend api and state for participating in metametrics.

* Add frontend action for participating in metametrics.

* Add metametrics opt-in screen.

* Add metametrics events to first time flow.

* Add metametrics events for route changes

* Add metametrics events for send and confirm screens

* Add metametrics events to dropdowns, transactions, log in and out, settings, sig requests and main screen

* Ensures each log in is measured as a new visit by metametrics.

* Ensure metametrics is called with an empty string for dimensions params if specified

* Adds opt in metametrics modal after unlock for existing users

* Adds settings page toggle for opting in and out of MetaMetrics

* Switch metametrics dimensions to page level scope

* Lint, test and translation fixes for metametrics.

* Update design for metametrics opt-in screen

* Complete responsive styling of metametrics-opt-in modal

* Use new chart image on metrics opt in screens

* Incorporate the metametrics opt-in screen into the new onboarding flow

* Update e2e tests to accomodate metametrics changes

* Mock out metametrics network requests in integration tests

* Fix tx-list integration test to support metametrics provider.

* Send number of tokens and accounts data with every metametrics event.

* Update metametrics event descriptor schema and add new events.

* Fix import tos bug and send gas button bug due to metametrics changes.

* Various small fixes on the metametrics branch.

* Add origin custom variable type to metametrics.util

* Fix names of onboarding complete actions (metametrics).

* Fix names of Metrics Options actions (metametrics).

* Clean up code related to metametrics.

* Fix bad merge conflict resolution and improve promise handling in sendMetaMetrics event and confrim tx base

* Don't send a second metrics event if user has gone back during first time flow.

* Collect metametrics on going back from onboarding create/import.

* Add missing custom variable constants for metametrics

* Fix metametrics provider

* Make height of opt-in modal responsive.

* Adjust text content for opt-in modal.

* Update metametrics event names and clean up code in opt-in-modal

* Put phishing warning step next to last in onboarding flow

* Link terms of service on create and import screens of first time flow

* Add subtext to options on the onboarding select action screen.

* Fix styling of bullet points on end of onboarding screen.

* Combine phishing warning and congratulations screens.

* Fix placement of users if unlocking after an incomplete onboarding import flow.

* Fix capitalization in opt-in screen

* Fix last onboarding screen translations

* Add link to 'Learn More' on the last screen of onboarding

* Code clean up: metametrics branch

* Update e2e tests for phishing warning step removal

* e2e tests passing on metametrics branch

* Different tracking urls for metametrics on development and prod
  • Loading branch information
danjm authored Mar 5, 2019
1 parent 1765864 commit c757366
Show file tree
Hide file tree
Showing 94 changed files with 2,203 additions and 162 deletions.
24 changes: 21 additions & 3 deletions app/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -470,15 +470,21 @@
"message": "Tips on storing it safely"
},
"endOfFlowMessage3": {
"message": "Save a backup in multiple places"
"message": "Save a backup in multiple places."
},
"endOfFlowMessage4": {
"message": "Never tell anyone"
"message": "Never share the phrase with anyone."
},
"endOfFlowMessage5": {
"message": "If you need to back your seed phrase again, you can find it in Settings -> Security."
"message": "Be careful of phishing! MetaMask will never spontaneously ask for your seed phrase."
},
"endOfFlowMessage6": {
"message": "If you need to back your up seed phrase again, you can find it in Settings -> Security."
},
"endOfFlowMessage7": {
"message": "If you ever have questions or see something fishy, email [email protected]."
},
"endOfFlowMessage8": {
"message": "MetaMask cannot recover your seedphrase. Learn more."
},
"ensNameNotFound": {
Expand Down Expand Up @@ -689,6 +695,9 @@
"importWallet": {
"message": "Import Wallet"
},
"importYourExisting": {
"message": "Import your existing wallet using a 12 word seed phrase"
},
"imported": {
"message": "Imported",
"description": "status showing that an account has been fully loaded into the keyring"
Expand Down Expand Up @@ -1000,6 +1009,12 @@
"originalTotal": {
"message": "Original Total"
},
"participateInMetaMetrics": {
"message": "Participate in MetaMetrics"
},
"participateInMetaMetricsDesciption": {
"message": "Participate in MetaMetrics to help us make MetaMask better"
},
"password": {
"message": "Password"
},
Expand Down Expand Up @@ -1435,6 +1450,9 @@
"testFaucet": {
"message": "Test Faucet"
},
"thisWillCreate": {
"message": "This will create a new wallet and seed phrase"
},
"tips": {
"message": "Tips"
},
Expand Down
8 changes: 8 additions & 0 deletions app/images/metrics-chart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 43 additions & 1 deletion app/scripts/controllers/preferences.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const ObservableStore = require('obs-store')
const normalizeAddress = require('eth-sig-util').normalize
const { isValidAddress } = require('ethereumjs-util')
const { isValidAddress, sha3, bufferToHex } = require('ethereumjs-util')
const extend = require('xtend')


Expand Down Expand Up @@ -42,6 +42,8 @@ class PreferencesController {
// perform sensitive operations.
featureFlags: {},
knownMethodData: {},
participateInMetaMetrics: null,
firstTimeFlowType: null,
currentLocale: opts.initLangCode,
identities: {},
lostIdentities: {},
Expand All @@ -52,6 +54,8 @@ class PreferencesController {
},
completedOnboarding: false,
completedUiMigration: true,
metaMetricsId: null,
metaMetricsSendCount: 0,
}, opts.initState)

this.diagnostics = opts.diagnostics
Expand Down Expand Up @@ -92,6 +96,44 @@ class PreferencesController {
this.store.updateState({ useBlockie: val })
}

/**
* Setter for the `participateInMetaMetrics` property
*
* @param {boolean} bool Whether or not the user wants to participate in MetaMetrics
* @returns {string|null} the string of the new metametrics id, or null if not set
*
*/
setParticipateInMetaMetrics (bool) {
this.store.updateState({ participateInMetaMetrics: bool })
let metaMetricsId = null
if (bool && !this.store.getState().metaMetricsId) {
metaMetricsId = bufferToHex(sha3(String(Date.now()) + String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER))))
this.store.updateState({ metaMetricsId })
} else if (bool === false) {
this.store.updateState({ metaMetricsId })
}
return metaMetricsId
}

setMetaMetricsSendCount (val) {
this.store.updateState({ metaMetricsSendCount: val })
}

getMetaMetricsSendCount () {
return this.store.getState().metaMetricsSendCount
}

/**
* Setter for the `firstTimeFlowType` property
*
* @param {String} type Indicates the type of first time flow - create or import - the user wishes to follow
*
*/
setFirstTimeFlowType (type) {
this.store.updateState({ firstTimeFlowType: type })
}


getSuggestedTokens () {
return this.store.getState().suggestedTokens
}
Expand Down
41 changes: 41 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,9 @@ module.exports = class MetamaskController extends EventEmitter {
getState: (cb) => cb(null, this.getState()),
setCurrentCurrency: this.setCurrentCurrency.bind(this),
setUseBlockie: this.setUseBlockie.bind(this),
setParticipateInMetaMetrics: this.setParticipateInMetaMetrics.bind(this),
setMetaMetricsSendCount: this.setMetaMetricsSendCount.bind(this),
setFirstTimeFlowType: this.setFirstTimeFlowType.bind(this),
setCurrentLocale: this.setCurrentLocale.bind(this),
markAccountsFound: this.markAccountsFound.bind(this),
markPasswordForgotten: this.markPasswordForgotten.bind(this),
Expand Down Expand Up @@ -1624,6 +1627,44 @@ module.exports = class MetamaskController extends EventEmitter {
}
}

/**
* Sets whether or not the user will have usage data tracked with MetaMetrics
* @param {boolean} bool - True for users that wish to opt-in, false for users that wish to remain out.
* @param {Function} cb - A callback function called when complete.
*/
setParticipateInMetaMetrics (bool, cb) {
try {
const metaMetricsId = this.preferencesController.setParticipateInMetaMetrics(bool)
cb(null, metaMetricsId)
} catch (err) {
cb(err)
}
}

setMetaMetricsSendCount (val, cb) {
try {
this.preferencesController.setMetaMetricsSendCount(val)
cb(null)
} catch (err) {
cb(err)
}
}

/**
* Sets the type of first time flow the user wishes to follow: create or import
* @param {String} type - Indicates the type of first time flow the user wishes to follow
* @param {Function} cb - A callback function called when complete.
*/
setFirstTimeFlowType (type, cb) {
try {
this.preferencesController.setFirstTimeFlowType(type)
cb(null)
} catch (err) {
cb(err)
}
}


/**
* A method for setting a user's current locale, affecting the language rendered.
* @param {string} key - Locale identifier.
Expand Down
3 changes: 2 additions & 1 deletion development/states/tx-list-items.json
Original file line number Diff line number Diff line change
Expand Up @@ -1403,5 +1403,6 @@
],
"priceAndTimeEstimatesLastRetrieved": 1541527901281,
"errors": {}
}
},
"confirmTransaction": {}
}
1 change: 1 addition & 0 deletions test/e2e/beta/fetch-mocks.js

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions test/e2e/beta/from-import-beta-ui.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ describe('Using MetaMask with an existing account', function () {
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasBasic + '\')) }); } else if ' +
'(args[0] === "https://ethgasstation.info/json/predictTable.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } else if ' +
'(args[0].match(/chromeextensionmm/)) { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.metametrics + '\')) }); } else if ' +
'(args[0] === "https://dev.blockscale.net/api/gasexpress.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.gasExpress + '\')) }); } ' +
'return window.origFetch(...args); }'
Expand Down Expand Up @@ -110,6 +112,12 @@ describe('Using MetaMask with an existing account', function () {
await delay(largeDelayMs)
})

it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default'))
optOutButton.click()
await delay(largeDelayMs)
})

it('imports a seed phrase', async () => {
const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea'))
await seedTextArea.sendKeys(testSeedPhrase)
Expand All @@ -128,13 +136,6 @@ describe('Using MetaMask with an existing account', function () {
await delay(regularDelayMs)
})

it('clicks through the security warning screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Protect Your Keys!')]`))
const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})

it('clicks through the success screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`))
const doneButton = await findElement(driver, By.css('button.first-time-flow__button'))
Expand Down
31 changes: 24 additions & 7 deletions test/e2e/beta/metamask-beta-responsive-ui.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const {
loadExtension,
verboseReportOnFailure,
} = require('./helpers')
const fetchMockResponses = require('./fetch-mocks.js')

describe('MetaMask', function () {
let extensionId
Expand Down Expand Up @@ -61,6 +62,23 @@ describe('MetaMask', function () {
await driver.get(extensionUrl)
})

beforeEach(async function () {
await driver.executeScript(
'window.origFetch = window.fetch.bind(window);' +
'window.fetch = ' +
'(...args) => { ' +
'if (args[0] === "https://ethgasstation.info/json/ethgasAPI.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasBasic + '\')) }); } else if ' +
'(args[0] === "https://ethgasstation.info/json/predictTable.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } else if ' +
'(args[0].match(/chromeextensionmm/)) { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.metametrics + '\')) }); } else if ' +
'(args[0] === "https://dev.blockscale.net/api/gasexpress.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.gasExpress + '\')) }); } ' +
'return window.origFetch(...args); }'
)
})

afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver)
Expand Down Expand Up @@ -93,6 +111,12 @@ describe('MetaMask', function () {
await delay(largeDelayMs)
})

it('clicks the "I agree" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-confirm'))
optOutButton.click()
await delay(largeDelayMs)
})

it('accepts a secure password', async () => {
const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password'))
const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password'))
Expand All @@ -108,13 +132,6 @@ describe('MetaMask', function () {
await delay(regularDelayMs)
})

it('clicks through the security warning screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Protect Your Keys!')]`))
const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})

let seedPhrase

it('reveals the seed phrase', async () => {
Expand Down
23 changes: 14 additions & 9 deletions test/e2e/beta/metamask-beta-ui.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ describe('MetaMask', function () {
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasBasic + '\')) }); } else if ' +
'(args[0] === "https://ethgasstation.info/json/predictTable.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } else if ' +
'(args[0].match(/chromeextensionmm/)) { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.metametrics + '\')) }); } else if ' +
'(args[0] === "https://dev.blockscale.net/api/gasexpress.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.gasExpress + '\')) }); } ' +
'return window.origFetch(...args); }'
Expand Down Expand Up @@ -114,6 +116,12 @@ describe('MetaMask', function () {
await delay(largeDelayMs)
})

it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default'))
optOutButton.click()
await delay(largeDelayMs)
})

it('accepts a secure password', async () => {
const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password'))
const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password'))
Expand All @@ -129,13 +137,6 @@ describe('MetaMask', function () {
await delay(regularDelayMs)
})

it('clicks through the security warning screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Protect Your Keys!')]`))
const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})

let seedPhrase

it('reveals the seed phrase', async () => {
Expand Down Expand Up @@ -688,12 +689,16 @@ describe('MetaMask', function () {
})

it('rejects a transaction', async () => {
await delay(tinyDelayMs / 2)
const rejectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Reject')]`), 10000)
await delay(tinyDelayMs / 2)
await rejectButton.click()
await delay(regularDelayMs)

const navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
await delay(tinyDelayMs / 2)
const navigationText = await navigationElement.getText()
await delay(tinyDelayMs / 2)
assert.equal(navigationText.includes('3'), true, 'transaction rejected')
})

Expand Down Expand Up @@ -1124,7 +1129,7 @@ describe('MetaMask', function () {
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/))
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/))
await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/), 10000)

const walletBalance = await findElement(driver, By.css('.wallet-balance'))
await walletBalance.click()
Expand All @@ -1137,7 +1142,7 @@ describe('MetaMask', function () {
// or possibly until we use latest version of firefox in the tests
if (process.env.SELENIUM_BROWSER !== 'firefox') {
const tokenBalanceAmount = await findElements(driver, By.css('.transaction-view-balance__primary-balance'))
await driver.wait(until.elementTextMatches(tokenBalanceAmount[0], /43\s*TST/))
await driver.wait(until.elementTextMatches(tokenBalanceAmount[0], /43\s*TST/), 10000)
}
})
})
Expand Down
8 changes: 8 additions & 0 deletions test/integration/lib/confirm-sig-requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const {
timeout,
queryAsync,
} = require('../../lib/util')
const fetchMockResponses = require('../../e2e/beta/fetch-mocks.js')

QUnit.module('confirm sig requests')

Expand All @@ -19,6 +20,13 @@ async function runConfirmSigRequestsTest (assert, done) {
selectState.val('confirm sig requests')
reactTriggerChange(selectState[0])

global.fetch = (...args) => {
if (args[0].match(/chromeextensionmm/)) {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) })
}
return window.fetch(...args)
}

const pendingRequestItem = $.find('.transaction-list-item .transaction-list-item__grid')

if (pendingRequestItem[2]) {
Expand Down
Loading

0 comments on commit c757366

Please sign in to comment.