diff --git a/app/scripts/background.js b/app/scripts/background.js index ac1b59d30..2f8d40ea0 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -22,6 +22,9 @@ import { SECOND } from '../../shared/constants/time'; import { REJECT_NOTFICIATION_CLOSE, REJECT_NOTFICIATION_CLOSE_SIG, + EVENT, + EVENT_NAMES, + TRAITS, } from '../../shared/constants/metametrics'; import { isManifestV3 } from '../../shared/modules/mv3.utils'; import { maskObject } from '../../shared/modules/object.utils'; @@ -69,6 +72,7 @@ let notificationIsOpen = false; let uiIsTriggering = false; const openMetamaskTabsIDs = {}; const requestAccountTabIds = {}; +let controller; // state persistence const inTest = process.env.IN_TEST; @@ -314,7 +318,7 @@ function setupController(initState, initLangCode, remoteSourcePort) { // MetaMask Controller // - const controller = new MetamaskController({ + controller = new MetamaskController({ infuraProjectId: process.env.INFURA_PROJECT_ID, // User confirmation callbacks: showUserConfirmation: triggerUi, @@ -751,12 +755,32 @@ async function openPopup() { }); } +// It adds the "App Installed" event into a queue of events, which will be tracked only after a user opts into metrics. +const addAppInstalledEvent = () => { + if (controller) { + controller.metaMetricsController.updateTraits({ + [TRAITS.INSTALL_DATE_EXT]: new Date().toISOString().split('T')[0], // yyyy-mm-dd + }); + controller.metaMetricsController.addEventBeforeMetricsOptIn({ + category: EVENT.CATEGORIES.APP, + event: EVENT_NAMES.APP_INSTALLED, + properties: {}, + }); + return; + } + setTimeout(() => { + // If the controller is not set yet, we wait and try to add the "App Installed" event again. + addAppInstalledEvent(); + }, 1000); +}; + // On first install, open a new tab with MetaMask browser.runtime.onInstalled.addListener(({ reason }) => { if ( reason === 'install' && !(process.env.METAMASK_DEBUG || process.env.IN_TEST) ) { + addAppInstalledEvent(); platform.openExtensionInBrowser(); } }); diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 4fa9ee2a3..0412def31 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -19,6 +19,8 @@ import { } from '../../../shared/constants/metametrics'; import { SECOND } from '../../../shared/constants/time'; +const EXTENSION_UNINSTALL_URL = 'https://metamask.io/uninstalled'; + const defaultCaptureException = (err) => { // throw error on clean stack so its captured by platform integrations (eg sentry) // but does not interrupt the call stack @@ -52,6 +54,9 @@ const exceptionsToFilter = { * whether or not events are tracked * @property {{[string]: MetaMetricsEventFragment}} [fragments] - Object keyed * by UUID with stored fragments as values. + * @property {Array} [eventsBeforeMetricsOptIn] - Array of queued events added before + * a user opts into metrics. + * @property {object} [traits] - Traits that are not derived from other state keys. */ export default class MetaMetricsController { @@ -69,6 +74,7 @@ export default class MetaMetricsController { * identifier from the network controller * @param {string} options.version - The version of the extension * @param {string} options.environment - The environment the extension is running in + * @param {string} options.extension - webextension-polyfill * @param {MetaMetricsControllerState} options.initState - State to initialized with * @param options.captureException */ @@ -81,6 +87,7 @@ export default class MetaMetricsController { version, environment, initState, + extension, captureException = defaultCaptureException, }) { this._captureException = (err) => { @@ -96,12 +103,16 @@ export default class MetaMetricsController { this.locale = prefState.currentLocale.replace('_', '-'); this.version = environment === 'production' ? version : `${version}-${environment}`; + this.extension = extension; + this.environment = environment; const abandonedFragments = omitBy(initState?.fragments, 'persist'); this.store = new ObservableStore({ participateInMetaMetrics: null, metaMetricsId: null, + eventsBeforeMetricsOptIn: [], + traits: {}, ...initState, fragments: { ...initState?.fragments, @@ -315,6 +326,24 @@ export default class MetaMetricsController { this._identify(allValidTraits); } + // It sets an uninstall URL ("Sorry to see you go!" page), + // which is opened if a user uninstalls the extension. + updateExtensionUninstallUrl(participateInMetaMetrics, metaMetricsId) { + // TODO: Change it to the right URL once it's available. + + const query = {}; + if (participateInMetaMetrics) { + // We only want to track these things if a user opted into metrics. + query.id = metaMetricsId; + query.env = this.environment; + query.av = this.version; + } + const queryString = new URLSearchParams(query); + this.extension.runtime.setUninstallURL( + `${EXTENSION_UNINSTALL_URL}?${queryString}`, + ); + } + /** * Setter for the `participateInMetaMetrics` property * @@ -331,6 +360,12 @@ export default class MetaMetricsController { metaMetricsId = null; } this.store.updateState({ participateInMetaMetrics, metaMetricsId }); + if (participateInMetaMetrics) { + this.trackEventsAfterMetricsOptIn(); + this.clearEventsAfterMetricsOptIn(); + } + // TODO: Uncomment the line below once we have a "Sorry to see you go" page ready. + // this.updateExtensionUninstallUrl(participateInMetaMetrics, metaMetricsId); return metaMetricsId; } @@ -472,6 +507,37 @@ export default class MetaMetricsController { } } + // Track all queued events after a user opted into metrics. + trackEventsAfterMetricsOptIn() { + const { eventsBeforeMetricsOptIn } = this.store.getState(); + eventsBeforeMetricsOptIn.forEach((eventBeforeMetricsOptIn) => { + this.trackEvent(eventBeforeMetricsOptIn); + }); + } + + // Once we track queued events after a user opts into metrics, we want to clear the event queue. + clearEventsAfterMetricsOptIn() { + this.store.updateState({ + eventsBeforeMetricsOptIn: [], + }); + } + + // It adds an event into a queue, which is only tracked if a user opts into metrics. + addEventBeforeMetricsOptIn(event) { + const prevState = this.store.getState().eventsBeforeMetricsOptIn; + this.store.updateState({ + eventsBeforeMetricsOptIn: [...prevState, event], + }); + } + + // Add or update traits for tracking. + updateTraits(newTraits) { + const { traits } = this.store.getState(); + this.store.updateState({ + traits: { ...traits, ...newTraits }, + }); + } + /** PRIVATE METHODS */ /** @@ -549,11 +615,13 @@ export default class MetaMetricsController { * @returns {MetaMetricsTraits | null} traits that have changed since last update */ _buildUserTraitsObject(metamaskState) { + const { traits } = this.store.getState(); /** @type {MetaMetricsTraits} */ const currentTraits = { [TRAITS.ADDRESS_BOOK_ENTRIES]: sum( Object.values(metamaskState.addressBook).map(size), ), + [TRAITS.INSTALL_DATE_EXT]: traits[TRAITS.INSTALL_DATE_EXT] || '', [TRAITS.LEDGER_CONNECTION_TYPE]: metamaskState.ledgerTransportType, [TRAITS.NETWORKS_ADDED]: metamaskState.frequentRpcListDetail.map( (rpc) => rpc.chainId, diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 57f4c5c3c..c5449b90e 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -688,6 +688,7 @@ describe('MetaMetricsController', function () { assert.deepEqual(traits, { [TRAITS.ADDRESS_BOOK_ENTRIES]: 3, + [TRAITS.INSTALL_DATE_EXT]: '', [TRAITS.LEDGER_CONNECTION_TYPE]: 'web-hid', [TRAITS.NETWORKS_ADDED]: [MAINNET_CHAIN_ID, ROPSTEN_CHAIN_ID, '0xaf'], [TRAITS.NETWORKS_WITHOUT_TICKER]: ['0xaf'], diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1f7d425db..40e8a8b78 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -394,6 +394,7 @@ export default class MetamaskController extends EventEmitter { ), version: this.platform.getVersion(), environment: process.env.METAMASK_ENVIRONMENT, + extension: this.extension, initState: initState.MetaMetricsController, captureException, }); diff --git a/shared/constants/metametrics.js b/shared/constants/metametrics.js index 1a875aa41..dd498f0aa 100644 --- a/shared/constants/metametrics.js +++ b/shared/constants/metametrics.js @@ -183,6 +183,7 @@ * @property {'theme'} THEME - when the user's theme changes we identify the theme trait * @property {'token_detection_enabled'} TOKEN_DETECTION_ENABLED - when token detection feature is toggled we * identify the token_detection_enabled trait + * @property {'install_date_ext'} INSTALL_DATE_EXT - when the user installed the extension */ /** @@ -192,6 +193,7 @@ export const TRAITS = { ADDRESS_BOOK_ENTRIES: 'address_book_entries', + INSTALL_DATE_EXT: 'install_date_ext', LEDGER_CONNECTION_TYPE: 'ledger_connection_type', NETWORKS_ADDED: 'networks_added', NETWORKS_WITHOUT_TICKER: 'networks_without_ticker', @@ -201,8 +203,8 @@ export const TRAITS = { NUMBER_OF_NFTS: 'number_of_nfts', NUMBER_OF_TOKENS: 'number_of_tokens', OPENSEA_API_ENABLED: 'opensea_api_enabled', - THREE_BOX_ENABLED: 'three_box_enabled', THEME: 'theme', + THREE_BOX_ENABLED: 'three_box_enabled', TOKEN_DETECTION_ENABLED: 'token_detection_enabled', }; @@ -276,12 +278,16 @@ export const REJECT_NOTFICIATION_CLOSE_SIG = */ export const EVENT_NAMES = { - ENCRYPTION_PUBLIC_KEY_APPROVED: 'Encryption Public Key Approved', - ENCRYPTION_PUBLIC_KEY_REJECTED: 'Encryption Public Key Rejected', - ENCRYPTION_PUBLIC_KEY_REQUESTED: 'Encryption Public Key Requested', + APP_INSTALLED: 'App Installed', DECRYPTION_APPROVED: 'Decryption Approved', DECRYPTION_REJECTED: 'Decryption Rejected', DECRYPTION_REQUESTED: 'Decryption Requested', + ENCRYPTION_PUBLIC_KEY_APPROVED: 'Encryption Public Key Approved', + ENCRYPTION_PUBLIC_KEY_REJECTED: 'Encryption Public Key Rejected', + ENCRYPTION_PUBLIC_KEY_REQUESTED: 'Encryption Public Key Requested', + NEW_WALLET_CREATED: 'New Wallet Created', + NEW_WALLET_IMPORTED: 'New Wallet Imported', + NFT_ADDED: 'NFT Added', PERMISSIONS_APPROVED: 'Permissions Approved', PERMISSIONS_REJECTED: 'Permissions Rejected', PERMISSIONS_REQUESTED: 'Permissions Requested', @@ -289,10 +295,10 @@ export const EVENT_NAMES = { SIGNATURE_APPROVED: 'Signature Approved', SIGNATURE_REJECTED: 'Signature Rejected', SIGNATURE_REQUESTED: 'Signature Requested', + SUPPORT_LINK_CLICKED: 'Support Link Clicked', TOKEN_ADDED: 'Token Added', TOKEN_DETECTED: 'Token Detected', TOKEN_HIDDEN: 'Token Hidden', - NFT_ADDED: 'NFT Added', TOKEN_IMPORT_CANCELED: 'Token Import Canceled', TOKEN_IMPORT_CLICKED: 'Token Import Clicked', }; @@ -300,8 +306,12 @@ export const EVENT_NAMES = { export const EVENT = { CATEGORIES: { ACCOUNTS: 'Accounts', + APP: 'App', AUTH: 'Auth', BACKGROUND: 'Background', + ERROR: 'Error', + FOOTER: 'Footer', + HOME: 'Home', INPAGE_PROVIDER: 'inpage_provider', MESSAGES: 'Messages', NAVIGATION: 'Navigation', @@ -314,29 +324,35 @@ export const EVENT = { TRANSACTIONS: 'Transactions', WALLET: 'Wallet', }, + LOCATION: { + TOKEN_DETAILS: 'token_details', + TOKEN_DETECTION: 'token_detection', + TOKEN_MENU: 'token_menu', + }, SOURCE: { NETWORK: { - POPULAR_NETWORK_LIST: 'popular_network_list', CUSTOM_NETWORK_FORM: 'custom_network_form', + POPULAR_NETWORK_LIST: 'popular_network_list', }, SWAPS: { MAIN_VIEW: 'Main View', TOKEN_VIEW: 'Token View', }, - TRANSACTION: { - USER: 'user', - DAPP: 'dapp', - }, TOKEN: { CUSTOM: 'custom', - DETECTED: 'detected', DAPP: 'dapp', + DETECTED: 'detected', LIST: 'list', }, + TRANSACTION: { + DAPP: 'dapp', + USER: 'user', + }, }, - LOCATION: { - TOKEN_DETECTION: 'token_detection', - TOKEN_MENU: 'token_menu', - TOKEN_DETAILS: 'token_details', - }, +}; + +// Values below (e.g. 'location') can be used in the "properties" +// tracking object as keys, e.g. { location: 'Home' } +export const CONTEXT_PROPS = { + PAGE_TITLE: 'location', }; diff --git a/ui/components/app/account-menu/account-menu.component.js b/ui/components/app/account-menu/account-menu.component.js index c50231bc7..6a708e678 100644 --- a/ui/components/app/account-menu/account-menu.component.js +++ b/ui/components/app/account-menu/account-menu.component.js @@ -5,7 +5,11 @@ import Fuse from 'fuse.js'; import InputAdornment from '@material-ui/core/InputAdornment'; import classnames from 'classnames'; import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; -import { EVENT } from '../../../../shared/constants/metametrics'; +import { + EVENT, + EVENT_NAMES, + CONTEXT_PROPS, +} from '../../../../shared/constants/metametrics'; import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import Identicon from '../../ui/identicon'; import SiteIcon from '../../ui/site-icon'; @@ -442,6 +446,18 @@ export default class AccountMenu extends Component { } { + trackEvent( + { + category: EVENT.CATEGORIES.NAVIGATION, + event: EVENT_NAMES.SUPPORT_LINK_CLICKED, + properties: { + url: supportLink, + }, + }, + { + contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE], + }, + ); global.platform.openTab({ url: supportLink }); }} icon={ diff --git a/ui/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js b/ui/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js index 15e0dfa08..b25976143 100644 --- a/ui/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js +++ b/ui/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; + import MetaFoxLogo from '../../../ui/metafox-logo'; import PageContainerFooter from '../../../ui/page-container/page-container-footer'; import { EVENT } from '../../../../../shared/constants/metametrics'; diff --git a/ui/contexts/metametrics.js b/ui/contexts/metametrics.js index fc63dafda..fd3d15265 100644 --- a/ui/contexts/metametrics.js +++ b/ui/contexts/metametrics.js @@ -17,6 +17,7 @@ import { captureException, captureMessage } from '@sentry/browser'; import { omit } from 'lodash'; import { getEnvironmentType } from '../../app/scripts/lib/util'; import { PATH_NAME_MAP } from '../helpers/constants/routes'; +import { CONTEXT_PROPS } from '../../shared/constants/metametrics'; import { useSegmentContext } from '../hooks/useSegmentContext'; import { trackMetaMetricsEvent, trackMetaMetricsPage } from '../store/actions'; @@ -57,11 +58,26 @@ export function MetaMetricsProvider({ children }) { const location = useLocation(); const context = useSegmentContext(); + // Sometimes we want to track context properties inside the event's "properties" object. + const addContextPropsIntoEventProperties = (payload, options) => { + const fields = options?.contextPropsIntoEventProperties; + if (!fields || fields.length === 0) { + return; + } + if (!payload.properties) { + payload.properties = {}; + } + if (fields.includes(CONTEXT_PROPS.PAGE_TITLE)) { + payload.properties[CONTEXT_PROPS.PAGE_TITLE] = context.page?.title; + } + }; + /** * @type {UITrackEventMethod} */ const trackEvent = useCallback( (payload, options) => { + addContextPropsIntoEventProperties(payload, options); trackMetaMetricsEvent( { ...payload, diff --git a/ui/hooks/useTokensToSearch.js b/ui/hooks/useTokensToSearch.js index 424fcb9dc..6023e595d 100644 --- a/ui/hooks/useTokensToSearch.js +++ b/ui/hooks/useTokensToSearch.js @@ -30,7 +30,8 @@ export function getRenderableTokenData( if (isSwapsDefaultTokenSymbol(symbol, chainId)) { contractExchangeRate = 1; } else if (string && conversionRate > 0) { - // This condition improves performance significantly. + // This condition improves performance significantly, because it only gets a contract exchange rate + // if a token amount is truthy and conversion rate is higher than 0. contractExchangeRate = contractExchangeRates[toChecksumHexAddress(address)]; } const formattedFiat = diff --git a/ui/pages/error/error.component.js b/ui/pages/error/error.component.js index 2e6ede6ca..888a64a07 100644 --- a/ui/pages/error/error.component.js +++ b/ui/pages/error/error.component.js @@ -3,10 +3,16 @@ import PropTypes from 'prop-types'; import { getEnvironmentType } from '../../../app/scripts/lib/util'; import { ENVIRONMENT_TYPE_POPUP } from '../../../shared/constants/app'; import { SUPPORT_REQUEST_LINK } from '../../helpers/constants/common'; +import { + EVENT, + EVENT_NAMES, + CONTEXT_PROPS, +} from '../../../shared/constants/metametrics'; class ErrorPage extends PureComponent { static contextTypes = { t: PropTypes.func.isRequired, + trackEvent: PropTypes.func, }; static propTypes = { @@ -41,6 +47,20 @@ class ErrorPage extends PureComponent { key="metamaskSupportLink" rel="noopener noreferrer" href={SUPPORT_REQUEST_LINK} + onClick={() => { + this.context.trackEvent( + { + category: EVENT.CATEGORIES.ERROR, + event: EVENT_NAMES.SUPPORT_LINK_CLICKED, + properties: { + url: SUPPORT_REQUEST_LINK, + }, + }, + { + contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE], + }, + ); + }} > {this.context.t('here')} diff --git a/ui/pages/first-time-flow/end-of-flow/end-of-flow.component.js b/ui/pages/first-time-flow/end-of-flow/end-of-flow.component.js index 54eaf6d4c..ef541c65d 100644 --- a/ui/pages/first-time-flow/end-of-flow/end-of-flow.component.js +++ b/ui/pages/first-time-flow/end-of-flow/end-of-flow.component.js @@ -6,7 +6,11 @@ import MetaFoxLogo from '../../../components/ui/metafox-logo'; import { SUPPORT_REQUEST_LINK } from '../../../helpers/constants/common'; import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'; import { returnToOnboardingInitiatorTab } from '../onboarding-initiator-util'; -import { EVENT } from '../../../../shared/constants/metametrics'; +import { + EVENT, + EVENT_NAMES, + CONTEXT_PROPS, +} from '../../../../shared/constants/metametrics'; export default class EndOfFlowScreen extends PureComponent { static contextTypes = { @@ -99,6 +103,20 @@ export default class EndOfFlowScreen extends PureComponent { key="metamaskSupportLink" rel="noopener noreferrer" href={SUPPORT_REQUEST_LINK} + onClick={() => { + this.context.trackEvent( + { + category: EVENT.CATEGORIES.ONBOARDING, + event: EVENT_NAMES.SUPPORT_LINK_CLICKED, + properties: { + url: SUPPORT_REQUEST_LINK, + }, + }, + { + contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE], + }, + ); + }} > {this.context.t('here')} diff --git a/ui/pages/first-time-flow/end-of-flow/end-of-flow.container.js b/ui/pages/first-time-flow/end-of-flow/end-of-flow.container.js index fa3bcec2d..64eef95a7 100644 --- a/ui/pages/first-time-flow/end-of-flow/end-of-flow.container.js +++ b/ui/pages/first-time-flow/end-of-flow/end-of-flow.container.js @@ -1,11 +1,13 @@ import { connect } from 'react-redux'; + import { getOnboardingInitiator } from '../../../selectors'; import { setCompletedOnboarding } from '../../../store/actions'; +import { EVENT_NAMES } from '../../../../shared/constants/metametrics'; import EndOfFlow from './end-of-flow.component'; const firstTimeFlowTypeNameMap = { - create: 'New Wallet Created', - import: 'New Wallet Imported', + create: EVENT_NAMES.NEW_WALLET_CREATED, + import: EVENT_NAMES.NEW_WALLET_IMPORTED, }; const mapStateToProps = (state) => { diff --git a/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js b/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js index 24c9deefc..d9bb82c17 100644 --- a/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js +++ b/ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js @@ -10,7 +10,10 @@ import { DEFAULT_ROUTE, INITIALIZE_SEED_PHRASE_INTRO_ROUTE, } from '../../../../helpers/constants/routes'; -import { EVENT } from '../../../../../shared/constants/metametrics'; +import { + EVENT, + EVENT_NAMES, +} from '../../../../../shared/constants/metametrics'; import { returnToOnboardingInitiatorTab } from '../../onboarding-initiator-util'; import { exportAsFile } from '../../../../../shared/modules/export-utils'; @@ -78,6 +81,15 @@ export default class RevealSeedPhrase extends PureComponent { await Promise.all([setCompletedOnboarding(), setSeedPhraseBackedUp(false)]); + this.context.trackEvent({ + category: EVENT.CATEGORIES.ONBOARDING, + event: EVENT_NAMES.NEW_WALLET_CREATED, + properties: { + action: 'Onboarding Complete', + legacy_event: true, + }, + }); + if (onboardingInitiator) { await returnToOnboardingInitiatorTab(onboardingInitiator); } diff --git a/ui/pages/home/beta/beta-home-footer.component.js b/ui/pages/home/beta/beta-home-footer.component.js index 886d7ddca..845d41ace 100644 --- a/ui/pages/home/beta/beta-home-footer.component.js +++ b/ui/pages/home/beta/beta-home-footer.component.js @@ -1,13 +1,39 @@ -import React from 'react'; +import React, { useContext } from 'react'; + import { SUPPORT_REQUEST_LINK } from '../../../helpers/constants/common'; import { useI18nContext } from '../../../hooks/useI18nContext'; +import { + EVENT, + EVENT_NAMES, + CONTEXT_PROPS, +} from '../../../../shared/constants/metametrics'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; const BetaHomeFooter = () => { const t = useI18nContext(); + const trackEvent = useContext(MetaMetricsContext); return ( <> - + { + trackEvent( + { + category: EVENT.CATEGORIES.FOOTER, + event: EVENT_NAMES.SUPPORT_LINK_CLICKED, + properties: { + url: SUPPORT_REQUEST_LINK, + }, + }, + { + contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE], + }, + ); + }} + > {t('needHelpSubmitTicket')} {' '} |{' '} diff --git a/ui/pages/home/flask/flask-home-footer.component.js b/ui/pages/home/flask/flask-home-footer.component.js index cac819e50..0925d9f12 100644 --- a/ui/pages/home/flask/flask-home-footer.component.js +++ b/ui/pages/home/flask/flask-home-footer.component.js @@ -1,13 +1,39 @@ -import React from 'react'; +import React, { useContext } from 'react'; + import { SUPPORT_REQUEST_LINK } from '../../../helpers/constants/common'; import { useI18nContext } from '../../../hooks/useI18nContext'; +import { + EVENT, + EVENT_NAMES, + CONTEXT_PROPS, +} from '../../../../shared/constants/metametrics'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; const FlaskHomeFooter = () => { const t = useI18nContext(); + const trackEvent = useContext(MetaMetricsContext); return ( <> - + { + trackEvent( + { + category: EVENT.CATEGORIES.FOOTER, + event: EVENT_NAMES.SUPPORT_LINK_CLICKED, + properties: { + url: SUPPORT_REQUEST_LINK, + }, + }, + { + contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE], + }, + ); + }} + > {t('needHelpSubmitTicket')} {' '} |{' '} diff --git a/ui/pages/home/home.component.js b/ui/pages/home/home.component.js index c893a4283..0716e5667 100644 --- a/ui/pages/home/home.component.js +++ b/ui/pages/home/home.component.js @@ -3,6 +3,11 @@ import PropTypes from 'prop-types'; import { Redirect, Route } from 'react-router-dom'; ///: BEGIN:ONLY_INCLUDE_IN(main) import { SUPPORT_LINK } from '../../helpers/constants/common'; +import { + EVENT, + EVENT_NAMES, + CONTEXT_PROPS, +} from '../../../shared/constants/metametrics'; ///: END:ONLY_INCLUDE_IN import { formatDate } from '../../helpers/utils/util'; import AssetList from '../../components/app/asset-list'; @@ -73,6 +78,7 @@ function shouldCloseNotificationPopup({ export default class Home extends PureComponent { static contextTypes = { t: PropTypes.func, + trackEvent: PropTypes.func, }; static propTypes = { @@ -608,6 +614,7 @@ export default class Home extends PureComponent { !completedOnboarding) && announcementsToShow && showWhatsNewPopup; + return (
@@ -681,6 +688,22 @@ export default class Home extends PureComponent { target="_blank" rel="noopener noreferrer" key="need-help-link" + onClick={() => { + this.context.trackEvent( + { + category: EVENT.CATEGORIES.HOME, + event: EVENT_NAMES.SUPPORT_LINK_CLICKED, + properties: { + url: SUPPORT_LINK, + }, + }, + { + contextPropsIntoEventProperties: [ + CONTEXT_PROPS.PAGE_TITLE, + ], + }, + ); + }} > {t('needHelpLinkText')} , diff --git a/ui/pages/onboarding-flow/creation-successful/creation-successful.js b/ui/pages/onboarding-flow/creation-successful/creation-successful.js index 969487f4e..452b9246c 100644 --- a/ui/pages/onboarding-flow/creation-successful/creation-successful.js +++ b/ui/pages/onboarding-flow/creation-successful/creation-successful.js @@ -1,6 +1,7 @@ import React, { useContext } from 'react'; import { useHistory } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; + import Box from '../../../components/ui/box'; import Typography from '../../../components/ui/typography'; import Button from '../../../components/ui/button'; @@ -18,12 +19,12 @@ import { import { setCompletedOnboarding } from '../../../store/actions'; import { getFirstTimeFlowType } from '../../../selectors'; import { MetaMetricsContext } from '../../../contexts/metametrics'; -import { EVENT } from '../../../../shared/constants/metametrics'; +import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics'; export default function CreationSuccessful() { const firstTimeFlowTypeNameMap = { - create: 'New Wallet Created', - import: 'New Wallet Imported', + create: EVENT_NAMES.NEW_WALLET_CREATED, + import: EVENT_NAMES.NEW_WALLET_IMPORTED, }; const history = useHistory(); const t = useI18nContext(); diff --git a/ui/pages/settings/info-tab/info-tab.component.js b/ui/pages/settings/info-tab/info-tab.component.js index 1ee949c87..d842b4f01 100644 --- a/ui/pages/settings/info-tab/info-tab.component.js +++ b/ui/pages/settings/info-tab/info-tab.component.js @@ -1,5 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; + import Button from '../../../components/ui/button'; import { SUPPORT_LINK, @@ -10,6 +11,11 @@ import { getNumberOfSettingsInSection, handleSettingsRefs, } from '../../../helpers/utils/settings-search'; +import { + EVENT, + EVENT_NAMES, + CONTEXT_PROPS, +} from '../../../../shared/constants/metametrics'; export default class InfoTab extends PureComponent { state = { @@ -18,6 +24,7 @@ export default class InfoTab extends PureComponent { static contextTypes = { t: PropTypes.func, + trackEvent: PropTypes.func, }; settingsRefs = Array( @@ -87,6 +94,20 @@ export default class InfoTab extends PureComponent { target="_blank" rel="noopener noreferrer" className="info-tab__link-text" + onClick={() => { + this.context.trackEvent( + { + category: EVENT.CATEGORIES.SETTINGS, + event: EVENT_NAMES.SUPPORT_LINK_CLICKED, + properties: { + url: SUPPORT_LINK, + }, + }, + { + contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE], + }, + ); + }} > {t('supportCenter')} @@ -109,6 +130,20 @@ export default class InfoTab extends PureComponent { target="_blank" rel="noopener noreferrer" className="info-tab__link-text" + onClick={() => { + this.context.trackEvent( + { + category: EVENT.CATEGORIES.SETTINGS, + event: EVENT_NAMES.SUPPORT_LINK_CLICKED, + properties: { + url: SUPPORT_REQUEST_LINK, + }, + }, + { + contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE], + }, + ); + }} > {t('contactUs')} diff --git a/ui/pages/swaps/awaiting-swap/awaiting-swap.js b/ui/pages/swaps/awaiting-swap/awaiting-swap.js index e912b65ae..117151aed 100644 --- a/ui/pages/swaps/awaiting-swap/awaiting-swap.js +++ b/ui/pages/swaps/awaiting-swap/awaiting-swap.js @@ -8,7 +8,11 @@ import { getBlockExplorerLink } from '@metamask/etherscan-link'; import { I18nContext } from '../../../contexts/i18n'; import { SUPPORT_LINK } from '../../../helpers/constants/common'; import { MetaMetricsContext } from '../../../contexts/metametrics'; -import { EVENT } from '../../../../shared/constants/metametrics'; +import { + EVENT, + EVENT_NAMES, + CONTEXT_PROPS, +} from '../../../../shared/constants/metametrics'; import { getCurrentChainId, @@ -156,6 +160,20 @@ export default function AwaitingSwap({ href={SUPPORT_LINK} target="_blank" rel="noopener noreferrer" + onClick={() => { + trackEvent( + { + category: EVENT.CATEGORIES.SWAPS, + event: EVENT_NAMES.SUPPORT_LINK_CLICKED, + properties: { + url: SUPPORT_LINK, + }, + }, + { + contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE], + }, + ); + }} > {new URL(SUPPORT_LINK).hostname} , diff --git a/ui/pages/unlock-page/unlock-page.component.js b/ui/pages/unlock-page/unlock-page.component.js index 55f8571da..0b0f82025 100644 --- a/ui/pages/unlock-page/unlock-page.component.js +++ b/ui/pages/unlock-page/unlock-page.component.js @@ -7,7 +7,11 @@ import TextField from '../../components/ui/text-field'; import Mascot from '../../components/ui/mascot'; import { SUPPORT_LINK } from '../../helpers/constants/common'; import { DEFAULT_ROUTE } from '../../helpers/constants/routes'; -import { EVENT } from '../../../shared/constants/metametrics'; +import { + EVENT, + EVENT_NAMES, + CONTEXT_PROPS, +} from '../../../shared/constants/metametrics'; export default class UnlockPage extends Component { static contextTypes = { @@ -205,6 +209,22 @@ export default class UnlockPage extends Component { target="_blank" rel="noopener noreferrer" key="need-help-link" + onClick={() => { + this.context.trackEvent( + { + category: EVENT.CATEGORIES.NAVIGATION, + event: EVENT_NAMES.SUPPORT_LINK_CLICKED, + properties: { + url: SUPPORT_LINK, + }, + }, + { + contextPropsIntoEventProperties: [ + CONTEXT_PROPS.PAGE_TITLE, + ], + }, + ); + }} > {t('needHelpLinkText')} ,