Add more tracking for MetaMask (#15462)

feature/default_network_editable
Daniel 2 years ago committed by GitHub
parent 86f02e4fd5
commit 2eb0fe6978
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      app/scripts/background.js
  2. 68
      app/scripts/controllers/metametrics.js
  3. 1
      app/scripts/controllers/metametrics.test.js
  4. 1
      app/scripts/metamask-controller.js
  5. 48
      shared/constants/metametrics.js
  6. 18
      ui/components/app/account-menu/account-menu.component.js
  7. 1
      ui/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js
  8. 16
      ui/contexts/metametrics.js
  9. 3
      ui/hooks/useTokensToSearch.js
  10. 20
      ui/pages/error/error.component.js
  11. 20
      ui/pages/first-time-flow/end-of-flow/end-of-flow.component.js
  12. 6
      ui/pages/first-time-flow/end-of-flow/end-of-flow.container.js
  13. 14
      ui/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
  14. 30
      ui/pages/home/beta/beta-home-footer.component.js
  15. 30
      ui/pages/home/flask/flask-home-footer.component.js
  16. 23
      ui/pages/home/home.component.js
  17. 7
      ui/pages/onboarding-flow/creation-successful/creation-successful.js
  18. 35
      ui/pages/settings/info-tab/info-tab.component.js
  19. 20
      ui/pages/swaps/awaiting-swap/awaiting-swap.js
  20. 22
      ui/pages/unlock-page/unlock-page.component.js

@ -22,6 +22,9 @@ import { SECOND } from '../../shared/constants/time';
import { import {
REJECT_NOTFICIATION_CLOSE, REJECT_NOTFICIATION_CLOSE,
REJECT_NOTFICIATION_CLOSE_SIG, REJECT_NOTFICIATION_CLOSE_SIG,
EVENT,
EVENT_NAMES,
TRAITS,
} from '../../shared/constants/metametrics'; } from '../../shared/constants/metametrics';
import { isManifestV3 } from '../../shared/modules/mv3.utils'; import { isManifestV3 } from '../../shared/modules/mv3.utils';
import { maskObject } from '../../shared/modules/object.utils'; import { maskObject } from '../../shared/modules/object.utils';
@ -69,6 +72,7 @@ let notificationIsOpen = false;
let uiIsTriggering = false; let uiIsTriggering = false;
const openMetamaskTabsIDs = {}; const openMetamaskTabsIDs = {};
const requestAccountTabIds = {}; const requestAccountTabIds = {};
let controller;
// state persistence // state persistence
const inTest = process.env.IN_TEST; const inTest = process.env.IN_TEST;
@ -314,7 +318,7 @@ function setupController(initState, initLangCode, remoteSourcePort) {
// MetaMask Controller // MetaMask Controller
// //
const controller = new MetamaskController({ controller = new MetamaskController({
infuraProjectId: process.env.INFURA_PROJECT_ID, infuraProjectId: process.env.INFURA_PROJECT_ID,
// User confirmation callbacks: // User confirmation callbacks:
showUserConfirmation: triggerUi, 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 // On first install, open a new tab with MetaMask
browser.runtime.onInstalled.addListener(({ reason }) => { browser.runtime.onInstalled.addListener(({ reason }) => {
if ( if (
reason === 'install' && reason === 'install' &&
!(process.env.METAMASK_DEBUG || process.env.IN_TEST) !(process.env.METAMASK_DEBUG || process.env.IN_TEST)
) { ) {
addAppInstalledEvent();
platform.openExtensionInBrowser(); platform.openExtensionInBrowser();
} }
}); });

@ -19,6 +19,8 @@ import {
} from '../../../shared/constants/metametrics'; } from '../../../shared/constants/metametrics';
import { SECOND } from '../../../shared/constants/time'; import { SECOND } from '../../../shared/constants/time';
const EXTENSION_UNINSTALL_URL = 'https://metamask.io/uninstalled';
const defaultCaptureException = (err) => { const defaultCaptureException = (err) => {
// throw error on clean stack so its captured by platform integrations (eg sentry) // throw error on clean stack so its captured by platform integrations (eg sentry)
// but does not interrupt the call stack // but does not interrupt the call stack
@ -52,6 +54,9 @@ const exceptionsToFilter = {
* whether or not events are tracked * whether or not events are tracked
* @property {{[string]: MetaMetricsEventFragment}} [fragments] - Object keyed * @property {{[string]: MetaMetricsEventFragment}} [fragments] - Object keyed
* by UUID with stored fragments as values. * 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 { export default class MetaMetricsController {
@ -69,6 +74,7 @@ export default class MetaMetricsController {
* identifier from the network controller * identifier from the network controller
* @param {string} options.version - The version of the extension * @param {string} options.version - The version of the extension
* @param {string} options.environment - The environment the extension is running in * @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 {MetaMetricsControllerState} options.initState - State to initialized with
* @param options.captureException * @param options.captureException
*/ */
@ -81,6 +87,7 @@ export default class MetaMetricsController {
version, version,
environment, environment,
initState, initState,
extension,
captureException = defaultCaptureException, captureException = defaultCaptureException,
}) { }) {
this._captureException = (err) => { this._captureException = (err) => {
@ -96,12 +103,16 @@ export default class MetaMetricsController {
this.locale = prefState.currentLocale.replace('_', '-'); this.locale = prefState.currentLocale.replace('_', '-');
this.version = this.version =
environment === 'production' ? version : `${version}-${environment}`; environment === 'production' ? version : `${version}-${environment}`;
this.extension = extension;
this.environment = environment;
const abandonedFragments = omitBy(initState?.fragments, 'persist'); const abandonedFragments = omitBy(initState?.fragments, 'persist');
this.store = new ObservableStore({ this.store = new ObservableStore({
participateInMetaMetrics: null, participateInMetaMetrics: null,
metaMetricsId: null, metaMetricsId: null,
eventsBeforeMetricsOptIn: [],
traits: {},
...initState, ...initState,
fragments: { fragments: {
...initState?.fragments, ...initState?.fragments,
@ -315,6 +326,24 @@ export default class MetaMetricsController {
this._identify(allValidTraits); 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 * Setter for the `participateInMetaMetrics` property
* *
@ -331,6 +360,12 @@ export default class MetaMetricsController {
metaMetricsId = null; metaMetricsId = null;
} }
this.store.updateState({ participateInMetaMetrics, metaMetricsId }); 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; 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 */ /** PRIVATE METHODS */
/** /**
@ -549,11 +615,13 @@ export default class MetaMetricsController {
* @returns {MetaMetricsTraits | null} traits that have changed since last update * @returns {MetaMetricsTraits | null} traits that have changed since last update
*/ */
_buildUserTraitsObject(metamaskState) { _buildUserTraitsObject(metamaskState) {
const { traits } = this.store.getState();
/** @type {MetaMetricsTraits} */ /** @type {MetaMetricsTraits} */
const currentTraits = { const currentTraits = {
[TRAITS.ADDRESS_BOOK_ENTRIES]: sum( [TRAITS.ADDRESS_BOOK_ENTRIES]: sum(
Object.values(metamaskState.addressBook).map(size), Object.values(metamaskState.addressBook).map(size),
), ),
[TRAITS.INSTALL_DATE_EXT]: traits[TRAITS.INSTALL_DATE_EXT] || '',
[TRAITS.LEDGER_CONNECTION_TYPE]: metamaskState.ledgerTransportType, [TRAITS.LEDGER_CONNECTION_TYPE]: metamaskState.ledgerTransportType,
[TRAITS.NETWORKS_ADDED]: metamaskState.frequentRpcListDetail.map( [TRAITS.NETWORKS_ADDED]: metamaskState.frequentRpcListDetail.map(
(rpc) => rpc.chainId, (rpc) => rpc.chainId,

@ -688,6 +688,7 @@ describe('MetaMetricsController', function () {
assert.deepEqual(traits, { assert.deepEqual(traits, {
[TRAITS.ADDRESS_BOOK_ENTRIES]: 3, [TRAITS.ADDRESS_BOOK_ENTRIES]: 3,
[TRAITS.INSTALL_DATE_EXT]: '',
[TRAITS.LEDGER_CONNECTION_TYPE]: 'web-hid', [TRAITS.LEDGER_CONNECTION_TYPE]: 'web-hid',
[TRAITS.NETWORKS_ADDED]: [MAINNET_CHAIN_ID, ROPSTEN_CHAIN_ID, '0xaf'], [TRAITS.NETWORKS_ADDED]: [MAINNET_CHAIN_ID, ROPSTEN_CHAIN_ID, '0xaf'],
[TRAITS.NETWORKS_WITHOUT_TICKER]: ['0xaf'], [TRAITS.NETWORKS_WITHOUT_TICKER]: ['0xaf'],

@ -394,6 +394,7 @@ export default class MetamaskController extends EventEmitter {
), ),
version: this.platform.getVersion(), version: this.platform.getVersion(),
environment: process.env.METAMASK_ENVIRONMENT, environment: process.env.METAMASK_ENVIRONMENT,
extension: this.extension,
initState: initState.MetaMetricsController, initState: initState.MetaMetricsController,
captureException, captureException,
}); });

@ -183,6 +183,7 @@
* @property {'theme'} THEME - when the user's theme changes we identify the theme trait * @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 * @property {'token_detection_enabled'} TOKEN_DETECTION_ENABLED - when token detection feature is toggled we
* identify the token_detection_enabled trait * 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 = { export const TRAITS = {
ADDRESS_BOOK_ENTRIES: 'address_book_entries', ADDRESS_BOOK_ENTRIES: 'address_book_entries',
INSTALL_DATE_EXT: 'install_date_ext',
LEDGER_CONNECTION_TYPE: 'ledger_connection_type', LEDGER_CONNECTION_TYPE: 'ledger_connection_type',
NETWORKS_ADDED: 'networks_added', NETWORKS_ADDED: 'networks_added',
NETWORKS_WITHOUT_TICKER: 'networks_without_ticker', NETWORKS_WITHOUT_TICKER: 'networks_without_ticker',
@ -201,8 +203,8 @@ export const TRAITS = {
NUMBER_OF_NFTS: 'number_of_nfts', NUMBER_OF_NFTS: 'number_of_nfts',
NUMBER_OF_TOKENS: 'number_of_tokens', NUMBER_OF_TOKENS: 'number_of_tokens',
OPENSEA_API_ENABLED: 'opensea_api_enabled', OPENSEA_API_ENABLED: 'opensea_api_enabled',
THREE_BOX_ENABLED: 'three_box_enabled',
THEME: 'theme', THEME: 'theme',
THREE_BOX_ENABLED: 'three_box_enabled',
TOKEN_DETECTION_ENABLED: 'token_detection_enabled', TOKEN_DETECTION_ENABLED: 'token_detection_enabled',
}; };
@ -276,12 +278,16 @@ export const REJECT_NOTFICIATION_CLOSE_SIG =
*/ */
export const EVENT_NAMES = { export const EVENT_NAMES = {
ENCRYPTION_PUBLIC_KEY_APPROVED: 'Encryption Public Key Approved', APP_INSTALLED: 'App Installed',
ENCRYPTION_PUBLIC_KEY_REJECTED: 'Encryption Public Key Rejected',
ENCRYPTION_PUBLIC_KEY_REQUESTED: 'Encryption Public Key Requested',
DECRYPTION_APPROVED: 'Decryption Approved', DECRYPTION_APPROVED: 'Decryption Approved',
DECRYPTION_REJECTED: 'Decryption Rejected', DECRYPTION_REJECTED: 'Decryption Rejected',
DECRYPTION_REQUESTED: 'Decryption Requested', 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_APPROVED: 'Permissions Approved',
PERMISSIONS_REJECTED: 'Permissions Rejected', PERMISSIONS_REJECTED: 'Permissions Rejected',
PERMISSIONS_REQUESTED: 'Permissions Requested', PERMISSIONS_REQUESTED: 'Permissions Requested',
@ -289,10 +295,10 @@ export const EVENT_NAMES = {
SIGNATURE_APPROVED: 'Signature Approved', SIGNATURE_APPROVED: 'Signature Approved',
SIGNATURE_REJECTED: 'Signature Rejected', SIGNATURE_REJECTED: 'Signature Rejected',
SIGNATURE_REQUESTED: 'Signature Requested', SIGNATURE_REQUESTED: 'Signature Requested',
SUPPORT_LINK_CLICKED: 'Support Link Clicked',
TOKEN_ADDED: 'Token Added', TOKEN_ADDED: 'Token Added',
TOKEN_DETECTED: 'Token Detected', TOKEN_DETECTED: 'Token Detected',
TOKEN_HIDDEN: 'Token Hidden', TOKEN_HIDDEN: 'Token Hidden',
NFT_ADDED: 'NFT Added',
TOKEN_IMPORT_CANCELED: 'Token Import Canceled', TOKEN_IMPORT_CANCELED: 'Token Import Canceled',
TOKEN_IMPORT_CLICKED: 'Token Import Clicked', TOKEN_IMPORT_CLICKED: 'Token Import Clicked',
}; };
@ -300,8 +306,12 @@ export const EVENT_NAMES = {
export const EVENT = { export const EVENT = {
CATEGORIES: { CATEGORIES: {
ACCOUNTS: 'Accounts', ACCOUNTS: 'Accounts',
APP: 'App',
AUTH: 'Auth', AUTH: 'Auth',
BACKGROUND: 'Background', BACKGROUND: 'Background',
ERROR: 'Error',
FOOTER: 'Footer',
HOME: 'Home',
INPAGE_PROVIDER: 'inpage_provider', INPAGE_PROVIDER: 'inpage_provider',
MESSAGES: 'Messages', MESSAGES: 'Messages',
NAVIGATION: 'Navigation', NAVIGATION: 'Navigation',
@ -314,29 +324,35 @@ export const EVENT = {
TRANSACTIONS: 'Transactions', TRANSACTIONS: 'Transactions',
WALLET: 'Wallet', WALLET: 'Wallet',
}, },
LOCATION: {
TOKEN_DETAILS: 'token_details',
TOKEN_DETECTION: 'token_detection',
TOKEN_MENU: 'token_menu',
},
SOURCE: { SOURCE: {
NETWORK: { NETWORK: {
POPULAR_NETWORK_LIST: 'popular_network_list',
CUSTOM_NETWORK_FORM: 'custom_network_form', CUSTOM_NETWORK_FORM: 'custom_network_form',
POPULAR_NETWORK_LIST: 'popular_network_list',
}, },
SWAPS: { SWAPS: {
MAIN_VIEW: 'Main View', MAIN_VIEW: 'Main View',
TOKEN_VIEW: 'Token View', TOKEN_VIEW: 'Token View',
}, },
TRANSACTION: {
USER: 'user',
DAPP: 'dapp',
},
TOKEN: { TOKEN: {
CUSTOM: 'custom', CUSTOM: 'custom',
DETECTED: 'detected',
DAPP: 'dapp', DAPP: 'dapp',
DETECTED: 'detected',
LIST: 'list', LIST: 'list',
}, },
TRANSACTION: {
DAPP: 'dapp',
USER: 'user',
},
}, },
LOCATION: { };
TOKEN_DETECTION: 'token_detection',
TOKEN_MENU: 'token_menu', // Values below (e.g. 'location') can be used in the "properties"
TOKEN_DETAILS: 'token_details', // tracking object as keys, e.g. { location: 'Home' }
}, export const CONTEXT_PROPS = {
PAGE_TITLE: 'location',
}; };

@ -5,7 +5,11 @@ import Fuse from 'fuse.js';
import InputAdornment from '@material-ui/core/InputAdornment'; import InputAdornment from '@material-ui/core/InputAdornment';
import classnames from 'classnames'; import classnames from 'classnames';
import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; 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 { getEnvironmentType } from '../../../../app/scripts/lib/util';
import Identicon from '../../ui/identicon'; import Identicon from '../../ui/identicon';
import SiteIcon from '../../ui/site-icon'; import SiteIcon from '../../ui/site-icon';
@ -442,6 +446,18 @@ export default class AccountMenu extends Component {
} }
<AccountMenuItem <AccountMenuItem
onClick={() => { onClick={() => {
trackEvent(
{
category: EVENT.CATEGORIES.NAVIGATION,
event: EVENT_NAMES.SUPPORT_LINK_CLICKED,
properties: {
url: supportLink,
},
},
{
contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE],
},
);
global.platform.openTab({ url: supportLink }); global.platform.openTab({ url: supportLink });
}} }}
icon={ icon={

@ -1,5 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import MetaFoxLogo from '../../../ui/metafox-logo'; import MetaFoxLogo from '../../../ui/metafox-logo';
import PageContainerFooter from '../../../ui/page-container/page-container-footer'; import PageContainerFooter from '../../../ui/page-container/page-container-footer';
import { EVENT } from '../../../../../shared/constants/metametrics'; import { EVENT } from '../../../../../shared/constants/metametrics';

@ -17,6 +17,7 @@ import { captureException, captureMessage } from '@sentry/browser';
import { omit } from 'lodash'; import { omit } from 'lodash';
import { getEnvironmentType } from '../../app/scripts/lib/util'; import { getEnvironmentType } from '../../app/scripts/lib/util';
import { PATH_NAME_MAP } from '../helpers/constants/routes'; import { PATH_NAME_MAP } from '../helpers/constants/routes';
import { CONTEXT_PROPS } from '../../shared/constants/metametrics';
import { useSegmentContext } from '../hooks/useSegmentContext'; import { useSegmentContext } from '../hooks/useSegmentContext';
import { trackMetaMetricsEvent, trackMetaMetricsPage } from '../store/actions'; import { trackMetaMetricsEvent, trackMetaMetricsPage } from '../store/actions';
@ -57,11 +58,26 @@ export function MetaMetricsProvider({ children }) {
const location = useLocation(); const location = useLocation();
const context = useSegmentContext(); 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} * @type {UITrackEventMethod}
*/ */
const trackEvent = useCallback( const trackEvent = useCallback(
(payload, options) => { (payload, options) => {
addContextPropsIntoEventProperties(payload, options);
trackMetaMetricsEvent( trackMetaMetricsEvent(
{ {
...payload, ...payload,

@ -30,7 +30,8 @@ export function getRenderableTokenData(
if (isSwapsDefaultTokenSymbol(symbol, chainId)) { if (isSwapsDefaultTokenSymbol(symbol, chainId)) {
contractExchangeRate = 1; contractExchangeRate = 1;
} else if (string && conversionRate > 0) { } 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)]; contractExchangeRate = contractExchangeRates[toChecksumHexAddress(address)];
} }
const formattedFiat = const formattedFiat =

@ -3,10 +3,16 @@ import PropTypes from 'prop-types';
import { getEnvironmentType } from '../../../app/scripts/lib/util'; import { getEnvironmentType } from '../../../app/scripts/lib/util';
import { ENVIRONMENT_TYPE_POPUP } from '../../../shared/constants/app'; import { ENVIRONMENT_TYPE_POPUP } from '../../../shared/constants/app';
import { SUPPORT_REQUEST_LINK } from '../../helpers/constants/common'; import { SUPPORT_REQUEST_LINK } from '../../helpers/constants/common';
import {
EVENT,
EVENT_NAMES,
CONTEXT_PROPS,
} from '../../../shared/constants/metametrics';
class ErrorPage extends PureComponent { class ErrorPage extends PureComponent {
static contextTypes = { static contextTypes = {
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
trackEvent: PropTypes.func,
}; };
static propTypes = { static propTypes = {
@ -41,6 +47,20 @@ class ErrorPage extends PureComponent {
key="metamaskSupportLink" key="metamaskSupportLink"
rel="noopener noreferrer" rel="noopener noreferrer"
href={SUPPORT_REQUEST_LINK} 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],
},
);
}}
> >
<span className="error-page__link-text">{this.context.t('here')}</span> <span className="error-page__link-text">{this.context.t('here')}</span>
</a> </a>

@ -6,7 +6,11 @@ import MetaFoxLogo from '../../../components/ui/metafox-logo';
import { SUPPORT_REQUEST_LINK } from '../../../helpers/constants/common'; import { SUPPORT_REQUEST_LINK } from '../../../helpers/constants/common';
import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'; import { DEFAULT_ROUTE } from '../../../helpers/constants/routes';
import { returnToOnboardingInitiatorTab } from '../onboarding-initiator-util'; 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 { export default class EndOfFlowScreen extends PureComponent {
static contextTypes = { static contextTypes = {
@ -99,6 +103,20 @@ export default class EndOfFlowScreen extends PureComponent {
key="metamaskSupportLink" key="metamaskSupportLink"
rel="noopener noreferrer" rel="noopener noreferrer"
href={SUPPORT_REQUEST_LINK} 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],
},
);
}}
> >
<span className="first-time-flow__link-text"> <span className="first-time-flow__link-text">
{this.context.t('here')} {this.context.t('here')}

@ -1,11 +1,13 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getOnboardingInitiator } from '../../../selectors'; import { getOnboardingInitiator } from '../../../selectors';
import { setCompletedOnboarding } from '../../../store/actions'; import { setCompletedOnboarding } from '../../../store/actions';
import { EVENT_NAMES } from '../../../../shared/constants/metametrics';
import EndOfFlow from './end-of-flow.component'; import EndOfFlow from './end-of-flow.component';
const firstTimeFlowTypeNameMap = { const firstTimeFlowTypeNameMap = {
create: 'New Wallet Created', create: EVENT_NAMES.NEW_WALLET_CREATED,
import: 'New Wallet Imported', import: EVENT_NAMES.NEW_WALLET_IMPORTED,
}; };
const mapStateToProps = (state) => { const mapStateToProps = (state) => {

@ -10,7 +10,10 @@ import {
DEFAULT_ROUTE, DEFAULT_ROUTE,
INITIALIZE_SEED_PHRASE_INTRO_ROUTE, INITIALIZE_SEED_PHRASE_INTRO_ROUTE,
} from '../../../../helpers/constants/routes'; } 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 { returnToOnboardingInitiatorTab } from '../../onboarding-initiator-util';
import { exportAsFile } from '../../../../../shared/modules/export-utils'; import { exportAsFile } from '../../../../../shared/modules/export-utils';
@ -78,6 +81,15 @@ export default class RevealSeedPhrase extends PureComponent {
await Promise.all([setCompletedOnboarding(), setSeedPhraseBackedUp(false)]); 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) { if (onboardingInitiator) {
await returnToOnboardingInitiatorTab(onboardingInitiator); await returnToOnboardingInitiatorTab(onboardingInitiator);
} }

@ -1,13 +1,39 @@
import React from 'react'; import React, { useContext } from 'react';
import { SUPPORT_REQUEST_LINK } from '../../../helpers/constants/common'; import { SUPPORT_REQUEST_LINK } from '../../../helpers/constants/common';
import { useI18nContext } from '../../../hooks/useI18nContext'; import { useI18nContext } from '../../../hooks/useI18nContext';
import {
EVENT,
EVENT_NAMES,
CONTEXT_PROPS,
} from '../../../../shared/constants/metametrics';
import { MetaMetricsContext } from '../../../contexts/metametrics';
const BetaHomeFooter = () => { const BetaHomeFooter = () => {
const t = useI18nContext(); const t = useI18nContext();
const trackEvent = useContext(MetaMetricsContext);
return ( return (
<> <>
<a href={SUPPORT_REQUEST_LINK} target="_blank" rel="noopener noreferrer"> <a
target="_blank"
rel="noopener noreferrer"
href={SUPPORT_REQUEST_LINK}
onClick={() => {
trackEvent(
{
category: EVENT.CATEGORIES.FOOTER,
event: EVENT_NAMES.SUPPORT_LINK_CLICKED,
properties: {
url: SUPPORT_REQUEST_LINK,
},
},
{
contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE],
},
);
}}
>
{t('needHelpSubmitTicket')} {t('needHelpSubmitTicket')}
</a>{' '} </a>{' '}
|{' '} |{' '}

@ -1,13 +1,39 @@
import React from 'react'; import React, { useContext } from 'react';
import { SUPPORT_REQUEST_LINK } from '../../../helpers/constants/common'; import { SUPPORT_REQUEST_LINK } from '../../../helpers/constants/common';
import { useI18nContext } from '../../../hooks/useI18nContext'; import { useI18nContext } from '../../../hooks/useI18nContext';
import {
EVENT,
EVENT_NAMES,
CONTEXT_PROPS,
} from '../../../../shared/constants/metametrics';
import { MetaMetricsContext } from '../../../contexts/metametrics';
const FlaskHomeFooter = () => { const FlaskHomeFooter = () => {
const t = useI18nContext(); const t = useI18nContext();
const trackEvent = useContext(MetaMetricsContext);
return ( return (
<> <>
<a href={SUPPORT_REQUEST_LINK} target="_blank" rel="noopener noreferrer"> <a
target="_blank"
rel="noopener noreferrer"
href={SUPPORT_REQUEST_LINK}
onClick={() => {
trackEvent(
{
category: EVENT.CATEGORIES.FOOTER,
event: EVENT_NAMES.SUPPORT_LINK_CLICKED,
properties: {
url: SUPPORT_REQUEST_LINK,
},
},
{
contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE],
},
);
}}
>
{t('needHelpSubmitTicket')} {t('needHelpSubmitTicket')}
</a>{' '} </a>{' '}
|{' '} |{' '}

@ -3,6 +3,11 @@ import PropTypes from 'prop-types';
import { Redirect, Route } from 'react-router-dom'; import { Redirect, Route } from 'react-router-dom';
///: BEGIN:ONLY_INCLUDE_IN(main) ///: BEGIN:ONLY_INCLUDE_IN(main)
import { SUPPORT_LINK } from '../../helpers/constants/common'; import { SUPPORT_LINK } from '../../helpers/constants/common';
import {
EVENT,
EVENT_NAMES,
CONTEXT_PROPS,
} from '../../../shared/constants/metametrics';
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
import { formatDate } from '../../helpers/utils/util'; import { formatDate } from '../../helpers/utils/util';
import AssetList from '../../components/app/asset-list'; import AssetList from '../../components/app/asset-list';
@ -73,6 +78,7 @@ function shouldCloseNotificationPopup({
export default class Home extends PureComponent { export default class Home extends PureComponent {
static contextTypes = { static contextTypes = {
t: PropTypes.func, t: PropTypes.func,
trackEvent: PropTypes.func,
}; };
static propTypes = { static propTypes = {
@ -608,6 +614,7 @@ export default class Home extends PureComponent {
!completedOnboarding) && !completedOnboarding) &&
announcementsToShow && announcementsToShow &&
showWhatsNewPopup; showWhatsNewPopup;
return ( return (
<div className="main-container"> <div className="main-container">
<Route path={CONNECTED_ROUTE} component={ConnectedSites} exact /> <Route path={CONNECTED_ROUTE} component={ConnectedSites} exact />
@ -681,6 +688,22 @@ export default class Home extends PureComponent {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
key="need-help-link" 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')} {t('needHelpLinkText')}
</a>, </a>,

@ -1,6 +1,7 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import Box from '../../../components/ui/box'; import Box from '../../../components/ui/box';
import Typography from '../../../components/ui/typography'; import Typography from '../../../components/ui/typography';
import Button from '../../../components/ui/button'; import Button from '../../../components/ui/button';
@ -18,12 +19,12 @@ import {
import { setCompletedOnboarding } from '../../../store/actions'; import { setCompletedOnboarding } from '../../../store/actions';
import { getFirstTimeFlowType } from '../../../selectors'; import { getFirstTimeFlowType } from '../../../selectors';
import { MetaMetricsContext } from '../../../contexts/metametrics'; import { MetaMetricsContext } from '../../../contexts/metametrics';
import { EVENT } from '../../../../shared/constants/metametrics'; import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
export default function CreationSuccessful() { export default function CreationSuccessful() {
const firstTimeFlowTypeNameMap = { const firstTimeFlowTypeNameMap = {
create: 'New Wallet Created', create: EVENT_NAMES.NEW_WALLET_CREATED,
import: 'New Wallet Imported', import: EVENT_NAMES.NEW_WALLET_IMPORTED,
}; };
const history = useHistory(); const history = useHistory();
const t = useI18nContext(); const t = useI18nContext();

@ -1,5 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Button from '../../../components/ui/button'; import Button from '../../../components/ui/button';
import { import {
SUPPORT_LINK, SUPPORT_LINK,
@ -10,6 +11,11 @@ import {
getNumberOfSettingsInSection, getNumberOfSettingsInSection,
handleSettingsRefs, handleSettingsRefs,
} from '../../../helpers/utils/settings-search'; } from '../../../helpers/utils/settings-search';
import {
EVENT,
EVENT_NAMES,
CONTEXT_PROPS,
} from '../../../../shared/constants/metametrics';
export default class InfoTab extends PureComponent { export default class InfoTab extends PureComponent {
state = { state = {
@ -18,6 +24,7 @@ export default class InfoTab extends PureComponent {
static contextTypes = { static contextTypes = {
t: PropTypes.func, t: PropTypes.func,
trackEvent: PropTypes.func,
}; };
settingsRefs = Array( settingsRefs = Array(
@ -87,6 +94,20 @@ export default class InfoTab extends PureComponent {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="info-tab__link-text" 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')} {t('supportCenter')}
</Button> </Button>
@ -109,6 +130,20 @@ export default class InfoTab extends PureComponent {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="info-tab__link-text" 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')} {t('contactUs')}
</Button> </Button>

@ -8,7 +8,11 @@ import { getBlockExplorerLink } from '@metamask/etherscan-link';
import { I18nContext } from '../../../contexts/i18n'; import { I18nContext } from '../../../contexts/i18n';
import { SUPPORT_LINK } from '../../../helpers/constants/common'; import { SUPPORT_LINK } from '../../../helpers/constants/common';
import { MetaMetricsContext } from '../../../contexts/metametrics'; import { MetaMetricsContext } from '../../../contexts/metametrics';
import { EVENT } from '../../../../shared/constants/metametrics'; import {
EVENT,
EVENT_NAMES,
CONTEXT_PROPS,
} from '../../../../shared/constants/metametrics';
import { import {
getCurrentChainId, getCurrentChainId,
@ -156,6 +160,20 @@ export default function AwaitingSwap({
href={SUPPORT_LINK} href={SUPPORT_LINK}
target="_blank" target="_blank"
rel="noopener noreferrer" 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} {new URL(SUPPORT_LINK).hostname}
</a>, </a>,

@ -7,7 +7,11 @@ import TextField from '../../components/ui/text-field';
import Mascot from '../../components/ui/mascot'; import Mascot from '../../components/ui/mascot';
import { SUPPORT_LINK } from '../../helpers/constants/common'; import { SUPPORT_LINK } from '../../helpers/constants/common';
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'; 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 { export default class UnlockPage extends Component {
static contextTypes = { static contextTypes = {
@ -205,6 +209,22 @@ export default class UnlockPage extends Component {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
key="need-help-link" 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')} {t('needHelpLinkText')}
</a>, </a>,

Loading…
Cancel
Save