1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Refactor background Segment usage (#9509)

* Create wrapper function for segment events
* Extract transaction controller metrics calls into own function

Co-authored-by: Mark Stacey <markjstacey@gmail.com>
This commit is contained in:
Erik Marks 2020-10-08 09:41:23 -07:00 committed by GitHub
parent 4411f0eb9c
commit 30d6ad83f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 150 additions and 63 deletions

View File

@ -22,7 +22,6 @@ import cleanErrorStack from '../../lib/cleanErrorStack'
import { hexToBn, bnToHex, BnMultiplyByFraction } from '../../lib/util'
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys'
import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/app/pages/swaps/swaps.util'
import { segment, METAMETRICS_ANONYMOUS_ID } from '../../lib/segment'
import TransactionStateManager from './tx-state-manager'
import TxGasUtil from './tx-gas-utils'
import PendingTransactionTracker from './pending-tx-tracker'
@ -77,7 +76,8 @@ export default class TransactionController extends EventEmitter {
this.blockTracker = opts.blockTracker
this.signEthTx = opts.signTransaction
this.inProcessOfSigning = new Set()
this.version = opts.version
this._trackSegmentEvent = opts.trackSegmentEvent
this._getParticipateInMetrics = opts.getParticipateInMetrics
this.memStore = new ObservableStore({})
this.query = new EthQuery(this.provider)
@ -569,7 +569,6 @@ export default class TransactionController extends EventEmitter {
}
try {
// It seems that sometimes the numerical values being returned from
// this.query.getTransactionReceipt are BN instances and not strings.
const gasUsed = typeof txReceipt.gasUsed === 'string'
@ -586,64 +585,8 @@ export default class TransactionController extends EventEmitter {
txMeta.postTxBalance = postTxBalance.toString(16)
}
if (txMeta.swapMetaData) {
let { version } = this
if (process.env.METAMASK_ENVIRONMENT !== 'production') {
version = `${version}-${process.env.METAMASK_ENVIRONMENT}`
}
const segmentContext = {
app: {
version,
name: 'MetaMask Extension',
},
locale: this.preferencesStore.getState().currentLocale.replace('_', '-'),
page: {
path: '/background-process',
title: 'Background Process',
url: '/background-process',
},
userAgent: window.navigator.userAgent,
}
const metametricsId = this.preferencesStore.getState().metaMetricsId
if (metametricsId && txMeta.swapMetaData && txReceipt.status !== '0x0') {
const tokensReceived = getSwapsTokensReceivedFromTxMeta(
txMeta.destinationTokenSymbol,
txMeta,
txMeta.destinationTokenAddress,
txMeta.txParams.from,
txMeta.destinationTokenDecimals,
)
const quoteVsExecutionRatio = `${(new BigNumber(tokensReceived, 10))
.div(txMeta.swapMetaData?.['token_to_amount'], 10).times(100).round(2)}%`
segment.track({ event: 'Swap Completed', userId: metametricsId, context: segmentContext, category: 'swaps' })
segment.track({
event: 'Swap Completed',
properties: {
...txMeta.swapMetaData,
token_to_amount_received: tokensReceived,
quote_vs_executionRatio: quoteVsExecutionRatio,
},
context: segmentContext,
anonymousId: METAMETRICS_ANONYMOUS_ID,
excludeMetaMetricsId: true,
category: 'swaps',
})
} else if (metametricsId && txMeta.swapMetaData) {
segment.track({ event: 'Swap Failed', userId: metametricsId, context: segmentContext, category: 'swaps' })
segment.track({
event: 'Swap Failed',
properties: { ...txMeta.swapMetaData },
anonymousId: METAMETRICS_ANONYMOUS_ID,
excludeMetaMetricsId: true,
context: segmentContext,
category: 'swaps',
})
}
}
this.txStateManager.updateTx(txMeta, 'transactions#confirmTransaction - add txReceipt')
this._trackSwapsMetrics(txMeta)
} catch (err) {
log.error(err)
}
@ -879,4 +822,53 @@ export default class TransactionController extends EventEmitter {
this.memStore.updateState({ unapprovedTxs, currentNetworkTxList })
}
_trackSwapsMetrics (txMeta) {
if (this._getParticipateInMetrics() && txMeta.swapMetaData) {
if (txMeta.txReceipt.status === '0x0') {
this._trackSegmentEvent({
event: 'Swap Failed',
category: 'swaps',
excludeMetaMetricsId: false,
})
this._trackSegmentEvent({
event: 'Swap Failed',
properties: { ...txMeta.swapMetaData },
category: 'swaps',
excludeMetaMetricsId: true,
})
} else {
const tokensReceived = getSwapsTokensReceivedFromTxMeta(
txMeta.destinationTokenSymbol,
txMeta,
txMeta.destinationTokenAddress,
txMeta.txParams.from,
txMeta.destinationTokenDecimals,
)
const quoteVsExecutionRatio = `${
(new BigNumber(tokensReceived, 10))
.div(txMeta.swapMetaData.token_to_amount, 10)
.times(100)
.round(2)
}%`
this._trackSegmentEvent({
event: 'Swap Completed',
category: 'swaps',
excludeMetaMetricsId: false,
})
this._trackSegmentEvent({
event: 'Swap Completed',
properties: {
...txMeta.swapMetaData,
token_to_amount_received: tokensReceived,
quote_vs_executionRatio: quoteVsExecutionRatio,
},
excludeMetaMetricsId: true,
})
}
}
}
}

View File

@ -4,6 +4,8 @@ const inDevelopment = process.env.METAMASK_DEBUG || process.env.IN_TEST
const flushAt = inDevelopment ? 1 : undefined
const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000'
const segmentNoop = {
track () {
// noop
@ -20,8 +22,83 @@ const segmentNoop = {
// provided a SEGMENT_WRITE_KEY. This also holds true for test environments and
// E2E, which is handled in the build process by never providing the SEGMENT_WRITE_KEY
// which process.env.IN_TEST is true
export const segment = process.env.SEGMENT_WRITE_KEY
const segment = process.env.SEGMENT_WRITE_KEY
? new Analytics(process.env.SEGMENT_WRITE_KEY, { flushAt })
: segmentNoop
export const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000'
/**
* Returns a function for tracking Segment events.
*
* @param {string} metamaskVersion - The current version of the MetaMask
* extension.
* @param {Function} getParticipateInMetrics - A function that returns
* whether the user participates in MetaMetrics.
* @param {Function} getMetricsState - A function for getting state relevant
* to MetaMetrics/Segment.
*/
export function getTrackSegmentEvent (
metamaskVersion,
getParticipateInMetrics,
getMetricsState,
) {
const version = process.env.METAMASK_ENVIRONMENT === 'production'
? metamaskVersion
: `${metamaskVersion}-${process.env.METAMASK_ENVIRONMENT}`
const segmentContext = {
app: {
name: 'MetaMask Extension',
version,
},
page: {
path: '/background-process',
title: 'Background Process',
url: '/background-process',
},
userAgent: window.navigator.userAgent,
}
/**
* Tracks a Segment event per the given arguments.
*
* @param {string} event - The event name.
* @param {string} category - The event category.
* @param {Object} [properties] - The event properties.
* @param {boolean} [excludeMetaMetricsId] - `true` if the user's MetaMetrics id should
* not be included, and `false` otherwise. Default: `true`
*/
return function trackSegmentEvent ({
event,
category,
properties = {},
excludeMetaMetricsId = true,
}) {
if (!event || !category) {
throw new Error('Must specify event and category.')
}
if (!getParticipateInMetrics()) {
return
}
const { currentLocale, metaMetricsId } = getMetricsState()
const trackOptions = {
event,
category,
context: {
...segmentContext,
locale: currentLocale.replace('_', '-'),
},
properties,
}
if (excludeMetaMetricsId) {
trackOptions.anonymousId = METAMETRICS_ANONYMOUS_ID
} else {
trackOptions.userId = metaMetricsId
}
segment.track(trackOptions)
}
}

View File

@ -55,6 +55,7 @@ import getRestrictedMethods from './controllers/permissions/restrictedMethods'
import nodeify from './lib/nodeify'
import accountImporter from './account-import-strategies'
import seedPhraseVerifier from './lib/seed-phrase-verifier'
import { getTrackSegmentEvent } from './lib/segment'
import backgroundMetaMetricsEvent from './lib/background-metametrics'
@ -114,6 +115,19 @@ export default class MetamaskController extends EventEmitter {
migrateAddressBookState: this.migrateAddressBookState.bind(this),
})
// This depends on preferences controller state
this.trackSegmentEvent = getTrackSegmentEvent(
this.platform.getVersion(),
() => this.preferencesController.getParticipateInMetaMetrics(),
() => {
const {
currentLocale,
metaMetricsId,
} = this.preferencesController.getState()
return { currentLocale, metaMetricsId }
},
)
this.appStateController = new AppStateController({
addUnlockListener: this.on.bind(this, 'unlock'),
isUnlocked: this.isUnlocked.bind(this),
@ -239,7 +253,8 @@ export default class MetamaskController extends EventEmitter {
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
provider: this.provider,
blockTracker: this.blockTracker,
version: this.platform.getVersion(),
trackSegmentEvent: this.trackSegmentEvent,
getParticipateInMetrics: () => this.preferencesController.getParticipateInMetaMetrics(),
})
this.txController.on('newUnapprovedTx', () => opts.showUnapprovedTx())

View File

@ -50,6 +50,7 @@ describe('Transaction Controller', function () {
}),
getPermittedAccounts: () => undefined,
getCurrentChainId: () => currentChainId,
getParticipateInMetrics: () => false,
})
txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop })
})
@ -558,6 +559,7 @@ describe('Transaction Controller', function () {
ethTx.sign(_fromAccount.key)
resolve()
}),
getParticipateInMetrics: () => false,
})
const result = await _txController._determineTransactionCategory({
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
@ -590,6 +592,7 @@ describe('Transaction Controller', function () {
ethTx.sign(_fromAccount.key)
resolve()
}),
getParticipateInMetrics: () => false,
})
const result = await _txController._determineTransactionCategory({
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',