diff --git a/app/actions/onboarding/index.ts b/app/actions/onboarding/index.ts index 641bf5568bd..8b1e2c3e6e0 100644 --- a/app/actions/onboarding/index.ts +++ b/app/actions/onboarding/index.ts @@ -1,11 +1,11 @@ -import { IMetaMetricsEvent } from '../../core/Analytics'; +import { ITrackingEvent } from '../../core/Analytics/MetaMetrics.types'; export const SAVE_EVENT = 'SAVE_EVENT'; export const CLEAR_EVENTS = 'CLEAR_EVENTS'; interface SaveEventAction { type: typeof SAVE_EVENT; - event: [IMetaMetricsEvent]; + event: [ITrackingEvent]; } interface ClearEventsAction { @@ -15,7 +15,7 @@ interface ClearEventsAction { export type OnboardingActionTypes = SaveEventAction | ClearEventsAction; export function saveOnboardingEvent( - eventArgs: [IMetaMetricsEvent], + eventArgs: [ITrackingEvent], ): SaveEventAction { return { type: SAVE_EVENT, diff --git a/app/component-library/components/Navigation/TabBar/TabBar.tsx b/app/component-library/components/Navigation/TabBar/TabBar.tsx index 9ec1e39f408..cd0eee06dd6 100644 --- a/app/component-library/components/Navigation/TabBar/TabBar.tsx +++ b/app/component-library/components/Navigation/TabBar/TabBar.tsx @@ -25,7 +25,7 @@ import OnboardingWizard from '../../../../components/UI/OnboardingWizard'; const TabBar = ({ state, descriptors, navigation }: TabBarProps) => { const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { bottom: bottomInset } = useSafeAreaInsets(); const { styles } = useStyles(styleSheet, { bottomInset }); const chainId = useSelector(selectChainId); @@ -73,10 +73,14 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => { navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.MODAL.WALLET_ACTIONS, }); - trackEvent(MetaMetricsEvents.ACTIONS_BUTTON_CLICKED, { - text: '', - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ACTIONS_BUTTON_CLICKED) + .addProperties({ + text: '', + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); break; case Routes.BROWSER_VIEW: navigation.navigate(Routes.BROWSER.HOME, { @@ -124,7 +128,15 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => { /> ); }, - [state, descriptors, navigation, colors, chainId, trackEvent], + [ + state, + descriptors, + navigation, + colors, + chainId, + trackEvent, + createEventBuilder, + ], ); const renderTabBarItems = useCallback( diff --git a/app/components/Approvals/PermissionApproval/PermissionApproval.test.tsx b/app/components/Approvals/PermissionApproval/PermissionApproval.test.tsx index 147155f87f3..6f545020d47 100644 --- a/app/components/Approvals/PermissionApproval/PermissionApproval.test.tsx +++ b/app/components/Approvals/PermissionApproval/PermissionApproval.test.tsx @@ -9,6 +9,7 @@ import { MetaMetricsEvents } from '../../../core/Analytics'; import { backgroundState } from '../../../util/test/initial-root-state'; import { render } from '@testing-library/react-native'; import { useMetrics } from '../../../components/hooks/useMetrics'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; jest.mock('../../Views/confirmations/hooks/useApprovalRequest'); jest.mock('../../../components/hooks/useMetrics'); @@ -68,22 +69,23 @@ const mockSelectorState = (state: any) => { const mockTrackEvent = jest.fn(); +(useMetrics as jest.MockedFn).mockReturnValue({ + trackEvent: mockTrackEvent, + createEventBuilder: MetricsEventBuilder.createEventBuilder, + enable: jest.fn(), + addTraitsToUser: jest.fn(), + createDataDeletionTask: jest.fn(), + checkDataDeleteStatus: jest.fn(), + getDeleteRegulationCreationDate: jest.fn(), + getDeleteRegulationId: jest.fn(), + isDataRecorded: jest.fn(), + isEnabled: jest.fn(), + getMetaMetricsId: jest.fn(), +}); + describe('PermissionApproval', () => { beforeEach(() => { - jest.resetAllMocks(); - (useMetrics as jest.MockedFn).mockReturnValue({ - trackEvent: mockTrackEvent, - createEventBuilder: jest.fn(), - enable: jest.fn(), - addTraitsToUser: jest.fn(), - createDataDeletionTask: jest.fn(), - checkDataDeleteStatus: jest.fn(), - getDeleteRegulationCreationDate: jest.fn(), - getDeleteRegulationId: jest.fn(), - isDataRecorded: jest.fn(), - isEnabled: jest.fn(), - getMetaMetricsId: jest.fn(), - }); + jest.clearAllMocks(); }); it('navigates', async () => { @@ -143,14 +145,17 @@ describe('PermissionApproval', () => { render(); - expect(mockTrackEvent).toHaveBeenCalledTimes(1); - expect(mockTrackEvent).toHaveBeenCalledWith( + const expectedEvent = MetricsEventBuilder.createEventBuilder( MetaMetricsEvents.CONNECT_REQUEST_STARTED, - { + ) + .addProperties({ number_of_accounts: 3, source: 'PERMISSION SYSTEM', - }, - ); + }) + .build(); + + expect(mockTrackEvent).toHaveBeenCalledTimes(1); + expect(mockTrackEvent).toHaveBeenCalledWith(expectedEvent); }); it('does not navigate if no approval request', async () => { diff --git a/app/components/Approvals/PermissionApproval/PermissionApproval.tsx b/app/components/Approvals/PermissionApproval/PermissionApproval.tsx index a63b45b0076..8e48c24364c 100644 --- a/app/components/Approvals/PermissionApproval/PermissionApproval.tsx +++ b/app/components/Approvals/PermissionApproval/PermissionApproval.tsx @@ -15,7 +15,7 @@ export interface PermissionApprovalProps { } const PermissionApproval = (props: PermissionApprovalProps) => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { approvalRequest } = useApprovalRequest(); const totalAccounts = useSelector(selectAccountsLength); const isProcessing = useRef(false); @@ -38,10 +38,14 @@ const PermissionApproval = (props: PermissionApprovalProps) => { isProcessing.current = true; - trackEvent(MetaMetricsEvents.CONNECT_REQUEST_STARTED, { - number_of_accounts: totalAccounts, - source: 'PERMISSION SYSTEM', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.CONNECT_REQUEST_STARTED) + .addProperties({ + number_of_accounts: totalAccounts, + source: 'PERMISSION SYSTEM', + }) + .build(), + ); props.navigation.navigate( ...createAccountConnectNavDetails({ @@ -49,7 +53,13 @@ const PermissionApproval = (props: PermissionApprovalProps) => { permissionRequestId: id, }), ); - }, [approvalRequest, totalAccounts, props.navigation, trackEvent]); + }, [ + approvalRequest, + totalAccounts, + props.navigation, + trackEvent, + createEventBuilder, + ]); return null; }; diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index e884d1664a1..18d7361ff51 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -400,7 +400,7 @@ const SettingsFlow = () => ( ); const HomeTabs = () => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const drawerRef = useRef(null); const [isKeyboardHidden, setIsKeyboardHidden] = useState(true); @@ -439,10 +439,14 @@ const HomeTabs = () => { home: { tabBarIconKey: TabBarIconKey.Wallet, callback: () => { - trackEvent(MetaMetricsEvents.WALLET_OPENED, { - number_of_accounts: accountsLength, - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.WALLET_OPENED) + .addProperties({ + number_of_accounts: accountsLength, + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); }, rootScreenName: Routes.WALLET_VIEW, }, @@ -453,27 +457,39 @@ const HomeTabs = () => { browser: { tabBarIconKey: TabBarIconKey.Browser, callback: () => { - trackEvent(MetaMetricsEvents.BROWSER_OPENED, { - number_of_accounts: accountsLength, - chain_id: getDecimalChainId(chainId), - source: 'Navigation Tab', - active_connected_dapp: activeConnectedDapp, - number_of_open_tabs: amountOfBrowserOpenTabs, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.BROWSER_OPENED) + .addProperties({ + number_of_accounts: accountsLength, + chain_id: getDecimalChainId(chainId), + source: 'Navigation Tab', + active_connected_dapp: activeConnectedDapp, + number_of_open_tabs: amountOfBrowserOpenTabs, + }) + .build(), + ); }, rootScreenName: Routes.BROWSER_VIEW, }, activity: { tabBarIconKey: TabBarIconKey.Activity, callback: () => { - trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_TRANSACTION_HISTORY); + trackEvent( + createEventBuilder( + MetaMetricsEvents.NAVIGATION_TAPS_TRANSACTION_HISTORY, + ).build(), + ); }, rootScreenName: Routes.TRANSACTIONS_VIEW, }, settings: { tabBarIconKey: TabBarIconKey.Setting, callback: () => { - trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_SETTINGS); + trackEvent( + createEventBuilder( + MetaMetricsEvents.NAVIGATION_TAPS_SETTINGS, + ).build(), + ); }, rootScreenName: Routes.SETTINGS_VIEW, unmountOnBlur: true, diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js index 086d3c3a61f..45f8d7dac6a 100644 --- a/app/components/Nav/Main/RootRPCMethodsUI.js +++ b/app/components/Nav/Main/RootRPCMethodsUI.js @@ -128,7 +128,7 @@ export const useSwapConfirmedEvent = ({ trackSwaps }) => { }; const RootRPCMethodsUI = (props) => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const [transactionModalType, setTransactionModalType] = useState(undefined); const tokenList = useSelector(selectTokenList); const setTransactionObject = props.setTransactionObject; @@ -241,15 +241,28 @@ const RootRPCMethodsUI = (props) => { Logger.log('Swaps', 'Sending metrics event', event); - trackEvent(event, { sensitiveProperties: { ...parameters } }); + trackEvent( + createEventBuilder(event) + .addSensitiveProperties({ ...parameters }) + .build(), + ); } catch (e) { Logger.error(e, MetaMetricsEvents.SWAP_TRACKING_FAILED); - trackEvent(MetaMetricsEvents.SWAP_TRACKING_FAILED, { - error: e, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.SWAP_TRACKING_FAILED) + .addProperties({ + error: e, + }) + .build(), + ); } }, - [props.selectedAddress, props.shouldUseSmartTransaction, trackEvent], + [ + props.selectedAddress, + props.shouldUseSmartTransaction, + trackEvent, + createEventBuilder, + ], ); const { addTransactionMetaIdForListening } = useSwapConfirmedEvent({ @@ -323,7 +336,11 @@ const RootRPCMethodsUI = (props) => { ); Logger.error(error, 'error while trying to send transaction (Main)'); } else { - trackEvent(MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED); + trackEvent( + createEventBuilder( + MetaMetricsEvents.QR_HARDWARE_TRANSACTION_CANCELED, + ).build(), + ); } } }, @@ -333,6 +350,7 @@ const RootRPCMethodsUI = (props) => { trackEvent, swapsTransactions, addTransactionMetaIdForListening, + createEventBuilder, ], ); diff --git a/app/components/UI/AccountApproval/index.js b/app/components/UI/AccountApproval/index.js index a486c783226..bce1c643fa3 100644 --- a/app/components/UI/AccountApproval/index.js +++ b/app/components/UI/AccountApproval/index.js @@ -1,10 +1,6 @@ import PropTypes from 'prop-types'; import React, { PureComponent } from 'react'; -import { - InteractionManager, - TouchableOpacity, - View, -} from 'react-native'; +import { InteractionManager, TouchableOpacity, View } from 'react-native'; import { connect } from 'react-redux'; import { strings } from '../../../../locales/i18n'; import Text from '../../../component-library/components/Texts/Text'; @@ -38,7 +34,8 @@ import { getDecimalChainId } from '../../../util/networks'; import { ThemeContext, mockTheme } from '../../../util/theme'; import ShowWarningBanner from './showWarningBanner'; import createStyles from './styles'; -import { SourceType } from '../../../components/hooks/useMetrics/useMetrics.types'; +import { SourceType } from '../../hooks/useMetrics/useMetrics.types'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; /** * Account access approval component @@ -165,8 +162,11 @@ class AccountApproval extends PureComponent { this.checkUrlFlaggedAsPhishing(hostname); this.props.metrics.trackEvent( - MetaMetricsEvents.CONNECT_REQUEST_STARTED, - this.getAnalyticsParams(), + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.CONNECT_REQUEST_STARTED, + ) + .addProperties(this.getAnalyticsParams()) + .build(), ); }; @@ -202,8 +202,11 @@ class AccountApproval extends PureComponent { this.props.onCancel(); this.props.metrics.trackEvent( - MetaMetricsEvents.CONNECT_REQUEST_OTPFAILURE, - this.getAnalyticsParams(), + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.CONNECT_REQUEST_OTPFAILURE, + ) + .addProperties(this.getAnalyticsParams()) + .build(), ); // Navigate to feedback modal @@ -223,8 +226,11 @@ class AccountApproval extends PureComponent { this.props.onConfirm(); this.props.metrics.trackEvent( - MetaMetricsEvents.CONNECT_REQUEST_COMPLETED, - this.getAnalyticsParams(), + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.CONNECT_REQUEST_COMPLETED, + ) + .addProperties(this.getAnalyticsParams()) + .build(), ); this.showWalletConnectNotification(true); }; @@ -234,10 +240,12 @@ class AccountApproval extends PureComponent { */ onCancel = () => { this.props.metrics.trackEvent( - MetaMetricsEvents.CONNECT_REQUEST_CANCELLED, - this.getAnalyticsParams(), + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.CONNECT_REQUEST_CANCELLED, + ) + .addProperties(this.getAnalyticsParams()) + .build(), ); - if (this.props.currentPageInformation.channelId) { SDKConnect.getInstance().removeChannel( this.props.currentPageInformation.channelId, diff --git a/app/components/UI/AccountApproval/showWarningBanner.tsx b/app/components/UI/AccountApproval/showWarningBanner.tsx index 439fc7326d6..ce3a7b54420 100644 --- a/app/components/UI/AccountApproval/showWarningBanner.tsx +++ b/app/components/UI/AccountApproval/showWarningBanner.tsx @@ -18,7 +18,8 @@ import { } from '../../../component-library/components/Icons/Icon'; import { CONNECTING_TO_A_DECEPTIVE_SITE } from '../../../constants/urls'; import { AccordionHeaderHorizontalAlignment } from '../../../component-library/components/Accordions/Accordion'; -import { MetaMetrics } from '../../../core/Analytics'; +import { MetaMetrics, MetaMetricsEvents } from '../../../core/Analytics'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; const descriptionArray = [ strings('accounts.fake_metamask'), @@ -29,12 +30,15 @@ const descriptionArray = [ const goToLearnMore = () => { Linking.openURL(CONNECTING_TO_A_DECEPTIVE_SITE); MetaMetrics.getInstance().trackEvent( - { category: 'EXTERNAL_LINK_CLICKED' }, - { - location: 'dapp_connection_request', - text: 'Learn More', - url_domain: CONNECTING_TO_A_DECEPTIVE_SITE, - }, + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.EXTERNAL_LINK_CLICKED, + ) + .addProperties({ + location: 'dapp_connection_request', + text: 'Learn More', + url_domain: CONNECTING_TO_A_DECEPTIVE_SITE, + }) + .build(), ); }; diff --git a/app/components/UI/AccountOverview/index.js b/app/components/UI/AccountOverview/index.js index 1a73bb38746..c812e65eee2 100644 --- a/app/components/UI/AccountOverview/index.js +++ b/app/components/UI/AccountOverview/index.js @@ -289,7 +289,11 @@ class AccountOverview extends PureComponent { }); setTimeout(() => this.props.protectWalletModalVisible(), 2000); - this.props.metrics.trackEvent(MetaMetricsEvents.WALLET_COPIED_ADDRESS); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.WALLET_COPIED_ADDRESS) + .build(), + ); }; doENSLookup = async () => { @@ -322,9 +326,12 @@ class AccountOverview extends PureComponent { screen: Routes.BROWSER.VIEW, params, }); - this.props.metrics.trackEvent(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED, { - portfolioUrl: AppConstants.PORTFOLIO.URL, - }); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED) + .addProperties({ portfolioUrl: AppConstants.PORTFOLIO.URL }) + .build(), + ); }; render() { diff --git a/app/components/UI/AccountRightButton/index.tsx b/app/components/UI/AccountRightButton/index.tsx index 4b21c8a6702..c50304d9dfc 100644 --- a/app/components/UI/AccountRightButton/index.tsx +++ b/app/components/UI/AccountRightButton/index.tsx @@ -59,7 +59,7 @@ const AccountRightButton = ({ // Placeholder ref for dismissing keyboard. Works when the focused input is within a Webview. const placeholderInputRef = useRef(null); const { navigate } = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const [isKeyboardVisible, setIsKeyboardVisible] = useState(false); // TODO: Replace "any" with type @@ -118,9 +118,13 @@ const AccountRightButton = ({ navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.NETWORK_SELECTOR, }); - trackEvent(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, { - chain_id: getDecimalChainId(providerConfig.chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED) + .addProperties({ + chain_id: getDecimalChainId(providerConfig.chainId), + }) + .build(), + ); } else { onPress?.(); } @@ -132,6 +136,7 @@ const AccountRightButton = ({ navigate, providerConfig.chainId, trackEvent, + createEventBuilder, ]); const route = useRoute, string>>(); diff --git a/app/components/UI/AddCustomCollectible/index.tsx b/app/components/UI/AddCustomCollectible/index.tsx index be364abd6c8..9e06e1b86ca 100644 --- a/app/components/UI/AddCustomCollectible/index.tsx +++ b/app/components/UI/AddCustomCollectible/index.tsx @@ -1,12 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; -import { - Alert, - Text, - TextInput, - View, - StyleSheet, -} from 'react-native'; +import { Alert, Text, TextInput, View, StyleSheet } from 'react-native'; import { fontStyles } from '../../../styles/common'; import Engine from '../../../core/Engine'; import { strings } from '../../../../locales/i18n'; @@ -86,7 +80,7 @@ const AddCustomCollectible = ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any const assetTokenIdInput = React.createRef() as any; const { colors, themeAppearance } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); const selectedAddress = useSelector( @@ -201,7 +195,11 @@ const AddCustomCollectible = ({ const { NftController } = Engine.context as any; NftController.addNft(address, tokenId); - trackEvent(MetaMetricsEvents.COLLECTIBLE_ADDED, getAnalyticsParams()); + trackEvent( + createEventBuilder(MetaMetricsEvents.COLLECTIBLE_ADDED) + .addProperties(getAnalyticsParams()) + .build(), + ); setLoading(false); navigation.goBack(); }; @@ -223,10 +221,7 @@ const AddCustomCollectible = ({ }; return ( - + { const { styles } = useStyles(styleSheet, {}); const dispatch = useDispatch(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const handleShowAlert = (config: { isVisible: boolean; @@ -59,7 +59,9 @@ const AddressCopy = () => { }); setTimeout(() => handleProtectWalletModalVisible(), 2000); - trackEvent(MetaMetricsEvents.WALLET_COPIED_ADDRESS); + trackEvent( + createEventBuilder(MetaMetricsEvents.WALLET_COPIED_ADDRESS).build(), + ); }; return ( diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index 995d61584c0..b4c230beb92 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -79,7 +79,7 @@ const AssetOverview: React.FC = ({ const selectedAddress = useSelector( selectSelectedInternalAccountChecksummedAddress, ); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const tokenExchangeRates = useSelector(selectContractExchangeRates); const tokenBalances = useSelector(selectContractBalances); const chainId = useSelector((state: RootState) => selectChainId(state)); @@ -138,21 +138,29 @@ const AssetOverview: React.FC = ({ sourcePage: 'MainView', }, }); - trackEvent(MetaMetricsEvents.SWAP_BUTTON_CLICKED, { - text: 'Swap', - tokenSymbol: '', - location: 'TokenDetails', - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.SWAP_BUTTON_CLICKED) + .addProperties({ + text: 'Swap', + tokenSymbol: '', + location: 'TokenDetails', + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); }; const onBuy = () => { const [route, params] = createBuyNavigationDetails(); navigation.navigate(route, params || {}); - trackEvent(MetaMetricsEvents.BUY_BUTTON_CLICKED, { - text: 'Buy', - location: 'TokenDetails', - chain_id_destination: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.BUY_BUTTON_CLICKED) + .addProperties({ + text: 'Buy', + location: 'TokenDetails', + chain_id_destination: getDecimalChainId(chainId), + }) + .build(), + ); }; const goToBrowserUrl = (url: string) => { diff --git a/app/components/UI/BackupAlert/BackupAlert.tsx b/app/components/UI/BackupAlert/BackupAlert.tsx index 2e804b39993..d3d0175adc8 100644 --- a/app/components/UI/BackupAlert/BackupAlert.tsx +++ b/app/components/UI/BackupAlert/BackupAlert.tsx @@ -41,7 +41,7 @@ const BLOCKED_LIST = [ const BackupAlert = ({ navigation, onDismiss }: BackupAlertI) => { const { styles } = useStyles(styleSheet, {}); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const [inBrowserView, setInBrowserView] = useState(false); const [inBlockedView, setInBlockedView] = useState(false); const [isVisible, setIsVisible] = useState(true); @@ -77,19 +77,27 @@ const BackupAlert = ({ navigation, onDismiss }: BackupAlertI) => { screen: 'AccountBackupStep1', }); - trackEvent(MetaMetricsEvents.WALLET_SECURITY_PROTECT_ENGAGED, { - wallet_protection_required: false, - source: 'Backup Alert', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.WALLET_SECURITY_PROTECT_ENGAGED) + .addProperties({ + wallet_protection_required: false, + source: 'Backup Alert', + }) + .build(), + ); }; const onDismissAlert = () => { dispatch(backUpSeedphraseAlertNotVisible()); - trackEvent(MetaMetricsEvents.WALLET_SECURITY_PROTECT_DISMISSED, { - wallet_protection_required: false, - source: 'Backup Alert', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.WALLET_SECURITY_PROTECT_DISMISSED) + .addProperties({ + wallet_protection_required: false, + source: 'Backup Alert', + }) + .build(), + ); if (onDismiss) onDismiss(); }; diff --git a/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx b/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx index c7181038c83..df79dac4e0e 100644 --- a/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx +++ b/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx @@ -44,7 +44,7 @@ interface Props { } const BasicFunctionalityModal = ({ route }: Props) => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { colors } = useTheme(); const styles = createStyles(colors); const bottomSheetRef = useRef(null); @@ -65,27 +65,34 @@ const BasicFunctionalityModal = ({ route }: Props) => { if (permission !== 'authorized') { return; } - enableNotifications(); + enableNotifications(); }, [enableNotifications]); const closeBottomSheet = async () => { bottomSheetRef.current?.onCloseBottomSheet(() => { dispatch(toggleBasicFunctionality(!isEnabled)); trackEvent( - !isEnabled - ? MetaMetricsEvents.BASIC_FUNCTIONALITY_ENABLED - : MetaMetricsEvents.BASIC_FUNCTIONALITY_DISABLED, + createEventBuilder( + !isEnabled + ? MetaMetricsEvents.BASIC_FUNCTIONALITY_ENABLED + : MetaMetricsEvents.BASIC_FUNCTIONALITY_DISABLED, + ).build(), + ); + trackEvent( + createEventBuilder(MetaMetricsEvents.SETTINGS_UPDATED) + .addProperties({ + settings_group: 'security_privacy', + settings_type: 'basic_functionality', + old_value: isEnabled, + new_value: !isEnabled, + was_notifications_on: isEnabled + ? isNotificationsFeatureEnabled + : false, + was_profile_syncing_on: isEnabled ? isProfileSyncingEnabled : false, + }) + .build(), ); - trackEvent(MetaMetricsEvents.SETTINGS_UPDATED, { - settings_group: 'security_privacy', - settings_type: 'basic_functionality', - old_value: isEnabled, - new_value: !isEnabled, - was_notifications_on: isEnabled ? isNotificationsFeatureEnabled : false, - was_profile_syncing_on: isEnabled ? isProfileSyncingEnabled : false, - }); }); - if ( route.params.caller === Routes.SETTINGS.NOTIFICATIONS || route.params.caller === Routes.NOTIFICATIONS.OPT_IN diff --git a/app/components/UI/Bridge/utils/useGoToBridge.ts b/app/components/UI/Bridge/utils/useGoToBridge.ts index 0019ea22ca1..cc1d030b40b 100644 --- a/app/components/UI/Bridge/utils/useGoToBridge.ts +++ b/app/components/UI/Bridge/utils/useGoToBridge.ts @@ -23,7 +23,7 @@ export default function useGoToBridge(location: string) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const browserTabs = useSelector((state: any) => state.browser.tabs); const { navigate } = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); return (address?: string) => { const existingBridgeTab = browserTabs.find((tab: BrowserTab) => isBridgeUrl(tab.url), @@ -48,11 +48,15 @@ export default function useGoToBridge(location: string) { screen: Routes.BROWSER.VIEW, params, }); - trackEvent(MetaMetricsEvents.BRIDGE_LINK_CLICKED, { - bridgeUrl: AppConstants.BRIDGE.URL, - location, - chain_id_source: getDecimalChainId(chainId), - token_address_source: address, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.BRIDGE_LINK_CLICKED) + .addProperties({ + bridgeUrl: AppConstants.BRIDGE.URL, + location, + chain_id_source: getDecimalChainId(chainId), + token_address_source: address, + }) + .build(), + ); }; } diff --git a/app/components/UI/BrowserBottomBar/index.js b/app/components/UI/BrowserBottomBar/index.js index 875bf6f93ac..d105fb8b26e 100644 --- a/app/components/UI/BrowserBottomBar/index.js +++ b/app/components/UI/BrowserBottomBar/index.js @@ -100,17 +100,27 @@ class BrowserBottomBar extends PureComponent { }; trackSearchEvent = () => { - this.props.metrics.trackEvent(MetaMetricsEvents.BROWSER_SEARCH_USED, { - option_chosen: 'Browser Bottom Bar Menu', - number_of_tabs: undefined, - }); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.BROWSER_SEARCH_USED) + .addProperties({ + option_chosen: 'Browser Bottom Bar Menu', + number_of_tabs: undefined, + }) + .build(), + ); }; trackNavigationEvent = (navigationOption) => { - this.props.metrics.trackEvent(MetaMetricsEvents.BROWSER_NAVIGATION, { - option_chosen: navigationOption, - os: Platform.OS, - }); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.BROWSER_NAVIGATION) + .addProperties({ + option_chosen: navigationOption, + os: Platform.OS, + }) + .build(), + ); }; render() { diff --git a/app/components/UI/CollectibleContractElement/index.js b/app/components/UI/CollectibleContractElement/index.js index 742d69dcdcd..eec785149e6 100644 --- a/app/components/UI/CollectibleContractElement/index.js +++ b/app/components/UI/CollectibleContractElement/index.js @@ -106,7 +106,7 @@ function CollectibleContractElement({ const longPressedCollectible = useRef(null); const { colors, themeAppearance, brandColors } = useTheme(); const styles = createStyles(colors, brandColors); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const toggleCollectibles = useCallback(() => { setCollectiblesVisible(!collectiblesVisible); @@ -138,9 +138,13 @@ function CollectibleContractElement({ longPressedCollectible.current.address, longPressedCollectible.current.tokenId, ); - trackEvent(MetaMetricsEvents.COLLECTIBLE_REMOVED, { - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.COLLECTIBLE_REMOVED) + .addProperties({ + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); Alert.alert( strings('wallet.collectible_removed_title'), strings('wallet.collectible_removed_desc'), diff --git a/app/components/UI/CollectibleContracts/index.js b/app/components/UI/CollectibleContracts/index.js index 52ccc725c63..47b315967dc 100644 --- a/app/components/UI/CollectibleContracts/index.js +++ b/app/components/UI/CollectibleContracts/index.js @@ -116,7 +116,7 @@ const CollectibleContracts = ({ (singleCollectible) => singleCollectible.isCurrentlyOwned === true, ); const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); const [isAddNFTEnabled, setIsAddNFTEnabled] = useState(true); const [refreshing, setRefreshing] = useState(false); @@ -218,9 +218,11 @@ const CollectibleContracts = ({ const goToAddCollectible = useCallback(() => { setIsAddNFTEnabled(false); navigation.push('AddAsset', { assetType: 'collectible' }); - trackEvent(MetaMetricsEvents.WALLET_ADD_COLLECTIBLES); + trackEvent( + createEventBuilder(MetaMetricsEvents.WALLET_ADD_COLLECTIBLES).build(), + ); setIsAddNFTEnabled(true); - }, [navigation, trackEvent]); + }, [navigation, trackEvent, createEventBuilder]); const renderFooter = useCallback( () => ( diff --git a/app/components/UI/CollectibleModal/CollectibleModal.tsx b/app/components/UI/CollectibleModal/CollectibleModal.tsx index 118aa84f13f..df135232abc 100644 --- a/app/components/UI/CollectibleModal/CollectibleModal.tsx +++ b/app/components/UI/CollectibleModal/CollectibleModal.tsx @@ -33,7 +33,7 @@ import { EXTERNAL_LINK_TYPE } from '../../../constants/browser'; const CollectibleModal = () => { const navigation = useNavigation(); const dispatch = useDispatch(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const chainId = useSelector(selectChainId); const { contractName, collectible } = useParams(); @@ -68,9 +68,11 @@ const CollectibleModal = () => { }, [handleUpdateCollectible]); useEffect(() => { - trackEvent(MetaMetricsEvents.COLLECTIBLE_DETAILS_OPENED, { - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.COLLECTIBLE_DETAILS_OPENED) + .addProperties({ chain_id: getDecimalChainId(chainId) }) + .build(), + ); // The linter wants `trackEvent` to be added as a dependency, // But the event fires twice if I do that. // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/app/components/UI/DeleteWalletModal/index.tsx b/app/components/UI/DeleteWalletModal/index.tsx index 843fad7db96..fb12f474eca 100644 --- a/app/components/UI/DeleteWalletModal/index.tsx +++ b/app/components/UI/DeleteWalletModal/index.tsx @@ -38,7 +38,7 @@ if (Device.isAndroid() && UIManager.setLayoutAnimationEnabledExperimental) { const DeleteWalletModal = () => { const navigation = useNavigation(); const { colors, themeAppearance } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); const modalRef = useRef(null); @@ -94,7 +94,11 @@ const DeleteWalletModal = () => { triggerClose(); await resetWalletState(); await deleteUser(); - trackEvent(MetaMetricsEvents.DELETE_WALLET_MODAL_WALLET_DELETED, {}); + trackEvent( + createEventBuilder( + MetaMetricsEvents.DELETE_WALLET_MODAL_WALLET_DELETED, + ).build(), + ); InteractionManager.runAfterInteractions(() => { navigateOnboardingRoot(); }); diff --git a/app/components/UI/DeprecatedNetworkModal/DeprecatedNetworkModal.tsx b/app/components/UI/DeprecatedNetworkModal/DeprecatedNetworkModal.tsx index e1917cade59..ee5a2b11058 100644 --- a/app/components/UI/DeprecatedNetworkModal/DeprecatedNetworkModal.tsx +++ b/app/components/UI/DeprecatedNetworkModal/DeprecatedNetworkModal.tsx @@ -19,7 +19,7 @@ import { MetaMetricsEvents } from '../../../core/Analytics'; const DeprecatedNetworkModal = () => { const { styles } = useStyles(styleSheet, {}); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const navigation = useNavigation(); const dismissModal = (): void => { @@ -28,11 +28,15 @@ const DeprecatedNetworkModal = () => { const goToLearnMore = () => { Linking.openURL(CONNECTING_TO_DEPRECATED_NETWORK); - trackEvent(MetaMetricsEvents.EXTERNAL_LINK_CLICKED, { - location: 'dapp_connection_request', - text: 'Learn More', - url_domain: CONNECTING_TO_DEPRECATED_NETWORK, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.EXTERNAL_LINK_CLICKED) + .addProperties({ + location: 'dapp_connection_request', + text: 'Learn More', + url_domain: CONNECTING_TO_DEPRECATED_NETWORK, + }) + .build(), + ); }; const sheetRef = useRef(null); diff --git a/app/components/UI/DrawerView/index.js b/app/components/UI/DrawerView/index.js index d3aea2d1ba5..618451f6626 100644 --- a/app/components/UI/DrawerView/index.js +++ b/app/components/UI/DrawerView/index.js @@ -539,11 +539,15 @@ class DrawerView extends PureComponent { this.setState({ showProtectWalletModal: true }); this.props.metrics.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_PROTECT_VIEWED, - { - wallet_protection_required: false, - source: 'Backup Alert', - }, + this.props.metrics + .createEventBuilder( + MetaMetricsEvents.WALLET_SECURITY_PROTECT_VIEWED, + ) + .addProperties({ + wallet_protection_required: false, + source: 'Backup Alert', + }) + .build(), ); } else { // eslint-disable-next-line react/no-did-update-set-state @@ -602,20 +606,25 @@ class DrawerView extends PureComponent { onSelectAccount: this.hideDrawer, }), ); - this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_ACCOUNT_NAME); - }; - - trackEvent = (event) => { - this.props.metrics.trackEvent(event); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.NAVIGATION_TAPS_ACCOUNT_NAME) + .build(), + ); }; // NOTE: do we need this event? trackOpenBrowserEvent = () => { const { providerConfig } = this.props; - this.props.metrics.trackEvent(MetaMetricsEvents.BROWSER_OPENED, { - source: 'In-app Navigation', - chain_id: getDecimalChainId(providerConfig.chainId), - }); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.BROWSER_OPENED) + .addProperties({ + source: 'In-app Navigation', + chain_id: getDecimalChainId(providerConfig.chainId), + }) + .build(), + ); }; onReceive = () => { @@ -623,14 +632,22 @@ class DrawerView extends PureComponent { initialScreen: QRTabSwitcherScreens.Receive, disableTabber: true, }); - this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_RECEIVE); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.NAVIGATION_TAPS_RECEIVE) + .build(), + ); }; onSend = async () => { this.props.newAssetTransaction(getEther(this.props.ticker)); this.props.navigation.navigate('SendFlowView'); this.hideDrawer(); - this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_SEND); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.NAVIGATION_TAPS_SEND) + .build(), + ); }; goToBrowser = () => { @@ -638,13 +655,21 @@ class DrawerView extends PureComponent { this.hideDrawer(); // Q: duplicated analytic event? this.trackOpenBrowserEvent(); - this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_BROWSER); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.NAVIGATION_TAPS_BROWSER) + .build(), + ); }; showWallet = () => { this.props.navigation.navigate('WalletTabHome'); this.hideDrawer(); - this.trackEvent(MetaMetricsEvents.WALLET_OPENED); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.WALLET_OPENED) + .build(), + ); }; onPressLock = async () => { @@ -677,7 +702,11 @@ class DrawerView extends PureComponent { ], { cancelable: false }, ); - this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_LOGOUT); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.NAVIGATION_TAPS_LOGOUT) + .build(), + ); }; viewInEtherscan = () => { @@ -701,11 +730,19 @@ class DrawerView extends PureComponent { ); this.goToBrowserUrl(url, etherscan_url); } - this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_VIEW_ETHERSCAN); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.NAVIGATION_TAPS_VIEW_ETHERSCAN) + .build(), + ); }; submitFeedback = () => { - this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_SEND_FEEDBACK); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.NAVIGATION_TAPS_SEND_FEEDBACK) + .build(), + ); this.goToBrowserUrl( 'https://community.metamask.io/c/feature-requests-ideas/', strings('drawer.request_feature'), @@ -720,7 +757,11 @@ class DrawerView extends PureComponent { timestamp: Date.now(), }, }); - this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_GET_HELP); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.NAVIGATION_TAPS_GET_HELP) + .build(), + ); this.hideDrawer(); }; @@ -900,7 +941,13 @@ class DrawerView extends PureComponent { .catch((err) => { Logger.log('Error while trying to share address', err); }); - this.trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_SHARE_PUBLIC_ADDRESS); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder( + MetaMetricsEvents.NAVIGATION_TAPS_SHARE_PUBLIC_ADDRESS, + ) + .build(), + ); }; onSecureWalletModalAction = () => { @@ -911,11 +958,13 @@ class DrawerView extends PureComponent { ); InteractionManager.runAfterInteractions(() => { this.props.metrics.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_PROTECT_ENGAGED, - { - wallet_protection_required: true, - source: 'Modal', - }, + this.props.metrics + .createEventBuilder(MetaMetricsEvents.WALLET_SECURITY_PROTECT_ENGAGED) + .addProperties({ + wallet_protection_required: true, + source: 'Modal', + }) + .build(), ); }); }; diff --git a/app/components/UI/EditGasFee1559/index.js b/app/components/UI/EditGasFee1559/index.js index d5789a6151e..f74bbab44d4 100644 --- a/app/components/UI/EditGasFee1559/index.js +++ b/app/components/UI/EditGasFee1559/index.js @@ -198,7 +198,7 @@ const EditGasFee1559 = ({ hideTimeEstimateInfoModal, ] = useModalHandler(false); const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); @@ -219,8 +219,9 @@ const EditGasFee1559 = ({ const toggleAdvancedOptions = () => { if (!showAdvancedOptions) { trackEvent( - MetaMetricsEvents.GAS_ADVANCED_OPTIONS_CLICKED, - getAnalyticsParams(), + createEventBuilder(MetaMetricsEvents.GAS_ADVANCED_OPTIONS_CLICKED) + .addProperties(getAnalyticsParams()) + .build(), ); } setShowAdvancedOptions((showAdvancedOptions) => !showAdvancedOptions); @@ -231,7 +232,11 @@ const EditGasFee1559 = ({ }; const save = () => { - trackEvent(MetaMetricsEvents.GAS_FEE_CHANGED, getAnalyticsParams()); + trackEvent( + createEventBuilder(MetaMetricsEvents.GAS_FEE_CHANGED) + .addProperties(getAnalyticsParams()) + .build(), + ); onSave(selectedOption); }; diff --git a/app/components/UI/EditGasFeeLegacy/index.js b/app/components/UI/EditGasFeeLegacy/index.js index 34681b6326b..dac72d02942 100644 --- a/app/components/UI/EditGasFeeLegacy/index.js +++ b/app/components/UI/EditGasFeeLegacy/index.js @@ -148,7 +148,7 @@ const EditGasFeeLegacy = ({ const [selectedOption, setSelectedOption] = useState(selected); const [gasPriceError, setGasPriceError] = useState(); const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); const getAnalyticsParams = () => { @@ -168,15 +168,20 @@ const EditGasFeeLegacy = ({ const toggleAdvancedOptions = () => { if (!showAdvancedOptions) { trackEvent( - MetaMetricsEvents.GAS_ADVANCED_OPTIONS_CLICKED, - getAnalyticsParams(), + createEventBuilder(MetaMetricsEvents.GAS_ADVANCED_OPTIONS_CLICKED) + .addProperties(getAnalyticsParams()) + .build(), ); } setShowAdvancedOptions((showAdvancedOptions) => !showAdvancedOptions); }; const save = () => { - trackEvent(MetaMetricsEvents.GAS_FEE_CHANGED, getAnalyticsParams()); + trackEvent( + createEventBuilder(MetaMetricsEvents.GAS_FEE_CHANGED) + .addProperties(getAnalyticsParams()) + .build(), + ); onSave(selectedOption); }; diff --git a/app/components/UI/EnableAutomaticSecurityChecksModal/EnableAutomaticSecurityChecksModal.tsx b/app/components/UI/EnableAutomaticSecurityChecksModal/EnableAutomaticSecurityChecksModal.tsx index e729f72f7e1..a0123631d78 100644 --- a/app/components/UI/EnableAutomaticSecurityChecksModal/EnableAutomaticSecurityChecksModal.tsx +++ b/app/components/UI/EnableAutomaticSecurityChecksModal/EnableAutomaticSecurityChecksModal.tsx @@ -38,7 +38,7 @@ export const createEnableAutomaticSecurityChecksModalNavDetails = const EnableAutomaticSecurityChecksModal = () => { const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); const modalRef = useRef(null); const dispatch = useDispatch(); @@ -47,10 +47,14 @@ const EnableAutomaticSecurityChecksModal = () => { modalRef?.current?.dismissModal(cb); useEffect(() => { - trackEvent(MetaMetricsEvents.AUTOMATIC_SECURITY_CHECKS_PROMPT_VIEWED, { - ...generateDeviceAnalyticsMetaData(), - }); - }, [trackEvent]); + trackEvent( + createEventBuilder( + MetaMetricsEvents.AUTOMATIC_SECURITY_CHECKS_PROMPT_VIEWED, + ) + .addProperties(generateDeviceAnalyticsMetaData()) + .build(), + ); + }, [trackEvent, createEventBuilder]); useEffect(() => { dispatch(setAutomaticSecurityChecksModalOpen(true)); @@ -63,24 +67,30 @@ const EnableAutomaticSecurityChecksModal = () => { () => dismissModal(() => { trackEvent( - MetaMetricsEvents.AUTOMATIC_SECURITY_CHECKS_DISABLED_FROM_PROMPT, - { ...generateDeviceAnalyticsMetaData() }, + createEventBuilder( + MetaMetricsEvents.AUTOMATIC_SECURITY_CHECKS_DISABLED_FROM_PROMPT, + ) + .addProperties(generateDeviceAnalyticsMetaData()) + .build(), ); dispatch(userSelectedAutomaticSecurityChecksOptions()); }), - [dispatch, trackEvent], + [dispatch, trackEvent, createEventBuilder], ); const enableAutomaticSecurityChecks = useCallback(() => { dismissModal(() => { trackEvent( - MetaMetricsEvents.AUTOMATIC_SECURITY_CHECKS_ENABLED_FROM_PROMPT, - { ...generateDeviceAnalyticsMetaData() }, + createEventBuilder( + MetaMetricsEvents.AUTOMATIC_SECURITY_CHECKS_ENABLED_FROM_PROMPT, + ) + .addProperties(generateDeviceAnalyticsMetaData()) + .build(), ); dispatch(userSelectedAutomaticSecurityChecksOptions()); dispatch(setAutomaticSecurityChecks(true)); }); - }, [dispatch, trackEvent]); + }, [dispatch, trackEvent, createEventBuilder]); return ( diff --git a/app/components/UI/Identicon/index.tsx b/app/components/UI/Identicon/index.tsx index d4dfffd2d17..da74af1f3f3 100644 --- a/app/components/UI/Identicon/index.tsx +++ b/app/components/UI/Identicon/index.tsx @@ -47,7 +47,8 @@ const Identicon: React.FC = ({ const { colors } = useTheme(); // eslint-disable-next-line @typescript-eslint/no-explicit-any - const useBlockieIcon = useSelector((state: RootState) => state.settings.useBlockieIcon) ?? true; + const useBlockieIcon = + useSelector((state: RootState) => state.settings.useBlockieIcon) ?? true; if (!address && !imageUri) return null; diff --git a/app/components/UI/LedgerModals/LedgerConfirmationModal.test.tsx b/app/components/UI/LedgerModals/LedgerConfirmationModal.test.tsx index 165c4032f0f..9fc0190d1f6 100644 --- a/app/components/UI/LedgerModals/LedgerConfirmationModal.test.tsx +++ b/app/components/UI/LedgerModals/LedgerConfirmationModal.test.tsx @@ -20,6 +20,7 @@ import { useMetrics } from '../../hooks/useMetrics'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { fireEvent } from '@testing-library/react-native'; import { HardwareDeviceTypes } from '../../../constants/keyringTypes'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; jest.mock('../../hooks/Ledger/useBluetooth', () => ({ __esModule: true, @@ -43,9 +44,9 @@ jest.mock('@react-navigation/native', () => ({ }), })); -jest.mock('../../../components/hooks/useMetrics', () => ({ - useMetrics: jest.fn(), -})); +jest.mock('../../../components/hooks/useMetrics'); + +const mockTrackEvent = jest.fn(); describe('LedgerConfirmationModal', () => { beforeEach(() => { @@ -70,8 +71,18 @@ describe('LedgerConfirmationModal', () => { error: null, }); - (useMetrics as jest.Mock).mockReturnValue({ - trackEvent: jest.fn(), + (useMetrics as jest.MockedFn).mockReturnValue({ + trackEvent: mockTrackEvent, + createEventBuilder: MetricsEventBuilder.createEventBuilder, + enable: jest.fn(), + addTraitsToUser: jest.fn(), + createDataDeletionTask: jest.fn(), + checkDataDeleteStatus: jest.fn(), + getDeleteRegulationCreationDate: jest.fn(), + getDeleteRegulationId: jest.fn(), + isDataRecorded: jest.fn(), + isEnabled: jest.fn(), + getMetaMetricsId: jest.fn(), }); }); @@ -378,11 +389,6 @@ describe('LedgerConfirmationModal', () => { throw new Error('error'); }); - const trackEvent = jest.fn(); - (useMetrics as jest.Mock).mockReturnValue({ - trackEvent, - }); - renderWithProvider( { expect(onConfirmation).not.toHaveBeenCalled(); - expect(trackEvent).toHaveBeenNthCalledWith( + expect(mockTrackEvent).toHaveBeenNthCalledWith( 1, - MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, - { - device_type: HardwareDeviceTypes.LEDGER, - error: 'LEDGER_ETH_APP_NOT_INSTALLED', - }, + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, + ) + .addProperties({ + device_type: HardwareDeviceTypes.LEDGER, + error: 'LEDGER_ETH_APP_NOT_INSTALLED', + }) + .build(), ); }); diff --git a/app/components/UI/LedgerModals/LedgerConfirmationModal.tsx b/app/components/UI/LedgerModals/LedgerConfirmationModal.tsx index b35967f8c40..33b7f55414b 100644 --- a/app/components/UI/LedgerModals/LedgerConfirmationModal.tsx +++ b/app/components/UI/LedgerModals/LedgerConfirmationModal.tsx @@ -46,7 +46,7 @@ const LedgerConfirmationModal = ({ }: LedgerConfirmationModalProps) => { const { colors } = useAppThemeFromContext() || mockTheme; const styles = useMemo(() => createStyles(colors), [colors]); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const [delayClose, setDelayClose] = useState(false); const [completeClose, setCompleteClose] = useState(false); const [permissionErrorShown, setPermissionErrorShown] = useState(false); @@ -78,10 +78,14 @@ const LedgerConfirmationModal = ({ } catch (_e) { // Handle a super edge case of the user starting a transaction with the device connected // After arriving to confirmation the ETH app is not installed anymore this causes a crash. - trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { - device_type: HardwareDeviceTypes.LEDGER, - error: 'LEDGER_ETH_APP_NOT_INSTALLED', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR) + .addProperties({ + device_type: HardwareDeviceTypes.LEDGER, + error: 'LEDGER_ETH_APP_NOT_INSTALLED', + }) + .build(), + ); } }; @@ -90,9 +94,15 @@ const LedgerConfirmationModal = ({ try { onRejection(); } finally { - trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_TRANSACTION_CANCELLED, { - device_type: HardwareDeviceTypes.LEDGER, - }); + trackEvent( + createEventBuilder( + MetaMetricsEvents.LEDGER_HARDWARE_TRANSACTION_CANCELLED, + ) + .addProperties({ + device_type: HardwareDeviceTypes.LEDGER, + }) + .build(), + ); } }; @@ -198,10 +208,14 @@ const LedgerConfirmationModal = ({ break; } if (ledgerError !== LedgerCommunicationErrors.UserRefusedConfirmation) { - trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { - device_type: HardwareDeviceTypes.LEDGER, - error: `${ledgerError}`, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR) + .addProperties({ + device_type: HardwareDeviceTypes.LEDGER, + error: `${ledgerError}`, + }) + .build(), + ); } } @@ -227,10 +241,14 @@ const LedgerConfirmationModal = ({ break; } setPermissionErrorShown(true); - trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { - device_type: HardwareDeviceTypes.LEDGER, - error: 'LEDGER_BLUETOOTH_PERMISSION_ERR', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR) + .addProperties({ + device_type: HardwareDeviceTypes.LEDGER, + error: 'LEDGER_BLUETOOTH_PERMISSION_ERR', + }) + .build(), + ); } if (bluetoothConnectionError) { @@ -238,10 +256,15 @@ const LedgerConfirmationModal = ({ title: strings('ledger.bluetooth_off'), subtitle: strings('ledger.bluetooth_off_message'), }); - trackEvent(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR, { - device_type: HardwareDeviceTypes.LEDGER, - error: 'LEDGER_BLUETOOTH_CONNECTION_ERR', - }); + + trackEvent( + createEventBuilder(MetaMetricsEvents.LEDGER_HARDWARE_WALLET_ERROR) + .addProperties({ + device_type: HardwareDeviceTypes.LEDGER, + error: 'LEDGER_BLUETOOTH_CONNECTION_ERR', + }) + .build(), + ); } if ( diff --git a/app/components/UI/ManageNetworks/ManageNetworks.tsx b/app/components/UI/ManageNetworks/ManageNetworks.tsx index 56fed1d3a22..97a721291fa 100644 --- a/app/components/UI/ManageNetworks/ManageNetworks.tsx +++ b/app/components/UI/ManageNetworks/ManageNetworks.tsx @@ -27,7 +27,7 @@ import styles from './ManageNetworks.styles'; export default function ManageNetworksComponent() { const providerConfig: ProviderConfig = useSelector(selectProviderConfig); const navigation = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const networkImageSource = useSelector(selectNetworkImageSource); const networkName = useSelector(selectNetworkName); @@ -37,10 +37,14 @@ export default function ManageNetworksComponent() { screen: Routes.SHEET.NETWORK_SELECTOR, }); - trackEvent(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, { - chain_id: getDecimalChainId(providerConfig.chainId), - }); - }, [navigation, trackEvent, providerConfig]); + trackEvent( + createEventBuilder(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED) + .addProperties({ + chain_id: getDecimalChainId(providerConfig.chainId), + }) + .build(), + ); + }, [navigation, trackEvent, providerConfig, createEventBuilder]); const handleLink = () => { Linking.openURL(AppConstants.URLS.PRIVACY_POLICY_2024); diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js index e4c1412a02d..3302133e331 100644 --- a/app/components/UI/Navbar/index.js +++ b/app/components/UI/Navbar/index.js @@ -58,9 +58,10 @@ import AddressCopy from '../AddressCopy'; import PickerAccount from '../../../component-library/components/Pickers/PickerAccount'; import { createAccountSelectorNavDetails } from '../../../components/Views/AccountSelector'; import { RequestPaymentViewSelectors } from '../../../../e2e/selectors/Receive/RequestPaymentView.selectors'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; -const trackEvent = (event, params = {}) => { - MetaMetrics.getInstance().trackEvent(event, params); +const trackEvent = (event) => { + MetaMetrics.getInstance().trackEvent(event); }; const styles = StyleSheet.create({ @@ -218,7 +219,10 @@ export function getNavigationOptionsTitle( }); function navigationPop() { - if (navigationPopEvent) trackEvent(navigationPopEvent); + if (navigationPopEvent) + trackEvent( + MetricsEventBuilder.createEventBuilder(navigationPopEvent).build(), + ); navigation.goBack(); } @@ -555,11 +559,15 @@ export function getSendFlowTitle( const providerType = route?.params?.providerType ?? ''; const additionalTransactionMetricsParams = getBlockaidTransactionMetricsParams(transaction); - trackEvent(MetaMetricsEvents.SEND_FLOW_CANCEL, { - view: title.split('.')[1], - network: providerType, - ...additionalTransactionMetricsParams, - }); + trackEvent( + MetricsEventBuilder.createEventBuilder(MetaMetricsEvents.SEND_FLOW_CANCEL) + .addProperties({ + view: title.split('.')[1], + network: providerType, + ...additionalTransactionMetricsParams, + }) + .build(), + ); resetTransaction(); navigation.dangerouslyGetParent()?.pop(); }; @@ -1003,22 +1011,38 @@ export function getWalletNavbarOptions( navigation.navigate(Routes.QR_TAB_SWITCHER, { onScanSuccess, }); - trackEvent(MetaMetricsEvents.WALLET_QR_SCANNER); + trackEvent( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.WALLET_QR_SCANNER, + ).build(), + ); } function handleNotificationOnPress() { if (isNotificationEnabled && isNotificationsFeatureEnabled()) { navigation.navigate(Routes.NOTIFICATIONS.VIEW); - trackEvent(MetaMetricsEvents.NOTIFICATIONS_MENU_OPENED, { - unread_count: unreadNotificationCount, - read_count: readNotificationCount, - }); + trackEvent( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.NOTIFICATIONS_MENU_OPENED, + ) + .addProperties({ + unread_count: unreadNotificationCount, + read_count: readNotificationCount, + }) + .build(), + ); } else { navigation.navigate(Routes.NOTIFICATIONS.OPT_IN_STACK); - trackEvent(MetaMetricsEvents.NOTIFICATIONS_ACTIVATED, { - action_type: 'started', - is_profile_syncing_enabled: isProfileSyncingEnabled, - }); + trackEvent( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.NOTIFICATIONS_ACTIVATED, + ) + .addProperties({ + action_type: 'started', + is_profile_syncing_enabled: isProfileSyncingEnabled, + }) + .build(), + ); } } @@ -1676,10 +1700,16 @@ export function getSwapsQuotesNavbar(navigation, route, themeColors) { const selectedQuote = route.params?.selectedQuote; const quoteBegin = route.params?.quoteBegin; if (!selectedQuote) { - trackEvent(MetaMetricsEvents.QUOTES_REQUEST_CANCELLED, { - ...trade, - responseTime: new Date().getTime() - quoteBegin, - }); + trackEvent( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.QUOTES_REQUEST_CANCELLED, + ) + .addProperties({ + ...trade, + responseTime: new Date().getTime() - quoteBegin, + }) + .build(), + ); } navigation.pop(); }; @@ -1689,10 +1719,16 @@ export function getSwapsQuotesNavbar(navigation, route, themeColors) { const selectedQuote = route.params?.selectedQuote; const quoteBegin = route.params?.quoteBegin; if (!selectedQuote) { - trackEvent(MetaMetricsEvents.QUOTES_REQUEST_CANCELLED, { - ...trade, - responseTime: new Date().getTime() - quoteBegin, - }); + trackEvent( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.QUOTES_REQUEST_CANCELLED, + ) + .addProperties({ + ...trade, + responseTime: new Date().getTime() - quoteBegin, + }) + .build(), + ); } navigation.dangerouslyGetParent()?.pop(); }; diff --git a/app/components/UI/NavbarTitle/index.js b/app/components/UI/NavbarTitle/index.js index 87ba198e044..d4e7932f52f 100644 --- a/app/components/UI/NavbarTitle/index.js +++ b/app/components/UI/NavbarTitle/index.js @@ -112,10 +112,12 @@ class NavbarTitle extends PureComponent { }); this.props.metrics.trackEvent( - MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, - { - chain_id: getDecimalChainId(this.props.providerConfig.chainId), - }, + this.props.metrics + .createEventBuilder(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED) + .addProperties({ + chain_id: getDecimalChainId(this.props.providerConfig.chainId), + }) + .build(), ); setTimeout(() => { this.animating = false; diff --git a/app/components/UI/Notification/List/index.tsx b/app/components/UI/Notification/List/index.tsx index afa08f1bc58..035977563e7 100644 --- a/app/components/UI/Notification/List/index.tsx +++ b/app/components/UI/Notification/List/index.tsx @@ -59,7 +59,7 @@ function Loading() { export function NotificationsListItem(props: NotificationsListItemProps) { const { styles } = useStyles(); const { markNotificationAsRead } = useMarkNotificationAsRead(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const onNotificationClick = useCallback( (item: Notification) => { markNotificationAsRead([ @@ -83,23 +83,26 @@ export function NotificationsListItem(props: NotificationsListItemProps) { } }); - trackEvent(MetaMetricsEvents.NOTIFICATION_CLICKED, { - notification_id: item.id, - notification_type: item.type, - ...('chain_id' in item && { - chain_id: item.chain_id, - }), - previously_read: item.isRead, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NOTIFICATION_CLICKED) + .addProperties({ + notification_id: item.id, + notification_type: item.type, + previously_read: item.isRead, + ...('chain_id' in item && { chain_id: item.chain_id }), + }) + .build(), + ); }, - [markNotificationAsRead, props.navigation, trackEvent], + [markNotificationAsRead, props.navigation, trackEvent, createEventBuilder], ); const menuItemState = useMemo(() => { const notificationState = - props.notification?.type && hasNotificationComponents(props.notification.type) - ? NotificationComponentState[props.notification.type] - : undefined; + props.notification?.type && + hasNotificationComponents(props.notification.type) + ? NotificationComponentState[props.notification.type] + : undefined; return notificationState?.createMenuItem(props.notification); }, [props.notification]); @@ -172,7 +175,7 @@ function TabbedNotificationList(props: NotificationsListProps) { theme: { colors }, styles, } = useStyles(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const getListProps = useNotificationListProps(props); @@ -180,19 +183,25 @@ function TabbedNotificationList(props: NotificationsListProps) { (tabLabel: string) => { switch (tabLabel) { case strings('notifications.list.0'): - trackEvent(MetaMetricsEvents.ALL_NOTIFICATIONS); + trackEvent( + createEventBuilder(MetaMetricsEvents.ALL_NOTIFICATIONS).build(), + ); break; case strings('notifications.list.1'): - trackEvent(MetaMetricsEvents.WALLET_NOTIFICATIONS); + trackEvent( + createEventBuilder(MetaMetricsEvents.WALLET_NOTIFICATIONS).build(), + ); break; case strings('notifications.list.2'): - // trackEvent(MetaMetricsEvents.WEB3_NOTIFICATIONS); + // trackEvent( + // createEventBuilder(MetaMetricsEvents.WEB3_NOTIFICATIONS).build(), + // ); break; default: break; } }, - [trackEvent], + [trackEvent, createEventBuilder], ); return ( diff --git a/app/components/UI/Notification/ResetNotificationsModal/index.tsx b/app/components/UI/Notification/ResetNotificationsModal/index.tsx index ff45a3da4b9..49201392686 100644 --- a/app/components/UI/Notification/ResetNotificationsModal/index.tsx +++ b/app/components/UI/Notification/ResetNotificationsModal/index.tsx @@ -8,7 +8,7 @@ import BottomSheet, { } from '../../../../component-library/components/BottomSheets/BottomSheet'; import { strings } from '../../../../../locales/i18n'; -import { +import { IconColor, IconName, IconSize, @@ -19,10 +19,11 @@ import ModalContent from '../Modal'; import { ToastContext } from '../../../../component-library/components/Toast'; import { ToastVariants } from '../../../../component-library/components/Toast/Toast.types'; const ResetNotificationsModal = () => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const bottomSheetRef = useRef(null); const [isChecked, setIsChecked] = React.useState(false); - const { deleteNotificationsStorageKey, loading } = useDeleteNotificationsStorageKey(); + const { deleteNotificationsStorageKey, loading } = + useDeleteNotificationsStorageKey(); const { toastRef } = useContext(ToastContext); const closeBottomSheet = () => bottomSheetRef.current?.onCloseBottomSheet(); @@ -42,9 +43,11 @@ const ResetNotificationsModal = () => { const handleCta = async () => { await deleteNotificationsStorageKey().then(() => { showResultToast(); - trackEvent(MetaMetricsEvents.NOTIFICATION_STORAGE_KEY_DELETED, { - settings_type: 'delete_notifications_storage_key', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NOTIFICATION_STORAGE_KEY_DELETED) + .addProperties({ settings_type: 'delete_notifications_storage_key' }) + .build(), + ); }); }; @@ -56,7 +59,6 @@ const ResetNotificationsModal = () => { prevLoading.current = loading; }, [loading]); - return ( { handleCta={handleCta} handleCancel={closeBottomSheet} loading={loading} - /> + /> ); }; diff --git a/app/components/UI/OnboardingWizard/Step1/index.tsx b/app/components/UI/OnboardingWizard/Step1/index.tsx index 9ac7b417980..71e8ffe5932 100644 --- a/app/components/UI/OnboardingWizard/Step1/index.tsx +++ b/app/components/UI/OnboardingWizard/Step1/index.tsx @@ -38,7 +38,7 @@ const Step1 = ({ onClose }: Step1Props) => { const theme = useContext(ThemeContext) || mockTheme; const dynamicOnboardingStyles = onboardingStyles(theme.colors); const dispatch = useDispatch(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const content = useCallback( () => ( @@ -54,11 +54,15 @@ const Step1 = ({ onClose }: Step1Props) => { const onNext = useCallback(() => { dispatch(setOnboardingWizardStep?.(2)); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STARTED, { - tutorial_step_count: 1, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[1], - }); - }, [dispatch, trackEvent]); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STARTED) + .addProperties({ + tutorial_step_count: 1, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[1], + }) + .build(), + ); + }, [dispatch, trackEvent, createEventBuilder]); return ( { const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const dispatch = useDispatch(); const { coachmarkTop } = useHandleLayout(coachmarkRef); const onNext = () => { dispatch(setOnboardingWizardStep?.(3)); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { - tutorial_step_count: 2, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2], - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED) + .addProperties({ + tutorial_step_count: 2, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2], + }) + .build(), + ); }; const onBack = () => { dispatch(setOnboardingWizardStep?.(1)); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { - tutorial_step_count: 2, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2], - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED) + .addProperties({ + tutorial_step_count: 2, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2], + }) + .build(), + ); }; const getOnboardingStyles = () => onboardingStyles(colors); diff --git a/app/components/UI/OnboardingWizard/Step3/index.tsx b/app/components/UI/OnboardingWizard/Step3/index.tsx index 4e3c984b104..f21941c5cfb 100644 --- a/app/components/UI/OnboardingWizard/Step3/index.tsx +++ b/app/components/UI/OnboardingWizard/Step3/index.tsx @@ -36,24 +36,32 @@ interface Step3Props { const Step3 = ({ coachmarkRef, onClose }: Step3Props) => { const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const dispatch = useDispatch(); const { coachmarkTop } = useHandleLayout(coachmarkRef); const onNext = () => { dispatch(setOnboardingWizardStep?.(4)); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { - tutorial_step_count: 3, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[3], - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED) + .addProperties({ + tutorial_step_count: 3, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[3], + }) + .build(), + ); }; const onBack = () => { dispatch(setOnboardingWizardStep?.(2)); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { - tutorial_step_count: 3, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[3], - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED) + .addProperties({ + tutorial_step_count: 3, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[3], + }) + .build(), + ); }; const getOnboardingStyles = () => onboardingStyles(colors); diff --git a/app/components/UI/OnboardingWizard/Step4/index.tsx b/app/components/UI/OnboardingWizard/Step4/index.tsx index 6e6538f4222..f7a5b2c1b1d 100644 --- a/app/components/UI/OnboardingWizard/Step4/index.tsx +++ b/app/components/UI/OnboardingWizard/Step4/index.tsx @@ -35,7 +35,7 @@ interface Step4Props { const Step4 = ({ onClose }: Step4Props) => { const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const dispatch = useDispatch(); const [coachmarkTop, setCoachmarkTop] = useState(0); @@ -50,18 +50,26 @@ const Step4 = ({ onClose }: Step4Props) => { const onNext = () => { dispatch(setOnboardingWizardStep?.(5)); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { - tutorial_step_count: 4, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[4], - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED) + .addProperties({ + tutorial_step_count: 4, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[4], + }) + .build(), + ); }; const onBack = () => { dispatch(setOnboardingWizardStep?.(3)); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { - tutorial_step_count: 4, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[4], - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED) + .addProperties({ + tutorial_step_count: 4, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[4], + }) + .build(), + ); }; const getOnboardingStyles = () => onboardingStyles(colors); diff --git a/app/components/UI/OnboardingWizard/Step5/index.tsx b/app/components/UI/OnboardingWizard/Step5/index.tsx index 69fe4dcd685..583b68468aa 100644 --- a/app/components/UI/OnboardingWizard/Step5/index.tsx +++ b/app/components/UI/OnboardingWizard/Step5/index.tsx @@ -36,7 +36,7 @@ interface Step5Props { } const Step5 = ({ onClose }: Step5Props) => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { colors } = useTheme(); const dispatch = useDispatch(); const dynamicOnboardingStyles = onboardingStyles(colors); @@ -46,10 +46,14 @@ const Step5 = ({ onClose }: Step5Props) => { */ const onNext = () => { dispatch(setOnboardingWizardStep?.(6)); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { - tutorial_step_count: 5, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[5], - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED) + .addProperties({ + tutorial_step_count: 5, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[5], + }) + .build(), + ); }; /** @@ -57,10 +61,14 @@ const Step5 = ({ onClose }: Step5Props) => { */ const onBack = () => { dispatch(setOnboardingWizardStep?.(4)); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { - tutorial_step_count: 5, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[5], - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED) + .addProperties({ + tutorial_step_count: 5, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[5], + }) + .build(), + ); }; /** diff --git a/app/components/UI/OnboardingWizard/Step6/index.tsx b/app/components/UI/OnboardingWizard/Step6/index.tsx index 79bda1f8770..58f08290442 100644 --- a/app/components/UI/OnboardingWizard/Step6/index.tsx +++ b/app/components/UI/OnboardingWizard/Step6/index.tsx @@ -38,7 +38,7 @@ interface Step6Props { } const Step6 = ({ onClose, navigation }: Step6Props) => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { colors } = useTheme(); const dynamicOnboardingStyles = onboardingStyles(colors); const dispatch = useDispatch(); @@ -48,10 +48,14 @@ const Step6 = ({ onClose, navigation }: Step6Props) => { const onNext = () => { dispatch(setOnboardingWizardStep?.(7)); navigation?.navigate(...createBrowserNavDetails()); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, { - tutorial_step_count: 6, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[6], - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED) + .addProperties({ + tutorial_step_count: 6, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[6], + }) + .build(), + ); }; /** @@ -59,10 +63,14 @@ const Step6 = ({ onClose, navigation }: Step6Props) => { */ const onBack = () => { dispatch(setOnboardingWizardStep?.(5)); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { - tutorial_step_count: 6, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[6], - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED) + .addProperties({ + tutorial_step_count: 6, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[6], + }) + .build(), + ); }; /** diff --git a/app/components/UI/OnboardingWizard/Step7/index.tsx b/app/components/UI/OnboardingWizard/Step7/index.tsx index 661a65ee593..cc0e539cfcd 100644 --- a/app/components/UI/OnboardingWizard/Step7/index.tsx +++ b/app/components/UI/OnboardingWizard/Step7/index.tsx @@ -37,7 +37,7 @@ interface Step7Props { } const Step7 = ({ navigation, onClose }: Step7Props) => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const dispatch = useDispatch(); const [ready, setReady] = useState(false); const [coachmarkTop, setCoachmarkTop] = useState(0); @@ -65,10 +65,14 @@ const Step7 = ({ navigation, onClose }: Step7Props) => { setTimeout(() => { dispatch(setOnboardingWizardStep?.(6)); }, 1); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, { - tutorial_step_count: 7, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[7], - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED) + .addProperties({ + tutorial_step_count: 7, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[7], + }) + .build(), + ); }; /** diff --git a/app/components/UI/OnboardingWizard/index.tsx b/app/components/UI/OnboardingWizard/index.tsx index 189db8a7699..3f96607358e 100644 --- a/app/components/UI/OnboardingWizard/index.tsx +++ b/app/components/UI/OnboardingWizard/index.tsx @@ -91,7 +91,7 @@ const OnboardingWizard = ({ const { drawerRef } = useContext(DrawerContext); const theme = useTheme(); const dispatch = useDispatch(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(theme); const isAutomaticSecurityChecksModalOpen = useSelector( @@ -106,11 +106,17 @@ const OnboardingWizard = ({ const closeOnboardingWizard = async () => { await StorageWrapper.setItem(ONBOARDING_WIZARD, EXPLORED); dispatch(setOnboardingWizardStep(0)); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_SKIPPED, { - tutorial_step_count: step, - tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[step], - }); - trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_COMPLETED); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_SKIPPED) + .addProperties({ + tutorial_step_count: step, + tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[step], + }) + .build(), + ); + trackEvent( + createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_COMPLETED).build(), + ); }; // Since react-native-default-preference is not covered by the fixtures, diff --git a/app/components/UI/OptinMetrics/index.js b/app/components/UI/OptinMetrics/index.js index d765fd8f60d..58506d586b3 100644 --- a/app/components/UI/OptinMetrics/index.js +++ b/app/components/UI/OptinMetrics/index.js @@ -385,12 +385,17 @@ class OptinMetrics extends PureComponent { this.props.clearOnboardingEvents(); // track event for user opting in on metrics and data collection for marketing - metrics.trackEvent(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, { - ...dataCollectionForMarketingTraits, - is_metrics_opted_in: true, - location: 'onboarding_metametrics', - updated_after_onboarding: false, - }); + metrics.trackEvent( + metrics + .createEventBuilder(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED) + .addProperties({ + ...dataCollectionForMarketingTraits, + is_metrics_opted_in: true, + location: 'onboarding_metametrics', + updated_after_onboarding: false, + }) + .build(), + ); }); this.continue(); }; diff --git a/app/components/UI/OptinMetrics/index.test.tsx b/app/components/UI/OptinMetrics/index.test.tsx index f101dad8312..3072907108e 100644 --- a/app/components/UI/OptinMetrics/index.test.tsx +++ b/app/components/UI/OptinMetrics/index.test.tsx @@ -3,6 +3,7 @@ import { renderScreen } from '../../../util/test/renderWithProvider'; import { MetaMetrics, MetaMetricsEvents } from '../../../core/Analytics'; import { fireEvent, screen, waitFor } from '@testing-library/react-native'; import { strings } from '../../../../locales/i18n'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; const { InteractionManager } = jest.requireActual('react-native'); @@ -60,12 +61,15 @@ describe('OptinMetrics', () => { await waitFor(() => { expect(mockMetrics.trackEvent).toHaveBeenNthCalledWith( 1, - MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, - { - is_metrics_opted_in: true, - location: 'onboarding_metametrics', - updated_after_onboarding: false, - }, + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, + ) + .addProperties({ + is_metrics_opted_in: true, + location: 'onboarding_metametrics', + updated_after_onboarding: false, + }) + .build(), ); expect(mockMetrics.addTraitsToUser).toHaveBeenNthCalledWith(1, { deviceProp: 'Device value', @@ -86,13 +90,16 @@ describe('OptinMetrics', () => { await waitFor(() => { expect(mockMetrics.trackEvent).toHaveBeenNthCalledWith( 1, - MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, - { - has_marketing_consent: true, - is_metrics_opted_in: true, - location: 'onboarding_metametrics', - updated_after_onboarding: false, - }, + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, + ) + .addProperties({ + has_marketing_consent: true, + is_metrics_opted_in: true, + location: 'onboarding_metametrics', + updated_after_onboarding: false, + }) + .build(), ); expect(mockMetrics.addTraitsToUser).toHaveBeenNthCalledWith(1, { deviceProp: 'Device value', diff --git a/app/components/UI/ProfileSyncing/ProfileSyncingModal/ProfileSyncingModal.tsx b/app/components/UI/ProfileSyncing/ProfileSyncingModal/ProfileSyncingModal.tsx index ab2672406e9..3b626171cfa 100644 --- a/app/components/UI/ProfileSyncing/ProfileSyncingModal/ProfileSyncingModal.tsx +++ b/app/components/UI/ProfileSyncing/ProfileSyncingModal/ProfileSyncingModal.tsx @@ -23,7 +23,7 @@ import { MetaMetricsEvents } from '../../../../core/Analytics'; import ModalContent from '../../Notification/Modal'; const ProfileSyncingModal = () => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const bottomSheetRef = useRef(null); const [isChecked, setIsChecked] = React.useState(false); const { disableProfileSyncing } = useProfileSyncing(); @@ -39,13 +39,17 @@ const ProfileSyncingModal = () => { if (isProfileSyncingEnabled) { await disableProfileSyncing(); } - trackEvent(MetaMetricsEvents.SETTINGS_UPDATED, { - settings_group: 'security_privacy', - settings_type: 'profile_syncing', - old_value: isProfileSyncingEnabled, - new_value: !isProfileSyncingEnabled, - was_notifications_on: isMetamaskNotificationsEnabled, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.SETTINGS_UPDATED) + .addProperties({ + settings_group: 'security_privacy', + settings_type: 'profile_syncing', + old_value: isProfileSyncingEnabled, + new_value: !isProfileSyncingEnabled, + was_notifications_on: isMetamaskNotificationsEnabled, + }) + .build(), + ); }); }; @@ -93,7 +97,7 @@ const ProfileSyncingModal = () => { hascheckBox={isProfileSyncingEnabled} handleCta={handleCta} handleCancel={handleCancel} - /> + /> ); }; diff --git a/app/components/UI/ProtectYourWalletModal/index.js b/app/components/UI/ProtectYourWalletModal/index.js index b3c0c2584ae..00342d87e6d 100644 --- a/app/components/UI/ProtectYourWalletModal/index.js +++ b/app/components/UI/ProtectYourWalletModal/index.js @@ -102,11 +102,13 @@ class ProtectYourWalletModal extends PureComponent { this.props.passwordSet ? { screen: 'AccountBackupStep1' } : undefined, ); this.props.metrics.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_PROTECT_ENGAGED, - { - wallet_protection_required: false, - source: 'Modal', - }, + this.props.metrics + .createEventBuilder(MetaMetricsEvents.WALLET_SECURITY_PROTECT_ENGAGED) + .addProperties({ + wallet_protection_required: false, + source: 'Modal', + }) + .build(), ); }; @@ -124,11 +126,13 @@ class ProtectYourWalletModal extends PureComponent { onDismiss = () => { this.props.protectWalletModalNotVisible(); this.props.metrics.trackEvent( - MetaMetricsEvents.WALLET_SECURITY_PROTECT_DISMISSED, - { - wallet_protection_required: false, - source: 'Modal', - }, + this.props.metrics + .createEventBuilder(MetaMetricsEvents.WALLET_SECURITY_PROTECT_DISMISSED) + .addProperties({ + wallet_protection_required: false, + source: 'Modal', + }) + .build(), ); }; diff --git a/app/components/UI/QRHardware/AnimatedQRScanner.tsx b/app/components/UI/QRHardware/AnimatedQRScanner.tsx index ae81985fe40..05420e31609 100644 --- a/app/components/UI/QRHardware/AnimatedQRScanner.tsx +++ b/app/components/UI/QRHardware/AnimatedQRScanner.tsx @@ -106,7 +106,7 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => { const [urDecoder, setURDecoder] = useState(new URRegistryDecoder()); const [progress, setProgress] = useState(0); const theme = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(theme); let expectedURTypes: string[]; @@ -143,14 +143,15 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => { const onError = useCallback( (error) => { if (onScanError && error) { - trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { - purpose, - error, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.HARDWARE_WALLET_ERROR) + .addProperties({ purpose, error }) + .build(), + ); onScanError(error.message); } }, - [purpose, onScanError, trackEvent], + [purpose, onScanError, trackEvent, createEventBuilder], ); const onBarCodeRead = useCallback( @@ -166,10 +167,11 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => { urDecoder.receivePart(content); setProgress(Math.ceil(urDecoder.getProgress() * 100)); if (urDecoder.isError()) { - trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { - purpose, - error: urDecoder.resultError(), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.HARDWARE_WALLET_ERROR) + .addProperties({ purpose, error: urDecoder.resultError() }) + .build(), + ); onScanError(strings('transaction.unknown_qr_code')); } else if (urDecoder.isSuccess()) { const ur = urDecoder.resultUR(); @@ -178,18 +180,26 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => { setProgress(0); setURDecoder(new URRegistryDecoder()); } else if (purpose === 'sync') { - trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { - purpose, - received_ur_type: ur.type, - error: 'invalid `sync` qr code', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.HARDWARE_WALLET_ERROR) + .addProperties({ + purpose, + received_ur_type: ur.type, + error: 'invalid `sync` qr code', + }) + .build(), + ); onScanError(strings('transaction.invalid_qr_code_sync')); } else { - trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { - purpose, - received_ur_type: ur.type, - error: 'invalid `sign` qr code', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.HARDWARE_WALLET_ERROR) + .addProperties({ + purpose, + received_ur_type: ur.type, + error: 'invalid `sign` qr code', + }) + .build(), + ); onScanError(strings('transaction.invalid_qr_code_sign')); } } @@ -205,6 +215,7 @@ const AnimatedQRScannerModal = (props: AnimatedQRScannerProps) => { purpose, onScanSuccess, trackEvent, + createEventBuilder, ], ); diff --git a/app/components/UI/QRHardware/QRSigningDetails.tsx b/app/components/UI/QRHardware/QRSigningDetails.tsx index baf4b93b0b4..634109398b6 100644 --- a/app/components/UI/QRHardware/QRSigningDetails.tsx +++ b/app/components/UI/QRHardware/QRSigningDetails.tsx @@ -130,7 +130,7 @@ const QRSigningDetails = ({ fromAddress, }: IQRSigningDetails) => { const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); const navigation = useNavigation(); const KeyringController = useMemo(() => { @@ -238,10 +238,14 @@ const QRSigningDetails = ({ return; } } - trackEvent(MetaMetricsEvents.HARDWARE_WALLET_ERROR, { - error: - 'received signature request id is not matched with origin request', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.HARDWARE_WALLET_ERROR) + .addProperties({ + error: + 'received signature request id is not matched with origin request', + }) + .build(), + ); setErrorMessage(strings('transaction.mismatched_qr_request_id')); failureCallback?.(strings('transaction.mismatched_qr_request_id')); }, @@ -251,6 +255,7 @@ const QRSigningDetails = ({ failureCallback, successCallback, trackEvent, + createEventBuilder, ], ); const onScanError = useCallback( diff --git a/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx b/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx index 9894e7e5d13..bd41a45d241 100644 --- a/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx +++ b/app/components/UI/Ramp/Views/NetworkSwitcher/NetworkSwitcher.tsx @@ -159,10 +159,7 @@ function NetworkSwitcher() { ); if (config) { - const { - rpcEndpoints, - defaultRpcEndpointIndex, - } = config; + const { rpcEndpoints, defaultRpcEndpointIndex } = config; const { networkClientId } = rpcEndpoints?.[defaultRpcEndpointIndex] ?? {}; diff --git a/app/components/UI/Ramp/hooks/useAnalytics.test.ts b/app/components/UI/Ramp/hooks/useAnalytics.test.ts index 27d288e377b..126f4baa866 100644 --- a/app/components/UI/Ramp/hooks/useAnalytics.test.ts +++ b/app/components/UI/Ramp/hooks/useAnalytics.test.ts @@ -1,6 +1,7 @@ import { MetaMetrics, MetaMetricsEvents } from '../../../../core/Analytics'; import { renderHookWithProvider } from '../../../../util/test/renderWithProvider'; import useAnalytics from './useAnalytics'; +import { MetricsEventBuilder } from '../../../../core/Analytics/MetricsEventBuilder'; jest.mock('../../../../core/Analytics', () => ({ ...jest.requireActual('../../../../core/Analytics'), @@ -24,38 +25,40 @@ describe('useAnalytics', () => { const { result } = renderHookWithProvider(() => useAnalytics()); const testEvent = 'BUY_BUTTON_CLICKED'; - - result.current(testEvent, { + const testEventParams = { location: 'Amount to Buy Screen', text: 'Buy', - }); + } as const; + + result.current(testEvent, testEventParams); expect(MetaMetrics.getInstance().trackEvent).toHaveBeenCalledWith( - MetaMetricsEvents[testEvent], - { - location: 'Amount to Buy Screen', - text: 'Buy', - }, + MetricsEventBuilder.createEventBuilder(MetaMetricsEvents[testEvent]) + .addProperties(testEventParams) + .build(), ); }); it('calls trackEvent for anonymous params', () => { - const { result } = renderHookWithProvider(() => useAnalytics()); - const testEvent = 'RAMP_REGION_SELECTED'; - const testPayload = { + const testEventParams = { country_id: 'test-country-id', is_unsupported_offramp: false, is_unsupported_onramp: false, - }; + } as const; + + jest.mock('../constants', () => ({ + AnonymousEvents: [testEvent], + })); + + const { result } = renderHookWithProvider(() => useAnalytics()); - result.current(testEvent, testPayload); + result.current(testEvent, testEventParams); expect(MetaMetrics.getInstance().trackEvent).toHaveBeenCalledWith( - MetaMetricsEvents[testEvent], - { - sensitiveProperties: testPayload, - }, + MetricsEventBuilder.createEventBuilder(MetaMetricsEvents[testEvent]) + .addSensitiveProperties(testEventParams) + .build(), ); }); }); diff --git a/app/components/UI/Ramp/hooks/useAnalytics.ts b/app/components/UI/Ramp/hooks/useAnalytics.ts index 3dc3c369af4..6a99304d3d4 100644 --- a/app/components/UI/Ramp/hooks/useAnalytics.ts +++ b/app/components/UI/Ramp/hooks/useAnalytics.ts @@ -2,6 +2,7 @@ import { useCallback } from 'react'; import { InteractionManager } from 'react-native'; import { AnalyticsEvents } from '../types'; import { MetaMetrics, MetaMetricsEvents } from '../../../../core/Analytics'; +import { MetricsEventBuilder } from '../../../../core/Analytics/MetricsEventBuilder'; const AnonymousEvents: (keyof AnalyticsEvents)[] = [ 'RAMP_REGION_SELECTED', @@ -36,18 +37,17 @@ export function trackEvent( eventType: T, params: AnalyticsEvents[T], ) { - const metrics = MetaMetrics.getInstance(); - const event = MetaMetricsEvents[eventType]; const anonymous = AnonymousEvents.includes(eventType); + const metrics = MetaMetrics.getInstance(); + const event = MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents[eventType], + ); + InteractionManager.runAfterInteractions(() => { if (anonymous) { - metrics.trackEvent(event, { - sensitiveProperties: { ...params }, - }); + metrics.trackEvent(event.addSensitiveProperties({ ...params }).build()); } else { - metrics.trackEvent(event, { - ...params, - }); + metrics.trackEvent(event.addProperties({ ...params }).build()); } }); } diff --git a/app/components/UI/ReceiveRequest/index.js b/app/components/UI/ReceiveRequest/index.js index 8cdbcb0f5cb..19cae3b3b8e 100644 --- a/app/components/UI/ReceiveRequest/index.js +++ b/app/components/UI/ReceiveRequest/index.js @@ -176,7 +176,9 @@ class ReceiveRequest extends PureComponent { }); this.props.metrics.trackEvent( - MetaMetricsEvents.RECEIVE_OPTIONS_SHARE_ADDRESS, + this.props.metrics + .createEventBuilder(MetaMetricsEvents.RECEIVE_OPTIONS_SHARE_ADDRESS) + .build(), ); }; @@ -193,11 +195,16 @@ class ReceiveRequest extends PureComponent { } else { navigation.navigate(...createBuyNavigationDetails()); - this.props.metrics.trackEvent(MetaMetricsEvents.BUY_BUTTON_CLICKED, { - text: 'Buy Native Token', - location: 'Receive Modal', - chain_id_destination: getDecimalChainId(this.props.chainId), - }); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.BUY_BUTTON_CLICKED) + .addProperties({ + text: 'Buy Native Token', + location: 'Receive Modal', + chain_id_destination: getDecimalChainId(this.props.chainId), + }) + .build(), + ); } }; @@ -223,7 +230,9 @@ class ReceiveRequest extends PureComponent { }); this.props.metrics.trackEvent( - MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST, + this.props.metrics + .createEventBuilder(MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST) + .build(), ); }; diff --git a/app/components/UI/ScreenshotDeterrent/ScreenshotDeterrent.tsx b/app/components/UI/ScreenshotDeterrent/ScreenshotDeterrent.tsx index c8a23aea45c..b5cc21f235d 100644 --- a/app/components/UI/ScreenshotDeterrent/ScreenshotDeterrent.tsx +++ b/app/components/UI/ScreenshotDeterrent/ScreenshotDeterrent.tsx @@ -36,18 +36,22 @@ const ScreenshotDeterrentWithNavigation = ({ enabled: boolean; isSRP: boolean; }) => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const [alertPresent, setAlertPresent] = useState(false); const navigation = useNavigation(); const openSRPGuide = useCallback(() => { setAlertPresent(false); - trackEvent(MetaMetricsEvents.SCREENSHOT_LEARN_MORE, {}); + trackEvent( + createEventBuilder(MetaMetricsEvents.SCREENSHOT_LEARN_MORE).build(), + ); Linking.openURL(SRP_GUIDE_URL); - }, [trackEvent]); + }, [trackEvent, createEventBuilder]); const showScreenshotAlert = useCallback(() => { - trackEvent(MetaMetricsEvents.SCREENSHOT_WARNING, {}); + trackEvent( + createEventBuilder(MetaMetricsEvents.SCREENSHOT_WARNING).build(), + ); setAlertPresent(true); navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { @@ -61,14 +65,16 @@ const ScreenshotDeterrentWithNavigation = ({ }), onCancel: () => { setAlertPresent(false); - trackEvent(MetaMetricsEvents.SCREENSHOT_OK, {}); + trackEvent( + createEventBuilder(MetaMetricsEvents.SCREENSHOT_OK).build(), + ); }, onConfirm: openSRPGuide, confirmLabel: strings('reveal_credential.learn_more'), cancelLabel: strings('reveal_credential.got_it'), }, }); - }, [isSRP, navigation, trackEvent, openSRPGuide]); + }, [isSRP, navigation, trackEvent, openSRPGuide, createEventBuilder]); const [enableScreenshotWarning] = useScreenshotDeterrent(showScreenshotAlert); diff --git a/app/components/UI/SearchTokenAutocomplete/index.tsx b/app/components/UI/SearchTokenAutocomplete/index.tsx index 34185acdbf0..ccb9d289431 100644 --- a/app/components/UI/SearchTokenAutocomplete/index.tsx +++ b/app/components/UI/SearchTokenAutocomplete/index.tsx @@ -79,7 +79,7 @@ interface Props { * Component that provides ability to add searched assets with metadata. */ const SearchTokenAutocomplete = ({ navigation }: Props) => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const [searchResults, setSearchResults] = useState([]); const [searchQuery, setSearchQuery] = useState(''); // TODO: Replace "any" with type @@ -163,9 +163,13 @@ const SearchTokenAutocomplete = ({ navigation }: Props) => { name, }); - trackEvent(MetaMetricsEvents.TOKEN_ADDED, getAnalyticsParams()); + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKEN_ADDED) + .addProperties(getAnalyticsParams()) + .build(), + ); }, - [getAnalyticsParams, trackEvent], + [getAnalyticsParams, trackEvent, createEventBuilder], ); /** @@ -216,10 +220,14 @@ const SearchTokenAutocomplete = ({ navigation }: Props) => { addTokenList, }); - trackEvent(MetaMetricsEvents.TOKEN_IMPORT_CLICKED, { - source: 'manual', - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKEN_IMPORT_CLICKED) + .addProperties({ + source: 'manual', + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); }; const renderTokenDetectionBanner = useCallback(() => { diff --git a/app/components/UI/SimulationDetails/useSimulationMetrics.test.ts b/app/components/UI/SimulationDetails/useSimulationMetrics.test.ts index d0428af4b64..cf9eacf54a6 100644 --- a/app/components/UI/SimulationDetails/useSimulationMetrics.test.ts +++ b/app/components/UI/SimulationDetails/useSimulationMetrics.test.ts @@ -24,6 +24,7 @@ import { } from './useSimulationMetrics'; import useLoadingTime from './useLoadingTime'; import { selectChainId } from '../../../selectors/networkController'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), @@ -42,6 +43,21 @@ jest.mock('./useLoadingTime'); jest.mock('../../hooks/DisplayName/useDisplayName'); jest.mock('../../../core/redux/slices/transactionMetrics'); jest.mock('../../../components/hooks/useMetrics'); +const mockTrackEvent = jest.fn(); +(useMetrics as jest.MockedFn).mockReturnValue({ + trackEvent: mockTrackEvent, + createEventBuilder: MetricsEventBuilder.createEventBuilder, + enable: jest.fn(), + addTraitsToUser: jest.fn(), + createDataDeletionTask: jest.fn(), + checkDataDeleteStatus: jest.fn(), + getDeleteRegulationCreationDate: jest.fn(), + getDeleteRegulationId: jest.fn(), + isDataRecorded: jest.fn(), + isEnabled: jest.fn(), + getMetaMetricsId: jest.fn(), +}); + jest.mock('../../../selectors/networkController'); const TRANSACTION_ID_MOCK = 'testTransactionId'; @@ -72,14 +88,9 @@ describe('useSimulationMetrics', () => { const useEffectMock = jest.mocked(useEffect); const useDisplayNamesMock = jest.mocked(useDisplayNames); const useLoadingTimeMock = jest.mocked(useLoadingTime); - const useMetricsMock = jest.mocked(useMetrics); const setLoadingCompleteMock = jest.fn(); const selectChainIdMock = jest.mocked(selectChainId); - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let trackEventMock: jest.MockedFunction; - function expectUpdateTransactionMetricsCalled( { balanceChanges, @@ -112,9 +123,7 @@ describe('useSimulationMetrics', () => { } beforeEach(() => { - jest.resetAllMocks(); - - trackEventMock = jest.fn(); + jest.clearAllMocks(); // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -132,9 +141,6 @@ describe('useSimulationMetrics', () => { loadingTime: LOADING_TIME_MOCK, setLoadingComplete: setLoadingCompleteMock, }); - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - useMetricsMock.mockReturnValue({ trackEvent: trackEventMock } as any); selectChainIdMock.mockReturnValue(CHAIN_IDS.MAINNET); }); @@ -384,17 +390,20 @@ describe('useSimulationMetrics', () => { transactionId: TRANSACTION_ID_MOCK, }); - expect(trackEventMock).toHaveBeenCalledTimes(1); - expect(trackEventMock).toHaveBeenCalledWith( - MetaMetricsEvents.INCOMPLETE_ASSET_DISPLAYED, - { - asset_address: ADDRESS_MOCK, - asset_symbol: SYMBOL_MOCK, - asset_petname: 'unknown', - asset_type: AssetType.ERC20, - fiat_conversion_available: FiatType.NotAvailable, - location: 'confirmation', - }, + expect(mockTrackEvent).toHaveBeenCalledTimes(1); + expect(mockTrackEvent).toHaveBeenCalledWith( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.INCOMPLETE_ASSET_DISPLAYED, + ) + .addProperties({ + asset_address: ADDRESS_MOCK, + asset_symbol: SYMBOL_MOCK, + asset_petname: 'unknown', + asset_type: AssetType.ERC20, + fiat_conversion_available: FiatType.NotAvailable, + location: 'confirmation', + }) + .build(), ); }); }); diff --git a/app/components/UI/SimulationDetails/useSimulationMetrics.ts b/app/components/UI/SimulationDetails/useSimulationMetrics.ts index 7507542b459..ed7e7f2f4c9 100644 --- a/app/components/UI/SimulationDetails/useSimulationMetrics.ts +++ b/app/components/UI/SimulationDetails/useSimulationMetrics.ts @@ -124,7 +124,7 @@ function useIncompleteAssetEvent( balanceChanges: BalanceChange[], displayNamesByAddress: { [address: string]: UseDisplayNameResponse }, ) { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const [processedAssets, setProcessedAssets] = useState([]); for (const change of balanceChanges) { @@ -139,17 +139,21 @@ function useIncompleteAssetEvent( continue; } - trackEvent(MetaMetricsEvents.INCOMPLETE_ASSET_DISPLAYED, { - asset_address: change.asset.address, - // Petnames doesn't exist in mobile so we set as unknown for now - asset_petname: 'unknown', - asset_symbol: displayName.contractDisplayName, - asset_type: change.asset.type, - fiat_conversion_available: change.fiatAmount - ? FiatType.Available - : FiatType.NotAvailable, - location: 'confirmation', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.INCOMPLETE_ASSET_DISPLAYED) + .addProperties({ + asset_address: change.asset.address, + // Petnames doesn't exist in mobile so we set as unknown for now + asset_petname: 'unknown', + asset_symbol: displayName.contractDisplayName, + asset_type: change.asset.type, + fiat_conversion_available: change.fiatAmount + ? FiatType.Available + : FiatType.NotAvailable, + location: 'confirmation', + }) + .build(), + ); setProcessedAssets([...processedAssets, assetAddress]); } diff --git a/app/components/UI/Stake/components/StakeButton/StakeButton.test.tsx b/app/components/UI/Stake/components/StakeButton/StakeButton.test.tsx index bfd4eafd26b..8a503d25d43 100644 --- a/app/components/UI/Stake/components/StakeButton/StakeButton.test.tsx +++ b/app/components/UI/Stake/components/StakeButton/StakeButton.test.tsx @@ -5,6 +5,8 @@ import StakeButton from './index'; import Routes from '../../../../../constants/navigation/Routes'; import renderWithProvider from '../../../../../util/test/renderWithProvider'; import { MOCK_STAKED_ETH_ASSET } from '../../__mocks__/mockData'; +import { useMetrics } from '../../../../hooks/useMetrics'; +import { MetricsEventBuilder } from '../../../../../core/Analytics/MetricsEventBuilder'; const mockNavigate = jest.fn(); @@ -22,14 +24,21 @@ jest.mock('../../constants', () => ({ isPooledStakingFeatureEnabled: jest.fn().mockReturnValue(true), })); -jest.mock('../../../../hooks/useMetrics', () => ({ - MetaMetricsEvents: { - STAKE_BUTTON_CLICKED: 'Stake Button Clicked', - }, - useMetrics: () => ({ - trackEvent: jest.fn(), - }), -})); +jest.mock('../../../../hooks/useMetrics'); + +(useMetrics as jest.MockedFn).mockReturnValue({ + trackEvent: jest.fn(), + createEventBuilder: MetricsEventBuilder.createEventBuilder, + enable: jest.fn(), + addTraitsToUser: jest.fn(), + createDataDeletionTask: jest.fn(), + checkDataDeleteStatus: jest.fn(), + getDeleteRegulationCreationDate: jest.fn(), + getDeleteRegulationId: jest.fn(), + isDataRecorded: jest.fn(), + isEnabled: jest.fn(), + getMetaMetricsId: jest.fn(), +}); jest.mock('../../../../../core/Engine', () => ({ context: { diff --git a/app/components/UI/Stake/components/StakeButton/index.tsx b/app/components/UI/Stake/components/StakeButton/index.tsx index d9339057a91..ca578bef3bd 100644 --- a/app/components/UI/Stake/components/StakeButton/index.tsx +++ b/app/components/UI/Stake/components/StakeButton/index.tsx @@ -33,7 +33,7 @@ const StakeButtonContent = ({ asset }: StakeButtonProps) => { const { colors } = useTheme(); const styles = createStyles(colors); const navigation = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const browserTabs = useSelector((state: RootState) => state.browser.tabs); const chainId = useSelector(selectChainId); @@ -65,13 +65,17 @@ const StakeButtonContent = ({ asset }: StakeButtonProps) => { params, }); } - trackEvent(MetaMetricsEvents.STAKE_BUTTON_CLICKED, { - chain_id: getDecimalChainId(chainId), - location: 'Home Screen', - text: 'Stake', - token_symbol: asset.symbol, - url: AppConstants.STAKE.URL, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.STAKE_BUTTON_CLICKED) + .addProperties({ + chain_id: getDecimalChainId(chainId), + location: 'Home Screen', + text: 'Stake', + token_symbol: asset.symbol, + url: AppConstants.STAKE.URL, + }) + .build(), + ); }; return ( diff --git a/app/components/UI/Stake/hooks/usePoolStakedUnstake/index.ts b/app/components/UI/Stake/hooks/usePoolStakedUnstake/index.ts index 641a2fd9c49..7c54c5ec1c7 100644 --- a/app/components/UI/Stake/hooks/usePoolStakedUnstake/index.ts +++ b/app/components/UI/Stake/hooks/usePoolStakedUnstake/index.ts @@ -43,11 +43,15 @@ const attemptUnstakeTransaction = 'function getShares(address) returns (uint256)', ]); const data = tempInterface.encodeFunctionData('getShares', [receiver]); - const sharesResult = await pooledStakingContract?.contract.provider.call({ - to: pooledStakingContract?.contract.address, - data, - }); - const [sharesBN] = tempInterface.decodeFunctionResult('getShares', sharesResult); + const sharesResult = + await pooledStakingContract?.contract.provider.call({ + to: pooledStakingContract?.contract.address, + data, + }); + const [sharesBN] = tempInterface.decodeFunctionResult( + 'getShares', + sharesResult, + ); shares = sharesBN.toString(); } else { shares = await pooledStakingContract.convertToShares(valueWei); @@ -93,7 +97,10 @@ const usePoolStakedUnstake = () => { const stakingContract = stakeContext.stakingContract as PooledStakingContract; return { - attemptUnstakeTransaction: attemptUnstakeTransaction(stakingContract, stakedBalanceWei), + attemptUnstakeTransaction: attemptUnstakeTransaction( + stakingContract, + stakedBalanceWei, + ), }; }; diff --git a/app/components/UI/Stake/hooks/usePoolStakedUnstake/usePoolStakedUnstake.test.tsx b/app/components/UI/Stake/hooks/usePoolStakedUnstake/usePoolStakedUnstake.test.tsx index 780791f1d0c..1bf8382eeed 100644 --- a/app/components/UI/Stake/hooks/usePoolStakedUnstake/usePoolStakedUnstake.test.tsx +++ b/app/components/UI/Stake/hooks/usePoolStakedUnstake/usePoolStakedUnstake.test.tsx @@ -148,10 +148,15 @@ describe('usePoolStakedUnstake', () => { }); it('attempts to create and submit an unstake all transaction', async () => { - jest.spyOn(ethers.utils, 'Interface').mockImplementation(() => ({ - encodeFunctionData: jest.fn(), - decodeFunctionResult: jest.fn().mockReturnValue([BigNumber.from(MOCK_UNSTAKE_ALL_VALUE_WEI)]), - } as unknown as ethers.utils.Interface)); + jest.spyOn(ethers.utils, 'Interface').mockImplementation( + () => + ({ + encodeFunctionData: jest.fn(), + decodeFunctionResult: jest + .fn() + .mockReturnValue([BigNumber.from(MOCK_UNSTAKE_ALL_VALUE_WEI)]), + } as unknown as ethers.utils.Interface), + ); const { result } = renderHookWithProvider(() => usePoolStakedUnstake(), { state: mockInitialState, @@ -165,7 +170,11 @@ describe('usePoolStakedUnstake', () => { expect(mockConvertToShares).toHaveBeenCalledTimes(0); expect(mockEstimateEnterExitQueueGas).toHaveBeenCalledTimes(1); expect(mockEncodeEnterExitQueueTransactionData).toHaveBeenCalledTimes(1); - expect(mockEncodeEnterExitQueueTransactionData).toHaveBeenCalledWith(BigNumber.from(MOCK_UNSTAKE_ALL_VALUE_WEI).toString(), MOCK_RECEIVER_ADDRESS, { gasLimit: MOCK_UNSTAKE_GAS_LIMIT }); + expect(mockEncodeEnterExitQueueTransactionData).toHaveBeenCalledWith( + BigNumber.from(MOCK_UNSTAKE_ALL_VALUE_WEI).toString(), + MOCK_RECEIVER_ADDRESS, + { gasLimit: MOCK_UNSTAKE_GAS_LIMIT }, + ); expect(mockAddTransaction).toHaveBeenCalledTimes(1); }); }); diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js index b0e37a6e718..2eb6cefaee3 100644 --- a/app/components/UI/Swaps/QuotesView.js +++ b/app/components/UI/Swaps/QuotesView.js @@ -408,7 +408,7 @@ function SwapsQuotesView({ const navigation = useNavigation(); /* Get params from navigation */ const route = useRoute(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { colors } = useTheme(); const styles = createStyles(colors); @@ -742,9 +742,11 @@ function SwapsQuotesView({ chain_id: getDecimalChainId(chainId), }; - trackEvent(MetaMetricsEvents.GAS_FEES_CHANGED, { - sensitiveProperties: { ...parameters }, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.GAS_FEES_CHANGED) + .addSensitiveProperties(parameters) + .build(), + ); }, [ chainId, @@ -753,6 +755,7 @@ function SwapsQuotesView({ gasEstimateType, gasLimit, trackEvent, + createEventBuilder, ], ); @@ -897,9 +900,11 @@ function SwapsQuotesView({ chain_id: getDecimalChainId(chainId), is_smart_transaction: shouldUseSmartTransaction, }; - trackEvent(MetaMetricsEvents.SWAP_STARTED, { - sensitiveProperties: { ...parameters }, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.SWAP_STARTED) + .addSensitiveProperties(parameters) + .build(), + ); }, // eslint-disable-next-line react-hooks/exhaustive-deps [ @@ -912,6 +917,7 @@ function SwapsQuotesView({ selectedQuoteId, conversionRate, destinationToken, + createEventBuilder, ], ); @@ -1011,7 +1017,9 @@ function SwapsQuotesView({ CHAIN_IDS.LINEA_SEPOLIA, ].includes(chainId) ) { - Logger.log('Delaying submitting trade tx to make Linea confirmation more likely',); + Logger.log( + 'Delaying submitting trade tx to make Linea confirmation more likely', + ); const waitPromise = new Promise((resolve) => setTimeout(resolve, 5000), ); @@ -1166,9 +1174,11 @@ function SwapsQuotesView({ custom_spend_limit_amount: currentAmount, chain_id: getDecimalChainId(chainId), }; - trackEvent(MetaMetricsEvents.EDIT_SPEND_LIMIT_OPENED, { - sensitiveProperties: { ...parameters }, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.EDIT_SPEND_LIMIT_OPENED) + .addSensitiveProperties(parameters) + .build(), + ); }, [ chainId, allQuotes, @@ -1185,6 +1195,7 @@ function SwapsQuotesView({ sourceAmount, sourceToken, trackEvent, + createEventBuilder, ]); const handleQuotesReceivedMetric = useCallback(() => { @@ -1214,9 +1225,11 @@ function SwapsQuotesView({ available_quotes: allQuotes.length, chain_id: getDecimalChainId(chainId), }; - trackEvent(MetaMetricsEvents.QUOTES_RECEIVED, { - sensitiveProperties: { ...parameters }, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.QUOTES_RECEIVED) + .addSensitiveProperties(parameters) + .build(), + ); }, [ chainId, sourceToken, @@ -1230,6 +1243,7 @@ function SwapsQuotesView({ allQuotes, conversionRate, trackEvent, + createEventBuilder, ]); const handleOpenQuotesModal = useCallback(() => { @@ -1261,9 +1275,11 @@ function SwapsQuotesView({ chain_id: getDecimalChainId(chainId), }; - trackEvent(MetaMetricsEvents.ALL_AVAILABLE_QUOTES_OPENED, { - sensitiveProperties: { ...parameters }, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ALL_AVAILABLE_QUOTES_OPENED) + .addSensitiveProperties(parameters) + .build(), + ); }, [ chainId, selectedQuote, @@ -1278,6 +1294,7 @@ function SwapsQuotesView({ conversionRate, allQuotes.length, trackEvent, + createEventBuilder, ]); const handleQuotesErrorMetric = useCallback( @@ -1300,16 +1317,20 @@ function SwapsQuotesView({ gas_fees: '', }; - trackEvent(MetaMetricsEvents.QUOTES_TIMED_OUT, { - sensitiveProperties: { ...parameters }, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.QUOTES_TIMED_OUT) + .addSensitiveProperties(parameters) + .build(), + ); } else if ( error?.key === swapsUtils.SwapsError.QUOTES_NOT_AVAILABLE_ERROR ) { const parameters = { ...data }; - trackEvent(MetaMetricsEvents.NO_QUOTES_AVAILABLE, { - sensitiveProperties: { ...parameters }, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NO_QUOTES_AVAILABLE) + .addSensitiveProperties(parameters) + .build(), + ); } else { trackErrorAsAnalytics(`Swaps: ${error?.key}`, error?.description); } @@ -1322,6 +1343,7 @@ function SwapsQuotesView({ hasEnoughTokenBalance, slippage, trackEvent, + createEventBuilder, ], ); @@ -1339,8 +1361,12 @@ function SwapsQuotesView({ Logger.error(error, 'Navigation: Error when navigating to buy ETH.'); } - trackEvent(MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST); - }, [navigation, trackEvent]); + trackEvent( + createEventBuilder( + MetaMetricsEvents.RECEIVE_OPTIONS_PAYMENT_REQUEST, + ).build(), + ); + }, [navigation, trackEvent, createEventBuilder]); const handleTermsPress = useCallback( () => @@ -1586,9 +1612,11 @@ function SwapsQuotesView({ navigation.setParams({ selectedQuote: undefined }); navigation.setParams({ quoteBegin: Date.now() }); - trackEvent(MetaMetricsEvents.QUOTES_REQUESTED, { - sensitiveProperties: { ...data }, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.QUOTES_REQUESTED) + .addSensitiveProperties(data) + .build(), + ); }, [ chainId, destinationToken, @@ -1600,6 +1628,7 @@ function SwapsQuotesView({ sourceToken, trackedRequestedQuotes, trackEvent, + createEventBuilder, ]); /* Metrics: Quotes received */ @@ -2156,7 +2185,11 @@ function SwapsQuotesView({ )} - + {strings('swaps.swap')} diff --git a/app/components/UI/Swaps/components/TokenSelectModal.js b/app/components/UI/Swaps/components/TokenSelectModal.js index ac620379b1b..3ed13e4e92d 100644 --- a/app/components/UI/Swaps/components/TokenSelectModal.js +++ b/app/components/UI/Swaps/components/TokenSelectModal.js @@ -157,7 +157,7 @@ function TokenSelectModal({ balances, }) { const navigation = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const searchInput = useRef(null); const list = useRef(); @@ -304,17 +304,25 @@ function TokenSelectModal({ const handlePressImportToken = useCallback( (item) => { const { address, symbol } = item; - trackEvent(MetaMetricsEvents.CUSTOM_TOKEN_IMPORTED, { - sensitiveProperties: { - address, - symbol, - chain_id: getDecimalChainId(chainId), - }, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.CUSTOM_TOKEN_IMPORTED) + .addSensitiveProperties({ + address, + symbol, + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); hideTokenImportModal(); onItemPress(item); }, - [chainId, hideTokenImportModal, onItemPress, trackEvent], + [ + chainId, + hideTokenImportModal, + onItemPress, + trackEvent, + createEventBuilder, + ], ); const handleBlockExplorerPress = useCallback(() => { diff --git a/app/components/UI/Swaps/index.js b/app/components/UI/Swaps/index.js index eeb33ec4e44..8874d0db584 100644 --- a/app/components/UI/Swaps/index.js +++ b/app/components/UI/Swaps/index.js @@ -197,7 +197,7 @@ function SwapsAmountView({ const navigation = useNavigation(); const route = useRoute(); const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); const previousSelectedAddress = useRef(); @@ -273,9 +273,11 @@ function SwapsAmountView({ chain_id: getDecimalChainId(chainId), }; - trackEvent(MetaMetricsEvents.SWAPS_OPENED, { - sensitiveProperties: { ...parameters }, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.SWAPS_OPENED) + .addSensitiveProperties(parameters) + .build(), + ); }); } else { navigation.pop(); diff --git a/app/components/UI/SwitchCustomNetwork/index.js b/app/components/UI/SwitchCustomNetwork/index.js index a6a55fb526c..eef8a1729c1 100644 --- a/app/components/UI/SwitchCustomNetwork/index.js +++ b/app/components/UI/SwitchCustomNetwork/index.js @@ -96,7 +96,7 @@ const SwitchCustomNetwork = ({ }) => { const { colors } = useTheme(); const styles = createStyles(colors); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { networkName } = useNetworkInfo( new URL(currentPageInformation.url).hostname, @@ -113,16 +113,23 @@ const SwitchCustomNetwork = ({ useEffect(() => { trackEvent( - MetaMetricsEvents.NETWORK_SWITCH_REQUESTED_AND_MODAL_SHOWN, - trackingData, + createEventBuilder( + MetaMetricsEvents.NETWORK_SWITCH_REQUESTED_AND_MODAL_SHOWN, + ) + .addProperties(trackingData) + .build(), ); - }, [trackEvent, trackingData]); + }, [trackEvent, trackingData, createEventBuilder]); /** * Calls onConfirm callback and analytics to track connect confirmed event */ const confirm = () => { - trackEvent(MetaMetricsEvents.NETWORK_SWITCH_CONFIRM_PRESSED, trackingData); + trackEvent( + createEventBuilder(MetaMetricsEvents.NETWORK_SWITCH_CONFIRM_PRESSED) + .addProperties(trackingData) + .build(), + ); onConfirm && onConfirm(); }; diff --git a/app/components/UI/Tabs/index.js b/app/components/UI/Tabs/index.js index 88fb50c12f9..5d4869dac08 100644 --- a/app/components/UI/Tabs/index.js +++ b/app/components/UI/Tabs/index.js @@ -228,7 +228,10 @@ class Tabs extends PureComponent { return ( - + {strings('browser.no_tabs_title')} {strings('browser.no_tabs_desc')} @@ -266,10 +269,15 @@ class Tabs extends PureComponent { }; trackNewTabEvent = (tabsNumber) => { - this.props.metrics.trackEvent(MetaMetricsEvents.BROWSER_NEW_TAB, { - option_chosen: 'Browser Bottom Bar Menu', - number_of_tabs: tabsNumber, - }); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.BROWSER_NEW_TAB) + .addProperties({ + option_chosen: 'Browser Bottom Bar Menu', + number_of_tabs: tabsNumber, + }) + .build(), + ); }; renderTabActions() { diff --git a/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx b/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx index 5da53e48066..c6afe5e217b 100644 --- a/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx +++ b/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx @@ -46,7 +46,7 @@ export const PortfolioBalance = () => { const styles = createStyles(colors); const balance = Engine.getTotalFiatAccountBalance(); const navigation = useNavigation(); - const { trackEvent, isEnabled } = useMetrics(); + const { trackEvent, isEnabled, createEventBuilder } = useMetrics(); const { type } = useSelector(selectProviderConfig); const chainId = useSelector(selectChainId); @@ -111,9 +111,13 @@ export const PortfolioBalance = () => { screen: Routes.BROWSER.VIEW, params, }); - trackEvent(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED, { - portfolioUrl: AppConstants.PORTFOLIO.URL, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED) + .addProperties({ + portfolioUrl: AppConstants.PORTFOLIO.URL, + }) + .build(), + ); }; const renderAggregatedPercentage = () => { diff --git a/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx b/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx index 92093347577..dbcddbc3059 100644 --- a/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx +++ b/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx @@ -42,7 +42,7 @@ export const TokenListFooter = ({ }: TokenListFooterProps) => { const navigation = useNavigation(); const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const [isNetworkRampSupported, isNativeTokenRampSupported] = useRampNetwork(); const detectedTokens = useSelector(selectDetectedTokens); @@ -60,11 +60,15 @@ export const TokenListFooter = ({ const goToBuy = () => { navigation.navigate(...createBuyNavigationDetails()); - trackEvent(MetaMetricsEvents.BUY_BUTTON_CLICKED, { - text: 'Buy Native Token', - location: 'Home Screen', - chain_id_destination: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.BUY_BUTTON_CLICKED) + .addProperties({ + text: 'Buy Native Token', + location: 'Home Screen', + chain_id_destination: getDecimalChainId(chainId), + }) + .build(), + ); }; return ( diff --git a/app/components/UI/Tokens/TokenList/index.tsx b/app/components/UI/Tokens/TokenList/index.tsx index e631513fbd3..41adb6d3407 100644 --- a/app/components/UI/Tokens/TokenList/index.tsx +++ b/app/components/UI/Tokens/TokenList/index.tsx @@ -50,7 +50,7 @@ export const TokenList = ({ StackNavigationProp >(); const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const chainId = useSelector(selectChainId); const detectedTokens = useSelector(selectDetectedTokens); @@ -62,13 +62,17 @@ export const TokenList = ({ const showDetectedTokens = () => { navigation.navigate(...createDetectedTokensNavDetails()); - trackEvent(MetaMetricsEvents.TOKEN_IMPORT_CLICKED, { - source: 'detected', - chain_id: getDecimalChainId(chainId), - tokens: detectedTokens?.map( - (token) => `${token.symbol} - ${token.address}`, - ), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKEN_IMPORT_CLICKED) + .addProperties({ + source: 'detected', + chain_id: getDecimalChainId(chainId), + tokens: detectedTokens?.map( + (token) => `${token.symbol} - ${token.address}`, + ), + }) + .build(), + ); setIsAddTokenEnabled(true); }; diff --git a/app/components/UI/UpdateNeeded/UpdateNeeded.tsx b/app/components/UI/UpdateNeeded/UpdateNeeded.tsx index 05c3382dfed..3113dcc9387 100644 --- a/app/components/UI/UpdateNeeded/UpdateNeeded.tsx +++ b/app/components/UI/UpdateNeeded/UpdateNeeded.tsx @@ -32,31 +32,49 @@ export const createUpdateNeededNavDetails = createNavigationDetails( const UpdateNeeded = () => { const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); const modalRef = useRef(null); useEffect(() => { - trackEvent(MetaMetricsEvents.FORCE_UPGRADE_UPDATE_NEEDED_PROMPT_VIEWED, { - ...generateDeviceAnalyticsMetaData(), - }); - }, [trackEvent]); + trackEvent( + createEventBuilder( + MetaMetricsEvents.FORCE_UPGRADE_UPDATE_NEEDED_PROMPT_VIEWED, + ) + .addProperties({ + ...generateDeviceAnalyticsMetaData(), + }) + .build(), + ); + }, [trackEvent, createEventBuilder]); const dismissModal = (cb?: () => void): void => modalRef?.current?.dismissModal(cb); const triggerClose = () => dismissModal(() => { - trackEvent(MetaMetricsEvents.FORCE_UPGRADE_REMIND_ME_LATER_CLICKED, { - ...generateDeviceAnalyticsMetaData(), - }); + trackEvent( + createEventBuilder( + MetaMetricsEvents.FORCE_UPGRADE_REMIND_ME_LATER_CLICKED, + ) + .addProperties({ + ...generateDeviceAnalyticsMetaData(), + }) + .build(), + ); }); const openAppStore = useCallback(() => { const link = Platform.OS === 'ios' ? MM_APP_STORE_LINK : MM_PLAY_STORE_LINK; trackEvent( - MetaMetricsEvents.FORCE_UPGRADE_UPDATE_TO_THE_LATEST_VERSION_CLICKED, - { ...generateDeviceAnalyticsMetaData(), link }, + createEventBuilder( + MetaMetricsEvents.FORCE_UPGRADE_UPDATE_TO_THE_LATEST_VERSION_CLICKED, + ) + .addProperties({ + ...generateDeviceAnalyticsMetaData(), + link, + }) + .build(), ); Linking.canOpenURL(link).then( @@ -65,7 +83,7 @@ const UpdateNeeded = () => { }, (err) => Logger.error(err, 'Unable to perform update'), ); - }, [trackEvent]); + }, [trackEvent, createEventBuilder]); const onUpdatePressed = useCallback(() => { dismissModal(openAppStore); diff --git a/app/components/Views/AccountActions/AccountActions.tsx b/app/components/Views/AccountActions/AccountActions.tsx index c44a1bc31a1..e8cd8e00345 100644 --- a/app/components/Views/AccountActions/AccountActions.tsx +++ b/app/components/Views/AccountActions/AccountActions.tsx @@ -67,7 +67,7 @@ const AccountActions = () => { const sheetRef = useRef(null); const { navigate } = useNavigation(); const dispatch = useDispatch(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const [blockingModalVisible, setBlockingModalVisible] = useState(false); @@ -123,7 +123,11 @@ const AccountActions = () => { goToBrowserUrl(url, etherscan_url); } - trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_VIEW_ETHERSCAN); + trackEvent( + createEventBuilder( + MetaMetricsEvents.NAVIGATION_TAPS_VIEW_ETHERSCAN, + ).build(), + ); }); }; @@ -139,13 +143,21 @@ const AccountActions = () => { Logger.log('Error while trying to share address', err); }); - trackEvent(MetaMetricsEvents.NAVIGATION_TAPS_SHARE_PUBLIC_ADDRESS); + trackEvent( + createEventBuilder( + MetaMetricsEvents.NAVIGATION_TAPS_SHARE_PUBLIC_ADDRESS, + ).build(), + ); }); }; const goToExportPrivateKey = () => { sheetRef.current?.onCloseBottomSheet(() => { - trackEvent(MetaMetricsEvents.REVEAL_PRIVATE_KEY_INITIATED); + trackEvent( + createEventBuilder( + MetaMetricsEvents.REVEAL_PRIVATE_KEY_INITIATED, + ).build(), + ); navigate(Routes.SETTINGS.REVEAL_PRIVATE_CREDENTIAL, { credentialName: 'private_key', @@ -183,16 +195,21 @@ const AccountActions = () => { if (selectedAddress) { await controllers.KeyringController.removeAccount(selectedAddress as Hex); await removeAccountsFromPermissions([selectedAddress]); - trackEvent(MetaMetricsEvents.ACCOUNT_REMOVED, { - accountType: keyring?.type, - selectedAddress, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ACCOUNT_REMOVED) + .addProperties({ + accountType: keyring?.type, + selectedAddress, + }) + .build(), + ); } }, [ controllers.KeyringController, keyring?.type, selectedAddress, trackEvent, + createEventBuilder, ]); /** @@ -214,16 +231,21 @@ const AccountActions = () => { if (selectedAddress) { await controllers.KeyringController.removeAccount(selectedAddress as Hex); await removeAccountsFromPermissions([selectedAddress]); - trackEvent(MetaMetricsEvents.ACCOUNT_REMOVED, { - accountType: keyring?.type, - selectedAddress, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ACCOUNT_REMOVED) + .addProperties({ + accountType: keyring?.type, + selectedAddress, + }) + .build(), + ); } }, [ controllers.KeyringController, keyring?.type, selectedAddress, trackEvent, + createEventBuilder, ]); const showRemoveSnapAccountAlert = useCallback(() => { @@ -272,21 +294,34 @@ const AccountActions = () => { switch (keyringType) { case ExtendedKeyringTypes.ledger: await forgetLedger(); - trackEvent(MetaMetricsEvents.HARDWARE_WALLET_FORGOTTEN, { - device_type: HardwareDeviceTypes.LEDGER, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.HARDWARE_WALLET_FORGOTTEN) + .addProperties({ + device_type: HardwareDeviceTypes.LEDGER, + }) + .build(), + ); break; case ExtendedKeyringTypes.qr: await controllers.KeyringController.forgetQRDevice(); - trackEvent(MetaMetricsEvents.HARDWARE_WALLET_FORGOTTEN, { - device_type: HardwareDeviceTypes.QR, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.HARDWARE_WALLET_FORGOTTEN) + .addProperties({ + device_type: HardwareDeviceTypes.QR, + }) + .build(), + ); break; default: break; } } - }, [controllers.KeyringController, keyring?.type, trackEvent]); + }, [ + controllers.KeyringController, + keyring?.type, + trackEvent, + createEventBuilder, + ]); /** * Trigger the remove hardware account action when user click on the remove account button diff --git a/app/components/Views/AccountBackupStep1B/index.js b/app/components/Views/AccountBackupStep1B/index.js index bae026a3e04..a2daeaf5740 100644 --- a/app/components/Views/AccountBackupStep1B/index.js +++ b/app/components/Views/AccountBackupStep1B/index.js @@ -26,6 +26,7 @@ import { MetaMetricsEvents } from '../../../core/Analytics'; import { useTheme } from '../../../util/theme'; import { ManualBackUpStepsSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ManualBackUpSteps.selectors'; import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; const explain_backup_seedphrase = require('../../../images/explain-backup-seedphrase.png'); // eslint-disable-line @@ -205,17 +206,17 @@ const AccountBackupStep1B = (props) => { const { colors } = useTheme(); const styles = createStyles(colors); - const track = (event, properties) => { - trackOnboarding(event, properties); - }; - useEffect(() => { navigation.setOptions(getOnboardingNavbarOptions(route, {}, colors)); }, [navigation, route, colors]); const goNext = () => { props.navigation.navigate('ManualBackupStep1', { ...props.route.params }); - track(MetaMetricsEvents.WALLET_SECURITY_MANUAL_BACKUP_INITIATED); + trackOnboarding( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.WALLET_SECURITY_MANUAL_BACKUP_INITIATED, + ).build(), + ); }; const learnMore = () => { diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index d13dde7fc17..e6e8d32f3fc 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -97,7 +97,7 @@ const AccountConnect = (props: AccountConnectProps) => { const { hostInfo, permissionRequestId } = props.route.params; const [isLoading, setIsLoading] = useState(false); const navigation = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const [blockedUrl, setBlockedUrl] = useState(''); @@ -366,12 +366,16 @@ const AccountConnect = (props: AccountConnectProps) => { }); } - trackEvent(MetaMetricsEvents.CONNECT_REQUEST_CANCELLED, { - number_of_accounts: accountsLength, - source: SourceType.PERMISSION_SYSTEM, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.CONNECT_REQUEST_CANCELLED) + .addProperties({ + number_of_accounts: accountsLength, + source: SourceType.PERMISSION_SYSTEM, + }) + .build(), + ); }, - [accountsLength, channelIdOrHostname, trackEvent], + [accountsLength, channelIdOrHostname, trackEvent, createEventBuilder], ); const navigateToUrlInEthPhishingModal = useCallback( @@ -444,12 +448,16 @@ const AccountConnect = (props: AccountConnectProps) => { triggerDappViewedEvent(connectedAccountLength); - trackEvent(MetaMetricsEvents.CONNECT_REQUEST_COMPLETED, { - number_of_accounts: accountsLength, - number_of_accounts_connected: connectedAccountLength, - account_type: getAddressAccountType(activeAddress), - source: eventSource, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.CONNECT_REQUEST_COMPLETED) + .addProperties({ + number_of_accounts: accountsLength, + number_of_accounts_connected: connectedAccountLength, + account_type: getAddressAccountType(activeAddress), + source: eventSource, + }) + .build(), + ); let labelOptions: ToastOptions['labelOptions'] = []; if (connectedAccountLength >= 1) { @@ -479,6 +487,7 @@ const AccountConnect = (props: AccountConnectProps) => { triggerDappViewedEvent, trackEvent, faviconSource, + createEventBuilder, ]); const handleCreateAccount = useCallback( @@ -491,7 +500,11 @@ const AccountConnect = (props: AccountConnectProps) => { addedAccountAddress, ) as string; !isMultiSelect && setSelectedAddresses([checksummedAddress]); - trackEvent(MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT); + trackEvent( + createEventBuilder( + MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT, + ).build(), + ); } catch (e) { if (e instanceof Error) { Logger.error(e, 'error while trying to add a new account'); @@ -500,7 +513,7 @@ const AccountConnect = (props: AccountConnectProps) => { setIsLoading(false); } }, - [trackEvent], + [trackEvent, createEventBuilder], ); const handleNetworksSelected = useCallback( @@ -562,13 +575,21 @@ const AccountConnect = (props: AccountConnectProps) => { case USER_INTENT.Import: { navigation.navigate('ImportPrivateKeyView'); // TODO: Confirm if this is where we want to track importing an account or within ImportPrivateKeyView screen. - trackEvent(MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT); + trackEvent( + createEventBuilder( + MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT, + ).build(), + ); break; } case USER_INTENT.ConnectHW: { navigation.navigate('ConnectQRHardwareFlow'); // TODO: Confirm if this is where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen. - trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET); + trackEvent( + createEventBuilder( + MetaMetricsEvents.CONNECT_HARDWARE_WALLET, + ).build(), + ); break; } @@ -588,6 +609,7 @@ const AccountConnect = (props: AccountConnectProps) => { handleConnect, trackEvent, handleUpdateNetworkPermissions, + createEventBuilder, ]); const handleSheetDismiss = () => { diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx index 42101ac25ef..4883a644c15 100755 --- a/app/components/Views/AccountPermissions/AccountPermissions.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx @@ -68,7 +68,7 @@ import { AvatarVariant } from '../../../component-library/components/Avatars/Ava const AccountPermissions = (props: AccountPermissionsProps) => { const navigation = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { hostInfo: { metadata: { origin: hostname }, @@ -279,11 +279,19 @@ const AccountPermissions = (props: AccountPermissionsProps) => { try { setIsLoading(true); await KeyringController.addNewAccount(); - trackEvent(MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT); - trackEvent(MetaMetricsEvents.SWITCHED_ACCOUNT, { - source: metricsSource, - number_of_accounts: accounts?.length, - }); + trackEvent( + createEventBuilder( + MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT, + ).build(), + ); + trackEvent( + createEventBuilder(MetaMetricsEvents.SWITCHED_ACCOUNT) + .addProperties({ + source: metricsSource, + number_of_accounts: accounts?.length, + }) + .build(), + ); } catch (e) { Logger.error(e as Error, 'Error while trying to add a new account.'); } finally { @@ -383,11 +391,15 @@ const AccountPermissions = (props: AccountPermissionsProps) => { hasNoTimeout: false, }); const totalAccounts = accountsLength; - trackEvent(MetaMetricsEvents.ADD_ACCOUNT_DAPP_PERMISSIONS, { - number_of_accounts: totalAccounts, - number_of_accounts_connected: connectedAccountLength, - number_of_networks: nonTestnetNetworks, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ADD_ACCOUNT_DAPP_PERMISSIONS) + .addProperties({ + number_of_accounts: totalAccounts, + number_of_accounts_connected: connectedAccountLength, + number_of_networks: nonTestnetNetworks, + }) + .build(), + ); } catch (e) { Logger.error(e as Error, 'Error while trying to connect to a dApp.'); } finally { @@ -405,6 +417,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { accountsLength, nonTestnetNetworks, trackEvent, + createEventBuilder, ]); useEffect(() => { @@ -436,10 +449,14 @@ const AccountPermissions = (props: AccountPermissionsProps) => { case USER_INTENT.Confirm: { handleConnect(); hideSheet(() => { - trackEvent(MetaMetricsEvents.SWITCHED_ACCOUNT, { - source: metricsSource, - number_of_accounts: accounts?.length, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.SWITCHED_ACCOUNT) + .addProperties({ + source: metricsSource, + number_of_accounts: accounts?.length, + }) + .build(), + ); }); break; } @@ -460,14 +477,22 @@ const AccountPermissions = (props: AccountPermissionsProps) => { case USER_INTENT.Import: { navigation.navigate('ImportPrivateKeyView'); // Is this where we want to track importing an account or within ImportPrivateKeyView screen? - trackEvent(MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT); + trackEvent( + createEventBuilder( + MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT, + ).build(), + ); break; } case USER_INTENT.ConnectHW: { navigation.navigate('ConnectQRHardwareFlow'); // Is this where we want to track connecting a hardware wallet or within ConnectQRHardwareFlow screen? - trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET); + trackEvent( + createEventBuilder( + MetaMetricsEvents.CONNECT_HARDWARE_WALLET, + ).build(), + ); break; } @@ -486,6 +511,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => { handleConnect, accounts?.length, trackEvent, + createEventBuilder, ]); const renderConnectedScreen = useCallback( diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx index dcb87efe829..018ebeccb98 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsConnected/AccountPermissionsConnected.tsx @@ -63,7 +63,7 @@ const AccountPermissionsConnected = ({ urlWithProtocol, }: AccountPermissionsConnectedProps) => { const { navigate } = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const providerConfig: ProviderConfig = useSelector(selectProviderConfig); @@ -121,10 +121,14 @@ const AccountPermissionsConnected = ({ screen: Routes.SHEET.NETWORK_SELECTOR, }); - trackEvent(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, { - chain_id: getDecimalChainId(providerConfig.chainId), - }); - }, [providerConfig.chainId, navigate, trackEvent]); + trackEvent( + createEventBuilder(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED) + .addProperties({ + chain_id: getDecimalChainId(providerConfig.chainId), + }) + .build(), + ); + }, [providerConfig.chainId, navigate, trackEvent, createEventBuilder]); const renderSheetAction = useCallback( () => ( diff --git a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx index 2fe5b03bf7f..69ffbe25cae 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx +++ b/app/components/Views/AccountPermissions/AccountPermissionsRevoke/AccountPermissionsRevoke.tsx @@ -54,7 +54,7 @@ const AccountPermissionsRevoke = ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any const Engine = UntypedEngine as any; const { styles } = useStyles(styleSheet, {}); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const activeAddress = permittedAddresses[0]; const { toastRef } = useContext(ToastContext); @@ -72,11 +72,15 @@ const AccountPermissionsRevoke = ({ await Engine.context.PermissionController.revokeAllPermissions( hostname, ); - trackEvent(MetaMetricsEvents.REVOKE_ACCOUNT_DAPP_PERMISSIONS, { - number_of_accounts: accountsLength, - number_of_accounts_connected: permittedAddresses.length, - number_of_networks: nonTestnetNetworks, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.REVOKE_ACCOUNT_DAPP_PERMISSIONS) + .addProperties({ + number_of_accounts: accountsLength, + number_of_accounts_connected: permittedAddresses.length, + number_of_networks: nonTestnetNetworks, + }) + .build(), + ); } catch (e) { Logger.log(`Failed to revoke all accounts for ${hostname}`, e); } @@ -182,11 +186,17 @@ const AccountPermissionsRevoke = ({ hasNoTimeout: false, }); } - trackEvent(MetaMetricsEvents.REVOKE_ACCOUNT_DAPP_PERMISSIONS, { - number_of_accounts: accountsLength, - number_of_accounts_connected: permittedAddresses.length, - number_of_networks: nonTestnetNetworks, - }); + trackEvent( + createEventBuilder( + MetaMetricsEvents.REVOKE_ACCOUNT_DAPP_PERMISSIONS, + ) + .addProperties({ + number_of_accounts: accountsLength, + number_of_accounts_connected: permittedAddresses.length, + number_of_networks: nonTestnetNetworks, + }) + .build(), + ); } }} label={strings('accounts.disconnect')} diff --git a/app/components/Views/AccountSelector/AccountSelector.tsx b/app/components/Views/AccountSelector/AccountSelector.tsx index 656c54a8673..15d7573f4fd 100644 --- a/app/components/Views/AccountSelector/AccountSelector.tsx +++ b/app/components/Views/AccountSelector/AccountSelector.tsx @@ -39,7 +39,7 @@ import { TraceName, endTrace } from '../../../util/trace'; const AccountSelector = ({ route }: AccountSelectorProps) => { const dispatch = useDispatch(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { onSelectAccount, checkBalanceError, privacyMode } = route.params || {}; @@ -71,12 +71,16 @@ const AccountSelector = ({ route }: AccountSelectorProps) => { onSelectAccount?.(address); // Track Event: "Switched Account" - trackEvent(MetaMetricsEvents.SWITCHED_ACCOUNT, { - source: 'Wallet Tab', - number_of_accounts: accounts?.length, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.SWITCHED_ACCOUNT) + .addProperties({ + source: 'Wallet Tab', + number_of_accounts: accounts?.length, + }) + .build(), + ); }, - [Engine, accounts?.length, onSelectAccount, trackEvent], + [Engine, accounts?.length, onSelectAccount, trackEvent, createEventBuilder], ); const onRemoveImportedAccount = useCallback( diff --git a/app/components/Views/ActivityView/index.js b/app/components/Views/ActivityView/index.js index fcb7769df8d..1d4f396036e 100644 --- a/app/components/Views/ActivityView/index.js +++ b/app/components/Views/ActivityView/index.js @@ -26,7 +26,7 @@ const styles = StyleSheet.create({ const ActivityView = () => { const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const navigation = useNavigation(); const selectedAddress = useSelector( selectSelectedInternalAccountChecksummedAddress, @@ -41,11 +41,22 @@ const ActivityView = () => { screen: Routes.SHEET.ACCOUNT_SELECTOR, }); // Track Event: "Opened Acount Switcher" - trackEvent(MetaMetricsEvents.BROWSER_OPEN_ACCOUNT_SWITCH, { - number_of_accounts: Object.keys(accountsByChainId[selectedAddress] ?? {}) - .length, - }); - }, [navigation, accountsByChainId, selectedAddress, trackEvent]); + trackEvent( + createEventBuilder(MetaMetricsEvents.BROWSER_OPEN_ACCOUNT_SWITCH) + .addProperties({ + number_of_accounts: Object.keys( + accountsByChainId[selectedAddress] ?? {}, + ).length, + }) + .build(), + ); + }, [ + navigation, + accountsByChainId, + selectedAddress, + trackEvent, + createEventBuilder, + ]); useEffect( () => { diff --git a/app/components/Views/AddAccountActions/AddAccountActions.tsx b/app/components/Views/AddAccountActions/AddAccountActions.tsx index eddc7634068..056ae045d51 100644 --- a/app/components/Views/AddAccountActions/AddAccountActions.tsx +++ b/app/components/Views/AddAccountActions/AddAccountActions.tsx @@ -20,20 +20,26 @@ import { useMetrics } from '../../../components/hooks/useMetrics'; const AddAccountActions = ({ onBack }: AddAccountActionsProps) => { const { navigate } = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const [isLoading, setIsLoading] = useState(false); const openImportAccount = useCallback(() => { navigate('ImportPrivateKeyView'); onBack(); - trackEvent(MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT, {}); - }, [navigate, onBack, trackEvent]); + trackEvent( + createEventBuilder( + MetaMetricsEvents.ACCOUNTS_IMPORTED_NEW_ACCOUNT, + ).build(), + ); + }, [navigate, onBack, trackEvent, createEventBuilder]); const openConnectHardwareWallet = useCallback(() => { navigate(Routes.HW.CONNECT); onBack(); - trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET, {}); - }, [onBack, navigate, trackEvent]); + trackEvent( + createEventBuilder(MetaMetricsEvents.CONNECT_HARDWARE_WALLET).build(), + ); + }, [onBack, navigate, trackEvent, createEventBuilder]); const createNewAccount = useCallback(async () => { const { KeyringController } = Engine.context; @@ -42,7 +48,11 @@ const AddAccountActions = ({ onBack }: AddAccountActionsProps) => { const addedAccountAddress = await KeyringController.addNewAccount(); Engine.setSelectedAddress(addedAccountAddress); - trackEvent(MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT, {}); + trackEvent( + createEventBuilder( + MetaMetricsEvents.ACCOUNTS_ADDED_NEW_ACCOUNT, + ).build(), + ); // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { @@ -52,7 +62,7 @@ const AddAccountActions = ({ onBack }: AddAccountActionsProps) => { setIsLoading(false); } - }, [onBack, setIsLoading, trackEvent]); + }, [onBack, setIsLoading, trackEvent, createEventBuilder]); return ( diff --git a/app/components/Views/AssetDetails/index.tsx b/app/components/Views/AssetDetails/index.tsx index bb97c33b30a..c4e7d682c09 100644 --- a/app/components/Views/AssetDetails/index.tsx +++ b/app/components/Views/AssetDetails/index.tsx @@ -107,7 +107,7 @@ interface Props { const AssetDetails = (props: Props) => { const { address } = props.route.params; const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); const navigation = useNavigation(); const dispatch = useDispatch(); @@ -182,13 +182,17 @@ const AssetDetails = (props: Props) => { tokenSymbol: symbol, }), }); - trackEvent(MetaMetricsEvents.TOKENS_HIDDEN, { - location: 'token_details', - token_standard: 'ERC20', - asset_type: 'token', - tokens: [`${symbol} - ${address}`], - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKENS_HIDDEN) + .addProperties({ + location: 'token_details', + token_standard: 'ERC20', + asset_type: 'token', + tokens: [`${symbol} - ${address}`], + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); } catch (err) { Logger.log(err, 'AssetDetails: Failed to hide token!'); } diff --git a/app/components/Views/AssetOptions/AssetOptions.tsx b/app/components/Views/AssetOptions/AssetOptions.tsx index c6c12d101a1..d19a4d4c749 100644 --- a/app/components/Views/AssetOptions/AssetOptions.tsx +++ b/app/components/Views/AssetOptions/AssetOptions.tsx @@ -59,7 +59,7 @@ const AssetOptions = (props: Props) => { (state: RootState) => state.security.dataCollectionForMarketing, ); const explorer = useBlockExplorer(providerConfig, networkConfigurations); - const { trackEvent, isEnabled } = useMetrics(); + const { trackEvent, isEnabled, createEventBuilder } = useMetrics(); const goToBrowserUrl = (url: string, title: string) => { modalRef.current?.dismissModal(() => { @@ -128,9 +128,13 @@ const AssetOptions = (props: Props) => { screen: Routes.BROWSER.VIEW, params, }); - trackEvent(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED, { - portfolioUrl: AppConstants.PORTFOLIO.URL, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED) + .addProperties({ + portfolioUrl: AppConstants.PORTFOLIO.URL, + }) + .build(), + ); }; const removeToken = () => { @@ -151,15 +155,21 @@ const AssetOptions = (props: Props) => { tokenSymbol: tokenList[address.toLowerCase()]?.symbol || null, }), }); - trackEvent(MetaMetricsEvents.TOKENS_HIDDEN, { - location: 'token_details', - token_standard: 'ERC20', - asset_type: 'token', - tokens: [ - `${tokenList[address.toLowerCase()]?.symbol} - ${address}`, - ], - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKENS_HIDDEN) + .addProperties({ + location: 'token_details', + token_standard: 'ERC20', + asset_type: 'token', + tokens: [ + `${ + tokenList[address.toLowerCase()]?.symbol + } - ${address}`, + ], + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); } catch (err) { Logger.log(err, 'AssetDetails: Failed to hide token!'); } diff --git a/app/components/Views/Browser/index.js b/app/components/Views/Browser/index.js index 9a32683dbb5..8d60dbfbd8b 100644 --- a/app/components/Views/Browser/index.js +++ b/app/components/Views/Browser/index.js @@ -63,7 +63,7 @@ export const Browser = (props) => { } = props; const previousTabs = useRef(null); const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { toastRef } = useContext(ToastContext); const browserUrl = props.route?.params?.url; const linkType = props.route?.params?.linkType; @@ -93,11 +93,15 @@ export const Browser = (props) => { }, isEqual); const handleRightTopButtonAnalyticsEvent = () => { - trackEvent(MetaMetricsEvents.OPEN_DAPP_PERMISSIONS, { - number_of_accounts: accountsLength, - number_of_accounts_connected: permittedAccountsList.length, - number_of_networks: nonTestnetNetworks, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.OPEN_DAPP_PERMISSIONS) + .addProperties({ + number_of_accounts: accountsLength, + number_of_accounts_connected: permittedAccountsList.length, + number_of_networks: nonTestnetNetworks, + }) + .build(), + ); }; useEffect( @@ -133,7 +137,9 @@ export const Browser = (props) => { }; const switchToTab = (tab) => { - trackEvent(MetaMetricsEvents.BROWSER_SWITCH_TAB, {}); + trackEvent( + createEventBuilder(MetaMetricsEvents.BROWSER_SWITCH_TAB).build(), + ); setActiveTab(tab.id); hideTabsAndUpdateUrl(tab.url); updateTabInfo(tab.url, tab.id); diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index 7910f9b52ca..edc4e0f5f79 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -399,8 +399,15 @@ export const BrowserTab = (props) => { dismissTextSelectionIfNeeded(); setShowOptions(!showOptions); - trackEvent(MetaMetricsEvents.DAPP_BROWSER_OPTIONS); - }, [dismissTextSelectionIfNeeded, showOptions, trackEvent]); + trackEvent( + createEventBuilder(MetaMetricsEvents.DAPP_BROWSER_OPTIONS).build(), + ); + }, [ + dismissTextSelectionIfNeeded, + showOptions, + trackEvent, + createEventBuilder, + ]); /** * Show the options menu @@ -852,11 +859,15 @@ export const BrowserTab = (props) => { ); const trackEventSearchUsed = useCallback(() => { - trackEvent(MetaMetricsEvents.BROWSER_SEARCH_USED, { - option_chosen: 'Search on URL', - number_of_tabs: undefined, - }); - }, [trackEvent]); + trackEvent( + createEventBuilder(MetaMetricsEvents.BROWSER_SEARCH_USED) + .addProperties({ + option_chosen: 'Search on URL', + number_of_tabs: undefined, + }) + .build(), + ); + }, [trackEvent, createEventBuilder]); /** * Function that allows custom handling of any web view requests. @@ -977,7 +988,7 @@ export const BrowserTab = (props) => { toggleOptionsIfNeeded(); if (url.current === HOMEPAGE_URL) return reload(); await go(HOMEPAGE_URL); - trackEvent(MetaMetricsEvents.DAPP_HOME); + trackEvent(createEventBuilder(MetaMetricsEvents.DAPP_HOME).build()); }; /** @@ -1129,9 +1140,13 @@ export const BrowserTab = (props) => { error, setAccountsPermissionsVisible: () => { // Track Event: "Opened Acount Switcher" - trackEvent(MetaMetricsEvents.BROWSER_OPEN_ACCOUNT_SWITCH, { - number_of_accounts: accounts?.length, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.BROWSER_OPEN_ACCOUNT_SWITCH) + .addProperties({ + number_of_accounts: accounts?.length, + }) + .build(), + ); props.navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.ACCOUNT_PERMISSIONS, params: { @@ -1202,33 +1217,43 @@ export const BrowserTab = (props) => { * Track new tab event */ const trackNewTabEvent = () => { - trackEvent(MetaMetricsEvents.BROWSER_NEW_TAB, { - option_chosen: 'Browser Options', - number_of_tabs: undefined, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.BROWSER_NEW_TAB) + .addProperties({ + option_chosen: 'Browser Options', + number_of_tabs: undefined, + }) + .build(), + ); }; /** * Track add site to favorites event */ const trackAddToFavoritesEvent = () => { - trackEvent(MetaMetricsEvents.BROWSER_ADD_FAVORITES, { - dapp_name: title.current || '', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.BROWSER_ADD_FAVORITES) + .addProperties({ + dapp_name: title.current || '', + }) + .build(), + ); }; /** * Track share site event */ const trackShareEvent = () => { - trackEvent(MetaMetricsEvents.BROWSER_SHARE_SITE); + trackEvent( + createEventBuilder(MetaMetricsEvents.BROWSER_SHARE_SITE).build(), + ); }; /** * Track reload site event */ const trackReloadEvent = () => { - trackEvent(MetaMetricsEvents.BROWSER_RELOAD); + trackEvent(createEventBuilder(MetaMetricsEvents.BROWSER_RELOAD).build()); }; /** @@ -1263,7 +1288,9 @@ export const BrowserTab = (props) => { }, }); trackAddToFavoritesEvent(); - trackEvent(MetaMetricsEvents.DAPP_ADD_TO_FAVORITE); + trackEvent( + createEventBuilder(MetaMetricsEvents.DAPP_ADD_TO_FAVORITE).build(), + ); }; /** @@ -1290,7 +1317,9 @@ export const BrowserTab = (props) => { error, ), ); - trackEvent(MetaMetricsEvents.DAPP_OPEN_IN_BROWSER); + trackEvent( + createEventBuilder(MetaMetricsEvents.DAPP_OPEN_IN_BROWSER).build(), + ); }; /** diff --git a/app/components/Views/ConnectHardware/SelectHardware/index.tsx b/app/components/Views/ConnectHardware/SelectHardware/index.tsx index 91a1ad19309..45e33401dc3 100644 --- a/app/components/Views/ConnectHardware/SelectHardware/index.tsx +++ b/app/components/Views/ConnectHardware/SelectHardware/index.tsx @@ -87,7 +87,7 @@ const qrHardwareLogoDark = require(qrHardwareLogoDarkImgPath); const SelectHardwareWallet = () => { const navigation = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { colors } = useAppThemeFromContext() || mockTheme; const styles = createStyle(colors); @@ -107,9 +107,13 @@ const SelectHardwareWallet = () => { }; const navigateToConnectLedger = async () => { - trackEvent(MetaMetricsEvents.CONNECT_LEDGER, { - device_type: HardwareDeviceTypes.LEDGER, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.CONNECT_LEDGER) + .addProperties({ + device_type: HardwareDeviceTypes.LEDGER, + }) + .build(), + ); navigation.navigate(Routes.HW.CONNECT_LEDGER); }; diff --git a/app/components/Views/ConnectQRHardware/index.tsx b/app/components/Views/ConnectQRHardware/index.tsx index a420e9e312a..b028495afc3 100644 --- a/app/components/Views/ConnectQRHardware/index.tsx +++ b/app/components/Views/ConnectQRHardware/index.tsx @@ -133,7 +133,7 @@ async function initiateQRHardwareConnection( const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => { const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); const KeyringController = useMemo(() => { @@ -215,9 +215,13 @@ const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => { >(); const onConnectHardware = useCallback(async () => { - trackEvent(MetaMetricsEvents.CONTINUE_QR_HARDWARE_WALLET, { - device_type: HardwareDeviceTypes.QR, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.CONTINUE_QR_HARDWARE_WALLET) + .addProperties({ + device_type: HardwareDeviceTypes.QR, + }) + .build(), + ); resetError(); const [qrInteractions, connectQRHardwarePromise] = await initiateQRHardwareConnection(PAGINATION_OPERATIONS.GET_FIRST_PAGE); @@ -227,14 +231,18 @@ const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => { delete qrInteractionsRef.current; setAccounts(firstPageAccounts); - }, [resetError, trackEvent]); + }, [resetError, trackEvent, createEventBuilder]); const onScanSuccess = useCallback( (ur: UR) => { hideScanner(); - trackEvent(MetaMetricsEvents.CONNECT_HARDWARE_WALLET_SUCCESS, { - device_type: HardwareDeviceTypes.QR, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.CONNECT_HARDWARE_WALLET_SUCCESS) + .addProperties({ + device_type: HardwareDeviceTypes.QR, + }) + .build(), + ); if (!qrInteractionsRef.current) { const errorMessage = 'Missing QR keyring interactions'; setErrorMsg(errorMessage); @@ -247,7 +255,7 @@ const ConnectQRHardware = ({ navigation }: IConnectQRHardwareProps) => { } resetError(); }, - [hideScanner, resetError, trackEvent], + [hideScanner, resetError, trackEvent, createEventBuilder], ); const onScanError = useCallback( diff --git a/app/components/Views/DetectedTokens/index.tsx b/app/components/Views/DetectedTokens/index.tsx index 4df619a584f..6d041266219 100644 --- a/app/components/Views/DetectedTokens/index.tsx +++ b/app/components/Views/DetectedTokens/index.tsx @@ -76,7 +76,7 @@ interface IgnoredTokensByAddress { const DetectedTokens = () => { const navigation = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const sheetRef = useRef(null); const detectedTokens = useSelector(selectDetectedTokens); const chainId = useSelector(selectChainId); @@ -137,12 +137,16 @@ const DetectedTokens = () => { await TokensController.addTokens(tokensToImport, networkClientId); InteractionManager.runAfterInteractions(() => tokensToImport.forEach(({ address, symbol }) => - trackEvent(MetaMetricsEvents.TOKEN_ADDED, { - token_address: address, - token_symbol: symbol, - chain_id: getDecimalChainId(chainId), - source: 'detected', - }), + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKEN_ADDED) + .addProperties({ + token_address: address, + token_symbol: symbol, + chain_id: getDecimalChainId(chainId), + source: 'detected', + }) + .build(), + ), ), ); } @@ -157,7 +161,14 @@ const DetectedTokens = () => { } }); }, - [chainId, detectedTokens, ignoredTokens, trackEvent, networkClientId], + [ + chainId, + detectedTokens, + ignoredTokens, + trackEvent, + networkClientId, + createEventBuilder, + ], ); const triggerIgnoreAllTokens = () => { @@ -166,13 +177,17 @@ const DetectedTokens = () => { isHidingAll: true, }); - trackEvent(MetaMetricsEvents.TOKENS_HIDDEN, { - location: 'token_detection', - token_standard: 'ERC20', - asset_type: 'token', - tokens: detectedTokensForAnalytics, - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKENS_HIDDEN) + .addProperties({ + location: 'token_detection', + token_standard: 'ERC20', + asset_type: 'token', + tokens: detectedTokensForAnalytics, + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); }; const triggerImportTokens = async () => { @@ -263,11 +278,15 @@ const DetectedTokens = () => { if (hasPendingAction) { return; } - trackEvent(MetaMetricsEvents.TOKEN_IMPORT_CANCELED, { - source: 'detected', - tokens: detectedTokensForAnalytics, - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKEN_IMPORT_CANCELED) + .addProperties({ + source: 'detected', + tokens: detectedTokensForAnalytics, + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); }; return ( diff --git a/app/components/Views/EditAccountName/EditAccountName.tsx b/app/components/Views/EditAccountName/EditAccountName.tsx index 2d6c0bacc22..3d6ac8af8cd 100644 --- a/app/components/Views/EditAccountName/EditAccountName.tsx +++ b/app/components/Views/EditAccountName/EditAccountName.tsx @@ -57,7 +57,7 @@ const EditAccountName = () => { const route = useRoute(); const { selectedAccount } = route.params; const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { styles } = useStyles(styleSheet, {}); const { setOptions, goBack, navigate } = useNavigation(); const [accountName, setAccountName] = useState(); @@ -116,7 +116,11 @@ const EditAccountName = () => { return { account_type, chain_id: getDecimalChainId(chainId) }; }; const analyticsProps = await analyticsProperties(); - trackEvent(MetaMetricsEvents.ACCOUNT_RENAMED, analyticsProps); + trackEvent( + createEventBuilder(MetaMetricsEvents.ACCOUNT_RENAMED) + .addProperties({ ...analyticsProps }) + .build(), + ); } catch { return {}; } diff --git a/app/components/Views/ErrorBoundary/index.js b/app/components/Views/ErrorBoundary/index.js index f300d534ea3..a0c7af42270 100644 --- a/app/components/Views/ErrorBoundary/index.js +++ b/app/components/Views/ErrorBoundary/index.js @@ -415,7 +415,7 @@ class ErrorBoundary extends Component { generateErrorReport = (error, errorInfo = '') => { const { view, - metrics: { trackEvent }, + metrics: { trackEvent, createEventBuilder }, } = this.props; const analyticsParams = { error: error?.toString(), boundary: view }; // Organize stack trace @@ -425,7 +425,11 @@ class ErrorBoundary extends Component { // Limit to 5 levels analyticsParams.stack = stackList.slice(1, 5).join(', '); - trackEvent(MetaMetricsEvents.ERROR_SCREEN_VIEWED, analyticsParams); + trackEvent( + createEventBuilder(MetaMetricsEvents.ERROR_SCREEN_VIEWED) + .addProperties(analyticsParams) + .build(), + ); }; componentDidCatch(error, errorInfo) { diff --git a/app/components/Views/ErrorBoundary/index.test.tsx b/app/components/Views/ErrorBoundary/index.test.tsx index 1c6f6547f4c..e61bff0e3fb 100644 --- a/app/components/Views/ErrorBoundary/index.test.tsx +++ b/app/components/Views/ErrorBoundary/index.test.tsx @@ -2,8 +2,10 @@ import React, { useEffect } from 'react'; import { View } from 'react-native'; import renderWithProvider from '../../../util/test/renderWithProvider'; import ErrorBoundary from './'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; const mockTrackEvent = jest.fn(); +const mockCreateEventBuilder = MetricsEventBuilder.createEventBuilder; jest.mock('../../../components/hooks/useMetrics', () => ({ ...jest.requireActual('../../../components/hooks/useMetrics'), @@ -12,7 +14,13 @@ jest.mock('../../../components/hooks/useMetrics', () => ({ // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any .mockImplementation((Children) => (props: any) => ( - + )), })); diff --git a/app/components/Views/ExperienceEnhancerModal/index.tsx b/app/components/Views/ExperienceEnhancerModal/index.tsx index 81ddaecb605..c3b8eccffee 100644 --- a/app/components/Views/ExperienceEnhancerModal/index.tsx +++ b/app/components/Views/ExperienceEnhancerModal/index.tsx @@ -29,7 +29,7 @@ import { ExperienceEnhancerModalSelectorsIDs } from '../../../../e2e/selectors/M const ExperienceEnhancerModal = () => { const dispatch = useDispatch(); const styles = createStyles(); - const { trackEvent, addTraitsToUser } = useMetrics(); + const { trackEvent, addTraitsToUser, createEventBuilder } = useMetrics(); const bottomSheetRef = useRef(null); const cancelButtonProps: ButtonProps = { @@ -44,10 +44,14 @@ const ExperienceEnhancerModal = () => { has_marketing_consent: false, }; addTraitsToUser(traits); - trackEvent(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, { - ...traits, - location: 'marketing_consent_modal', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED) + .addProperties({ + ...traits, + location: 'marketing_consent_modal', + }) + .build(), + ); }, testID: ExperienceEnhancerModalSelectorsIDs.CANCEL_BUTTON, }; @@ -62,10 +66,14 @@ const ExperienceEnhancerModal = () => { const traits = { has_marketing_consent: true }; addTraitsToUser(traits); - trackEvent(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED, { - ...traits, - location: 'marketing_consent_modal', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.ANALYTICS_PREFERENCE_SELECTED) + .addProperties({ + ...traits, + location: 'marketing_consent_modal', + }) + .build(), + ); }, testID: ExperienceEnhancerModalSelectorsIDs.ACCEPT_BUTTON, }; diff --git a/app/components/Views/LedgerConnect/Scan.test.tsx b/app/components/Views/LedgerConnect/Scan.test.tsx index b0e1620232b..85f6c6e70d8 100644 --- a/app/components/Views/LedgerConnect/Scan.test.tsx +++ b/app/components/Views/LedgerConnect/Scan.test.tsx @@ -7,7 +7,11 @@ import useBluetooth from '../../hooks/Ledger/useBluetooth'; import { BluetoothPermissionErrors } from '../../../core/Ledger/ledgerErrors'; import { fireEvent } from '@testing-library/react-native'; import { SELECT_DROP_DOWN } from '../../UI/SelectOptionSheet/constants'; -import { NavigationProp, ParamListBase, useNavigation } from '@react-navigation/native'; +import { + NavigationProp, + ParamListBase, + useNavigation, +} from '@react-navigation/native'; jest.mock('../../hooks/Ledger/useBluetooth'); jest.mock('../../hooks/Ledger/useBluetoothDevices'); @@ -197,7 +201,9 @@ describe('Scan', () => { const onDeviceSelected = jest.fn(); const navigateMock = { - navigate: jest.fn().mockImplementation(() => {onDeviceSelected(device2)}), + navigate: jest.fn().mockImplementation(() => { + onDeviceSelected(device2); + }), } as unknown as NavigationProp; jest.mocked(useNavigation).mockReturnValue(navigateMock); @@ -206,7 +212,7 @@ describe('Scan', () => { deviceScanError: false, }); - const {getByTestId} = renderWithProvider( + const { getByTestId } = renderWithProvider( { expect(onDeviceSelected).toHaveBeenCalledWith(device2); }); - }); diff --git a/app/components/Views/LedgerSelectAccount/index.tsx b/app/components/Views/LedgerSelectAccount/index.tsx index 9e2b8656260..19945f1b99e 100644 --- a/app/components/Views/LedgerSelectAccount/index.tsx +++ b/app/components/Views/LedgerSelectAccount/index.tsx @@ -53,7 +53,7 @@ const LedgerSelectAccount = () => { const [errorMsg, setErrorMsg] = useState(null); const dispatch = useDispatch(); const { colors } = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); const ledgerThemedImage = useAssetFromTheme( ledgerDeviceLightImage, @@ -140,15 +140,19 @@ const LedgerSelectAccount = () => { const onConnectHardware = useCallback(async () => { setErrorMsg(null); - trackEvent(MetaMetricsEvents.CONTINUE_LEDGER_HARDWARE_WALLET, { - device_type: HardwareDeviceTypes.LEDGER, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.CONTINUE_LEDGER_HARDWARE_WALLET) + .addProperties({ + device_type: HardwareDeviceTypes.LEDGER, + }) + .build(), + ); const _accounts = await getLedgerAccountsByOperation( PAGINATION_OPERATIONS.GET_FIRST_PAGE, ); setAccounts(_accounts); - }, [trackEvent]); + }, [trackEvent, createEventBuilder]); useEffect(() => { if (accounts.length > 0 && selectedOption) { @@ -233,10 +237,14 @@ const LedgerSelectAccount = () => { setBlockingModalVisible(false); } - trackEvent(MetaMetricsEvents.CONNECT_LEDGER_SUCCESS, { - device_type: HardwareDeviceTypes.LEDGER, - hd_path: getPathString(selectedOption.value), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.CONNECT_LEDGER_SUCCESS) + .addProperties({ + device_type: HardwareDeviceTypes.LEDGER, + hd_path: getPathString(selectedOption.value), + }) + .build(), + ); navigation.pop(2); }, [ @@ -244,6 +252,7 @@ const LedgerSelectAccount = () => { selectedOption.value, trackEvent, updateNewLegacyAccountsLabel, + createEventBuilder, ], ); @@ -251,12 +260,16 @@ const LedgerSelectAccount = () => { showLoadingModal(); await forgetLedger(); dispatch(setReloadAccounts(true)); - trackEvent(MetaMetricsEvents.HARDWARE_WALLET_FORGOTTEN, { - device_type: HardwareDeviceTypes.LEDGER, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.HARDWARE_WALLET_FORGOTTEN) + .addProperties({ + device_type: HardwareDeviceTypes.LEDGER, + }) + .build(), + ); setBlockingModalVisible(false); navigation.dispatch(StackActions.pop(2)); - }, [dispatch, navigation, trackEvent]); + }, [dispatch, navigation, trackEvent, createEventBuilder]); const onAnimationCompleted = useCallback(async () => { if (!blockingModalVisible) { diff --git a/app/components/Views/Login/index.js b/app/components/Views/Login/index.js index 20664341975..0f157b92406 100644 --- a/app/components/Views/Login/index.js +++ b/app/components/Views/Login/index.js @@ -265,7 +265,11 @@ class Login extends PureComponent { parentContext: this.parentSpan, }); - this.props.metrics.trackEvent(MetaMetricsEvents.LOGIN_SCREEN_VIEWED); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.LOGIN_SCREEN_VIEWED) + .build(), + ); BackHandler.addEventListener('hardwareBackPress', this.handleBackPress); const authData = await Authentication.getType(); @@ -533,7 +537,11 @@ class Login extends PureComponent { handleDownloadStateLogs = () => { const { fullState } = this.props; - this.props.metrics.trackEvent(MetaMetricsEvents.LOGIN_DOWNLOAD_LOGS); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.LOGIN_DOWNLOAD_LOGS) + .build(), + ); downloadStateLogs(fullState, false); }; diff --git a/app/components/Views/ManualBackupStep1/index.js b/app/components/Views/ManualBackupStep1/index.js index 058bee5a307..2f50fcbcca8 100644 --- a/app/components/Views/ManualBackupStep1/index.js +++ b/app/components/Views/ManualBackupStep1/index.js @@ -37,6 +37,7 @@ import { MetaMetricsEvents } from '../../../core/Analytics'; import { Authentication } from '../../../core'; import { ManualBackUpStepsSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ManualBackUpSteps.selectors'; import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; /** * View that's shown during the second step of @@ -70,10 +71,6 @@ const ManualBackupStep1 = ({ route, navigation, appTheme }) => { return uint8ArrayToMnemonic(uint8ArrayMnemonic, wordlist).split(' '); }; - const track = (event, properties) => { - trackOnboarding(event, properties); - }; - useEffect(() => { const getSeedphrase = async () => { if (!words.length) { @@ -117,7 +114,11 @@ const ManualBackupStep1 = ({ route, navigation, appTheme }) => { const revealSeedPhrase = () => { setSeedPhraseHidden(false); - track(MetaMetricsEvents.WALLET_SECURITY_PHRASE_REVEALED); + trackOnboarding( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.WALLET_SECURITY_PHRASE_REVEALED, + ).build(), + ); }; const tryUnlockWithPassword = async (password) => { diff --git a/app/components/Views/ManualBackupStep2/index.js b/app/components/Views/ManualBackupStep2/index.js index 123341804c9..9b79e49b604 100644 --- a/app/components/Views/ManualBackupStep2/index.js +++ b/app/components/Views/ManualBackupStep2/index.js @@ -22,6 +22,7 @@ import { useTheme } from '../../../util/theme'; import createStyles from './styles'; import { ManualBackUpStepsSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ManualBackUpSteps.selectors'; import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; const ManualBackupStep2 = ({ navigation, seedphraseBackedUp, route }) => { const { colors } = useTheme(); @@ -46,10 +47,6 @@ const ManualBackupStep2 = ({ navigation, seedphraseBackedUp, route }) => { setWordsDict(dict); }; - const track = (event, properties) => { - trackOnboarding(event, properties); - }; - const updateNavBar = useCallback(() => { navigation.setOptions(getOnboardingNavbarOptions(route, {}, colors)); }, [colors, navigation, route]); @@ -133,7 +130,11 @@ const ManualBackupStep2 = ({ navigation, seedphraseBackedUp, route }) => { steps: route.params?.steps, words, }); - track(MetaMetricsEvents.WALLET_SECURITY_PHRASE_CONFIRMED); + trackOnboarding( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.WALLET_SECURITY_PHRASE_CONFIRMED, + ).build(), + ); }); } else { Alert.alert( diff --git a/app/components/Views/ManualBackupStep3/index.js b/app/components/Views/ManualBackupStep3/index.js index eb1a4e12088..ecbc61a00eb 100644 --- a/app/components/Views/ManualBackupStep3/index.js +++ b/app/components/Views/ManualBackupStep3/index.js @@ -21,6 +21,7 @@ import { MetaMetricsEvents } from '../../../core/Analytics'; import { ThemeContext, mockTheme } from '../../../util/theme'; import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; import OnboardingSuccess from '../OnboardingSuccess'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; const createStyles = (colors) => StyleSheet.create({ @@ -101,10 +102,6 @@ class ManualBackupStep3 extends PureComponent { setOnboardingWizardStep: PropTypes.func, }; - track = (event, properties) => { - trackOnboarding(event, properties); - }; - updateNavBar = () => { const { navigation } = this.props; const colors = this.context.colors || mockTheme.colors; @@ -126,7 +123,11 @@ class ManualBackupStep3 extends PureComponent { this.setState({ hintText: manualBackup, }); - this.track(MetaMetricsEvents.WALLET_SECURITY_COMPLETED); + trackOnboarding( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.WALLET_SECURITY_COMPLETED, + ).build(), + ); BackHandler.addEventListener(HARDWARE_BACK_PRESS, hardwareBackPress); }; diff --git a/app/components/Views/MultiRpcModal/MultiRpcModal.tsx b/app/components/Views/MultiRpcModal/MultiRpcModal.tsx index 7925bd6c862..abf73d2ade9 100644 --- a/app/components/Views/MultiRpcModal/MultiRpcModal.tsx +++ b/app/components/Views/MultiRpcModal/MultiRpcModal.tsx @@ -46,22 +46,26 @@ const MultiRpcModal = () => { const navigation = useNavigation(); const chainId = useSelector(selectChainId); const networkConfigurations = useSelector(selectNetworkConfigurations); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { navigate } = useNavigation(); const dismissMultiRpcModalMigration = useCallback(() => { const { PreferencesController } = Engine.context; PreferencesController.setShowMultiRpcModal(false); - trackEvent(MetaMetricsEvents.MULTI_RPC_MIGRATION_MODAL_ACCEPTED, { - chainId, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.MULTI_RPC_MIGRATION_MODAL_ACCEPTED) + .addProperties({ + chainId, + }) + .build(), + ); if (sheetRef?.current) { sheetRef.current.onCloseBottomSheet(); } else { navigation.goBack(); } - }, [trackEvent, chainId, navigation]); + }, [trackEvent, chainId, navigation, createEventBuilder]); return ( diff --git a/app/components/Views/NFTAutoDetectionModal/NFTAutoDetectionModal.tsx b/app/components/Views/NFTAutoDetectionModal/NFTAutoDetectionModal.tsx index df3930970af..971077db39d 100644 --- a/app/components/Views/NFTAutoDetectionModal/NFTAutoDetectionModal.tsx +++ b/app/components/Views/NFTAutoDetectionModal/NFTAutoDetectionModal.tsx @@ -34,7 +34,7 @@ const NFTAutoDetectionModal = () => { const navigation = useNavigation(); const chainId = useSelector(selectChainId); const displayNftMedia = useSelector(selectDisplayNftMedia); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const enableNftDetectionAndDismissModal = (value: boolean) => { if (value) { @@ -43,13 +43,21 @@ const NFTAutoDetectionModal = () => { PreferencesController.setDisplayNftMedia(true); } PreferencesController.setUseNftDetection(true); - trackEvent(MetaMetricsEvents.NFT_AUTO_DETECTION_MODAL_ENABLE, { - chainId, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NFT_AUTO_DETECTION_MODAL_ENABLE) + .addProperties({ + chainId, + }) + .build(), + ); } else { - trackEvent(MetaMetricsEvents.NFT_AUTO_DETECTION_MODAL_DISABLE, { - chainId, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NFT_AUTO_DETECTION_MODAL_DISABLE) + .addProperties({ + chainId, + }) + .build(), + ); } if (sheetRef?.current) { diff --git a/app/components/Views/NetworkSelector/NetworkSelector.tsx b/app/components/Views/NetworkSelector/NetworkSelector.tsx index 21bf02d1b71..95e0d8cd5aa 100644 --- a/app/components/Views/NetworkSelector/NetworkSelector.tsx +++ b/app/components/Views/NetworkSelector/NetworkSelector.tsx @@ -121,7 +121,7 @@ const NetworkSelector = () => { const [searchString, setSearchString] = useState(''); const { navigate } = useNavigation(); const theme = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { colors } = theme; const styles = createStyles(colors); const sheetRef = useRef(null); @@ -271,11 +271,15 @@ const NetworkSelector = () => { sheetRef.current?.onCloseBottomSheet(); endTrace({ name: TraceName.SwitchCustomNetwork }); endTrace({ name: TraceName.NetworkSwitch }); - trackEvent(MetaMetricsEvents.NETWORK_SWITCHED, { - chain_id: getDecimalChainId(chainId), - from_network: selectedNetworkName, - to_network: nickname, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NETWORK_SWITCHED) + .addProperties({ + chain_id: getDecimalChainId(chainId), + from_network: selectedNetworkName, + to_network: nickname, + }) + .build(), + ); } }; @@ -391,11 +395,15 @@ const NetworkSelector = () => { sheetRef.current?.onCloseBottomSheet(); endTrace({ name: TraceName.SwitchBuiltInNetwork }); endTrace({ name: TraceName.NetworkSwitch }); - trackEvent(MetaMetricsEvents.NETWORK_SWITCHED, { - chain_id: getDecimalChainId(selectedChainId), - from_network: selectedNetworkName, - to_network: type, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NETWORK_SWITCHED) + .addProperties({ + chain_id: getDecimalChainId(selectedChainId), + from_network: selectedNetworkName, + to_network: type, + }) + .build(), + ); }; const filterNetworksByName = ( diff --git a/app/components/Views/NftDetails/NftDetails.tsx b/app/components/Views/NftDetails/NftDetails.tsx index 452a61c5ab0..04748b5070e 100644 --- a/app/components/Views/NftDetails/NftDetails.tsx +++ b/app/components/Views/NftDetails/NftDetails.tsx @@ -55,7 +55,7 @@ const NftDetails = () => { const dispatch = useDispatch(); const currentCurrency = useSelector(selectCurrentCurrency); const ticker = useSelector(selectTicker); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const selectedNativeConversionRate = useSelector(selectConversionRate); const hasLastSalePrice = Boolean( collectible.lastSale?.price?.amount?.usd && @@ -95,9 +95,13 @@ const NftDetails = () => { }, [updateNavBar]); useEffect(() => { - trackEvent(MetaMetricsEvents.COLLECTIBLE_DETAILS_OPENED, { - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.COLLECTIBLE_DETAILS_OPENED) + .addProperties({ + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); // The linter wants `trackEvent` to be added as a dependency, // But the event fires twice if I do that. // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/app/components/Views/NftOptions/NftOptions.tsx b/app/components/Views/NftOptions/NftOptions.tsx index 15852b81f10..81116eb7309 100644 --- a/app/components/Views/NftOptions/NftOptions.tsx +++ b/app/components/Views/NftOptions/NftOptions.tsx @@ -42,7 +42,7 @@ const NftOptions = (props: Props) => { const navigation = useNavigation(); const modalRef = useRef(null); const chainId = useSelector(selectChainId); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const selectedAddress = useSelector( selectSelectedInternalAccountChecksummedAddress, ); @@ -96,9 +96,13 @@ const NftOptions = (props: Props) => { collectible.address, collectible.tokenId.toString(), ); - trackEvent(MetaMetricsEvents.COLLECTIBLE_REMOVED, { - chain_id: getDecimalChainId(chainId), - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.COLLECTIBLE_REMOVED) + .addProperties({ + chain_id: getDecimalChainId(chainId), + }) + .build(), + ); Alert.alert( strings('wallet.collectible_removed_title'), strings('wallet.collectible_removed_desc'), diff --git a/app/components/Views/Notifications/Details/Fields/NetworkFeeField.tsx b/app/components/Views/Notifications/Details/Fields/NetworkFeeField.tsx index 9e8cb1c16db..148d6e20591 100644 --- a/app/components/Views/Notifications/Details/Fields/NetworkFeeField.tsx +++ b/app/components/Views/Notifications/Details/Fields/NetworkFeeField.tsx @@ -22,9 +22,7 @@ import Icon, { } from '../../../../../component-library/components/Icons/Icon'; import { NotificationDetailStyles } from '../styles'; import { CURRENCY_SYMBOL_BY_CHAIN_ID } from '../../../../../constants/network'; -import { - type Notification, -} from '../../../../../util/notifications'; +import { type Notification } from '../../../../../util/notifications'; import { useMetrics } from '../../../../../components/hooks/useMetrics'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; import NetworkFeeFieldSkeleton from './Skeletons/NetworkFeeField'; @@ -51,7 +49,8 @@ export function useNetworkFee({ getNetworkFees }: NetworkFeeFieldProps) { }) .catch(() => { setData(undefined); - }).finally(() => { + }) + .finally(() => { setIsLoading(false); }); }, [getNetworkFees]); @@ -85,8 +84,8 @@ function NetworkFeeField(props: NetworkFeeFieldProps) { const { setIsCollapsed, isCollapsed, notification } = props; const { styles, theme } = useStyles(); const sheetRef = useRef(null); - const {data: networkFee, isLoading} = useNetworkFee(props); - const { trackEvent } = useMetrics(); + const { data: networkFee, isLoading } = useNetworkFee(props); + const { trackEvent, createEventBuilder } = useMetrics(); if (isLoading && !networkFee) { return ( @@ -97,55 +96,59 @@ function NetworkFeeField(props: NetworkFeeFieldProps) { } const renderNetworkFeeDetails = () => { - if (!networkFee) { + if (!networkFee) { + return ( + + + {strings('notifications.network_fee_not_available')} + + + ); + } + + const ticker = CURRENCY_SYMBOL_BY_CHAIN_ID[networkFee.chainId]; + const collapsedIcon = isCollapsed ? IconName.ArrowDown : IconName.ArrowUp; return ( - - - {strings('notifications.network_fee_not_available')} - - + <> + + + {strings('asset_details.network_fee')} + + + + {networkFee.transactionFeeInEth} {ticker} ($ + {networkFee.transactionFeeInUsd}) + + + + + {strings('transaction.details')} + + + + ); - } - - const ticker = CURRENCY_SYMBOL_BY_CHAIN_ID[networkFee.chainId]; - const collapsedIcon = isCollapsed ? IconName.ArrowDown : IconName.ArrowUp; - return ( - <> - - - {strings('asset_details.network_fee')} - - - - {networkFee.transactionFeeInEth} {ticker} ($ - {networkFee.transactionFeeInUsd}) - - - - - {strings('transaction.details')} - - - - - ); }; const onPress = () => { setIsCollapsed(!isCollapsed); if (!isCollapsed) { - trackEvent(MetaMetricsEvents.NOTIFICATION_DETAIL_CLICKED, { - notification_id: notification.id, - notification_type: notification.type, - ...('chain_id' in notification && { - chain_id: notification.chain_id, - }), - clicked_item: 'fee_details', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NOTIFICATION_DETAIL_CLICKED) + .addProperties({ + notification_id: notification.id, + notification_type: notification.type, + ...('chain_id' in notification && { + chain_id: notification.chain_id, + }), + clicked_item: 'fee_details', + }) + .build(), + ); } }; diff --git a/app/components/Views/Notifications/Details/Fields/TransactionField.test.tsx b/app/components/Views/Notifications/Details/Fields/TransactionField.test.tsx index c30c6ddaa26..55e09bdaa3a 100644 --- a/app/components/Views/Notifications/Details/Fields/TransactionField.test.tsx +++ b/app/components/Views/Notifications/Details/Fields/TransactionField.test.tsx @@ -6,6 +6,7 @@ import * as useMetricsModule from '../../../../hooks/useMetrics'; import * as useCopyClipboardModule from '../hooks/useCopyClipboard'; import { ModalFieldType } from '../../../../../util/notifications'; import MOCK_NOTIFICATIONS from '../../../../UI/Notification/__mocks__/mock_notifications'; +import { MetricsEventBuilder } from '../../../../../core/Analytics/MetricsEventBuilder'; // Mock the required modules jest.mock('../../../../hooks/useMetrics'); @@ -28,8 +29,15 @@ describe('TransactionField', () => { const mockCopyToClipboard = jest.fn(); beforeEach(() => { - jest.spyOn(useMetricsModule, 'useMetrics').mockReturnValue({ trackEvent: mockTrackEvent } as unknown as useMetricsModule.IUseMetricsHook); - jest.spyOn(useCopyClipboardModule, 'default').mockReturnValue(mockCopyToClipboard); + jest + .spyOn(useMetricsModule, 'useMetrics') + .mockReturnValue({ + trackEvent: mockTrackEvent, + createEventBuilder: MetricsEventBuilder.createEventBuilder, + } as unknown as useMetricsModule.IUseMetricsHook); + jest + .spyOn(useCopyClipboardModule, 'default') + .mockReturnValue(mockCopyToClipboard); }); afterEach(() => { @@ -42,12 +50,21 @@ describe('TransactionField', () => { type={ModalFieldType.TRANSACTION} notification={MOCK_NOTIFICATIONS[0]} txHash={mockProps.txHash} - /> + />, ); const copyButton = getByText('transaction.transaction_id'); fireEvent.press(copyButton); - - expect(mockTrackEvent).toHaveBeenCalledWith({'category': 'Notification Detail Clicked'}, {'chain_id': 1, 'clicked_item': 'tx_id', 'notification_id': '3fa85f64-5717-4562-b3fc-2c963f66afa7', 'notification_type': 'eth_sent'}); + const expectedEvent = MetricsEventBuilder.createEventBuilder({ + category: 'Notification Detail Clicked', + }) + .addProperties({ + chain_id: 1, + clicked_item: 'tx_id', + notification_id: '3fa85f64-5717-4562-b3fc-2c963f66afa7', + notification_type: 'eth_sent', + }) + .build(); + expect(mockTrackEvent).toHaveBeenCalledWith(expectedEvent); }); }); diff --git a/app/components/Views/Notifications/Details/Fields/TransactionField.tsx b/app/components/Views/Notifications/Details/Fields/TransactionField.tsx index ab2f8ccd59d..b330ac96f82 100644 --- a/app/components/Views/Notifications/Details/Fields/TransactionField.tsx +++ b/app/components/Views/Notifications/Details/Fields/TransactionField.tsx @@ -28,7 +28,7 @@ type TransactionFieldProps = ModalFieldTransaction & { }; function TransactionField(props: TransactionFieldProps) { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { txHash, notification } = props; const { styles, theme } = useStyles(); const copyToClipboard = useCopyClipboard(); @@ -55,14 +55,14 @@ function TransactionField(props: TransactionFieldProps) { { - trackEvent(MetaMetricsEvents.NOTIFICATION_DETAIL_CLICKED, { + trackEvent(createEventBuilder(MetaMetricsEvents.NOTIFICATION_DETAIL_CLICKED).addProperties({ notification_id: notification.id, notification_type: notification.type, ...('chain_id' in notification && { chain_id: notification.chain_id, }), clicked_item: 'tx_id', - }); + }).build()); copyToClipboard(txHash, CopyClipboardAlertMessage.transaction()); }} hitSlop={{ top: 24, bottom: 24, left: 24, right: 24 }} diff --git a/app/components/Views/Notifications/Details/Footers/BlockExplorerFooter.test.tsx b/app/components/Views/Notifications/Details/Footers/BlockExplorerFooter.test.tsx index 821e51db4e7..2cc04f726ba 100644 --- a/app/components/Views/Notifications/Details/Footers/BlockExplorerFooter.test.tsx +++ b/app/components/Views/Notifications/Details/Footers/BlockExplorerFooter.test.tsx @@ -5,10 +5,14 @@ import { useSelector } from 'react-redux'; import { fireEvent, render } from '@testing-library/react-native'; import { strings } from '../../../../../../locales/i18n'; import BlockExplorerFooter from './BlockExplorerFooter'; -import { MetaMetricsEvents, useMetrics } from '../../../../../components/hooks/useMetrics'; +import { + MetaMetricsEvents, + useMetrics, +} from '../../../../../components/hooks/useMetrics'; import { getBlockExplorerByChainId } from '../../../../../util/notifications'; import { ModalFooterType } from '../../../../../util/notifications/constants/config'; import MOCK_NOTIFICATIONS from '../../../../UI/Notification/__mocks__/mock_notifications'; +import { MetricsEventBuilder } from '../../../../../core/Analytics/MetricsEventBuilder'; jest.mock('react-native/Libraries/Linking/Linking', () => ({ openURL: jest.fn(), @@ -23,23 +27,29 @@ jest.mock('../../../../../util/notifications', () => ({ jest.mock('../../../../../components/hooks/useMetrics'); +const trackEventMock = jest.fn(); + +(useMetrics as jest.MockedFn).mockReturnValue({ + trackEvent: trackEventMock, + createEventBuilder: MetricsEventBuilder.createEventBuilder, + enable: jest.fn(), + addTraitsToUser: jest.fn(), + createDataDeletionTask: jest.fn(), + checkDataDeleteStatus: jest.fn(), + getDeleteRegulationCreationDate: jest.fn(), + getDeleteRegulationId: jest.fn(), + isDataRecorded: jest.fn(), + isEnabled: jest.fn(), + getMetaMetricsId: jest.fn(), +}); + jest.mock('react-native/Libraries/Linking/Linking', () => ({ openURL: jest.fn(), })); - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let trackEventMock: jest.MockedFunction; - describe('BlockExplorerFooter', () => { - const useMetricsMock = jest.mocked(useMetrics); - beforeEach(() => { - jest.resetAllMocks(); - trackEventMock = jest.fn(); - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - useMetricsMock.mockReturnValue({ trackEvent: trackEventMock } as any); + jest.clearAllMocks(); }); it('returns null when no URL is available', () => { @@ -59,15 +69,15 @@ describe('BlockExplorerFooter', () => { }); it('tracks event with chain_id when present in notification', () => { - (useMetrics as jest.Mock).mockReturnValue({ trackEvent: trackEventMock }); - (useSelector as jest.Mock).mockReturnValue({}); - (getBlockExplorerByChainId as jest.Mock).mockReturnValue('https://blockexplorer.com'); + (getBlockExplorerByChainId as jest.Mock).mockReturnValue( + 'https://blockexplorer.com', + ); const props = { chainId: 1, txHash: '0x123', - notification: {...MOCK_NOTIFICATIONS[0], chain_id: 1}, + notification: { ...MOCK_NOTIFICATIONS[0], chain_id: 1 }, type: ModalFooterType.BLOCK_EXPLORER, } as const; @@ -78,12 +88,17 @@ describe('BlockExplorerFooter', () => { fireEvent(button, 'onPress'); expect(Linking.openURL).toHaveBeenCalled(); - expect(trackEventMock).toHaveBeenCalledWith(MetaMetricsEvents.NOTIFICATION_DETAIL_CLICKED, { - notification_id: props.notification.id, - notification_type: props.notification.type, - chain_id: props.notification.chain_id, - clicked_item: 'block_explorer', - }); + expect(trackEventMock).toHaveBeenCalledWith( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.NOTIFICATION_DETAIL_CLICKED, + ) + .addProperties({ + notification_id: props.notification.id, + notification_type: props.notification.type, + chain_id: props.notification.chain_id, + clicked_item: 'block_explorer', + }) + .build(), + ); }); }); - diff --git a/app/components/Views/Notifications/Details/Footers/BlockExplorerFooter.tsx b/app/components/Views/Notifications/Details/Footers/BlockExplorerFooter.tsx index d6d0a35b27f..10badcdc875 100644 --- a/app/components/Views/Notifications/Details/Footers/BlockExplorerFooter.tsx +++ b/app/components/Views/Notifications/Details/Footers/BlockExplorerFooter.tsx @@ -7,9 +7,7 @@ import Button, { ButtonVariants, } from '../../../../../component-library/components/Buttons/Button'; import { selectNetworkConfigurations } from '../../../../../selectors/networkController'; -import { - getBlockExplorerByChainId, -} from '../../../../../util/notifications'; +import { getBlockExplorerByChainId } from '../../../../../util/notifications'; import { ModalFooterBlockExplorer } from '../../../../../util/notifications/notification-states/types/NotificationModalDetails'; import useStyles from '../useStyles'; import { IconName } from '../../../../../component-library/components/Icons/Icon'; @@ -24,7 +22,7 @@ type BlockExplorerFooterProps = ModalFooterBlockExplorer & { export default function BlockExplorerFooter(props: BlockExplorerFooterProps) { const { styles } = useStyles(); const { notification } = props; - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const defaultBlockExplorer = getBlockExplorerByChainId(props.chainId); const networkConfigurations = useSelector(selectNetworkConfigurations); const networkBlockExplorer = useMemo(() => { @@ -44,14 +42,18 @@ export default function BlockExplorerFooter(props: BlockExplorerFooterProps) { const onPress = () => { Linking.openURL(txHashUrl); - trackEvent(MetaMetricsEvents.NOTIFICATION_DETAIL_CLICKED, { - notification_id: notification.id, - notification_type: notification.type, - ...('chain_id' in notification && { - chain_id: notification.chain_id, - }), - clicked_item: 'block_explorer', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NOTIFICATION_DETAIL_CLICKED) + .addProperties({ + notification_id: notification.id, + notification_type: notification.type, + ...('chain_id' in notification && { + chain_id: notification.chain_id, + }), + clicked_item: 'block_explorer', + }) + .build(), + ); }; return ( diff --git a/app/components/Views/Notifications/OptIn/index.tsx b/app/components/Views/Notifications/OptIn/index.tsx index 4c69eb7d7dd..60e97022945 100644 --- a/app/components/Views/Notifications/OptIn/index.tsx +++ b/app/components/Views/Notifications/OptIn/index.tsx @@ -28,7 +28,7 @@ import { } from '../../../../selectors/notifications'; const OptIn = () => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const theme = useTheme(); const styles = createStyles(theme); const navigation = useNavigation(); @@ -54,10 +54,14 @@ const OptIn = () => { React.useState(false); const navigateToMainWallet = () => { if (!isUpdating) { - trackEvent(MetaMetricsEvents.NOTIFICATIONS_ACTIVATED, { - action_type: 'dismissed', - is_profile_syncing_enabled: isProfileSyncingEnabled, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NOTIFICATIONS_ACTIVATED) + .addProperties({ + action_type: 'dismissed', + is_profile_syncing_enabled: isProfileSyncingEnabled, + }) + .build(), + ); } navigation.navigate(Routes.WALLET_VIEW); }; @@ -77,25 +81,29 @@ const OptIn = () => { if (permission !== 'authorized') { return; } - /** - * Although this is an async function, we are dispatching an action (firing & forget) - * to emulate optimistic UI. - * Setting a standard timeout to emulate loading state - * for 5 seconds. This only happens during the first time the user - * optIn to notifications. - */ - enableNotifications(); - setOptimisticLoading(true); - setTimeout(() => { - setOptimisticLoading(false); - navigation.navigate(Routes.NOTIFICATIONS.VIEW); - }, 5000); - } - setIsUpdating(true); - trackEvent(MetaMetricsEvents.NOTIFICATIONS_ACTIVATED, { - action_type: 'activated', - is_profile_syncing_enabled: isProfileSyncingEnabled, - }); + /** + * Although this is an async function, we are dispatching an action (firing & forget) + * to emulate optimistic UI. + * Setting a standard timeout to emulate loading state + * for 5 seconds. This only happens during the first time the user + * optIn to notifications. + */ + enableNotifications(); + setOptimisticLoading(true); + setTimeout(() => { + setOptimisticLoading(false); + navigation.navigate(Routes.NOTIFICATIONS.VIEW); + }, 5000); + } + setIsUpdating(true); + trackEvent( + createEventBuilder(MetaMetricsEvents.NOTIFICATIONS_ACTIVATED) + .addProperties({ + action_type: 'activated', + is_profile_syncing_enabled: isProfileSyncingEnabled, + }) + .build(), + ); }, [ basicFunctionalityEnabled, enableNotifications, @@ -103,6 +111,7 @@ const OptIn = () => { isProfileSyncingEnabled, trackEvent, setIsUpdating, + createEventBuilder, ]); const goToLearnMore = () => { diff --git a/app/components/Views/Notifications/index.tsx b/app/components/Views/Notifications/index.tsx index 77d736afa4a..4864df098e5 100644 --- a/app/components/Views/Notifications/index.tsx +++ b/app/components/Views/Notifications/index.tsx @@ -43,7 +43,7 @@ const NotificationsView = ({ }: { navigation: NavigationProp; }) => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { isLoading } = useListNotifications(); const isNotificationEnabled = useSelector( selectIsMetamaskNotificationsEnabled, @@ -54,8 +54,8 @@ const NotificationsView = ({ const handleMarkAllAsRead = useCallback(() => { markNotificationAsRead(notifications); NotificationsService.setBadgeCount(0); - trackEvent(MetaMetricsEvents.NOTIFICATIONS_MARKED_ALL_AS_READ); - }, [markNotificationAsRead, notifications, trackEvent]); + trackEvent(createEventBuilder(MetaMetricsEvents.NOTIFICATIONS_MARKED_ALL_AS_READ).build()); + }, [markNotificationAsRead, notifications, trackEvent, createEventBuilder]); const allNotifications = useMemo(() => { // All unique notifications diff --git a/app/components/Views/OnboardingCarousel/index.tsx b/app/components/Views/OnboardingCarousel/index.tsx index 0641aa1e98b..c6ef75de38c 100644 --- a/app/components/Views/OnboardingCarousel/index.tsx +++ b/app/components/Views/OnboardingCarousel/index.tsx @@ -10,7 +10,9 @@ import { } from 'react-native'; import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types'; import { NavigationProp, ParamListBase } from '@react-navigation/native'; -import { MetaMetricsEvents, IMetaMetricsEvent } from '../../../core/Analytics'; +import { MetaMetricsEvents } from '../../../core/Analytics'; +import { ITrackingEvent } from '../../../core/Analytics/MetaMetrics.types'; +import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; import StyledButton from '../../UI/StyledButton'; import { fontStyles, baseStyles } from '../../../styles/common'; import { strings } from '../../../../locales/i18n'; @@ -28,7 +30,6 @@ import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboardi import { isTest } from '../../../util/test/utils'; import StorageWrapper from '../../../store/storage-wrapper'; import { PerformanceRegressionSelectorIDs } from '../../../../e2e/selectors/PerformanceRegression.selectors'; -import { JsonMap } from '@segment/analytics-react-native'; import { Dispatch } from 'redux'; import { saveOnboardingEvent as SaveEvent, @@ -143,7 +144,7 @@ const carousel_images = [ interface OnboardingCarouselProps { navigation: NavigationProp; - saveOnboardingEvent: (...eventArgs: [IMetaMetricsEvent]) => void; + saveOnboardingEvent: (...eventArgs: [ITrackingEvent]) => void; } /** * View that is displayed to first time (new) users @@ -161,26 +162,36 @@ export const OnboardingCarousel: React.FC = ({ const styles = createStyles(colors); const track = useCallback( - (event: IMetaMetricsEvent, properties: JsonMap = {}) => { - trackOnboarding(event, properties, saveOnboardingEvent); + (event: ITrackingEvent) => { + trackOnboarding(event, saveOnboardingEvent); }, [saveOnboardingEvent], ); const onPressGetStarted = () => { navigation.navigate('Onboarding'); - track(MetaMetricsEvents.ONBOARDING_STARTED); + track( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.ONBOARDING_STARTED, + ).build(), + ); }; const renderTabBar = () => ; const onChangeTab = (obj: { i: number }) => { setCurrentTab(obj.i + 1); - track(MetaMetricsEvents.ONBOARDING_WELCOME_SCREEN_ENGAGEMENT, { - message_title: strings(`onboarding_carousel.title${[obj.i + 1]}`, { - locale: 'en', - }), - }); + track( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.ONBOARDING_WELCOME_SCREEN_ENGAGEMENT, + ) + .addProperties({ + message_title: strings(`onboarding_carousel.title${[obj.i + 1]}`, { + locale: 'en', + }), + }) + .build(), + ); }; const updateNavBar = useCallback(() => { @@ -189,7 +200,11 @@ export const OnboardingCarousel: React.FC = ({ const initialize = useCallback(async () => { updateNavBar(); - track(MetaMetricsEvents.ONBOARDING_WELCOME_MESSAGE_VIEWED); + track( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.ONBOARDING_WELCOME_MESSAGE_VIEWED, + ).build(), + ); const newAppStartTime = await StorageWrapper.getItem('appStartTime'); setAppStartTime(newAppStartTime); }, [updateNavBar, track]); @@ -295,7 +310,7 @@ export const OnboardingCarousel: React.FC = ({ }; const mapDispatchToProps = (dispatch: Dispatch) => ({ - saveOnboardingEvent: (...eventArgs: [IMetaMetricsEvent]) => + saveOnboardingEvent: (...eventArgs: [ITrackingEvent]) => dispatch(SaveEvent(eventArgs)), }); diff --git a/app/components/Views/OnboardingSuccess/OnboardingGeneralSettings/index.tsx b/app/components/Views/OnboardingSuccess/OnboardingGeneralSettings/index.tsx index 6dc4007b32a..8c3b92cbbe9 100644 --- a/app/components/Views/OnboardingSuccess/OnboardingGeneralSettings/index.tsx +++ b/app/components/Views/OnboardingSuccess/OnboardingGeneralSettings/index.tsx @@ -20,7 +20,7 @@ const GeneralSettings = () => { useOnboardingHeader(strings('default_settings.drawer_general_title')); const { styles } = useStyles(styleSheet, {}); const navigation = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const isBasicFunctionalityEnabled = useSelector( (state: RootState) => state?.settings?.basicFunctionalityEnabled, ); @@ -30,13 +30,17 @@ const GeneralSettings = () => { navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.BASIC_FUNCTIONALITY, }); - trackEvent(MetaMetricsEvents.SETTINGS_UPDATED, { - settings_group: 'onboarding_advanced_configuration', - settings_type: 'basic_functionality', - old_value: isBasicFunctionalityEnabled, - new_value: !isBasicFunctionalityEnabled, - was_profile_syncing_on: isProfileSyncingEnabled, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.SETTINGS_UPDATED) + .addProperties({ + settings_group: 'onboarding_advanced_configuration', + settings_type: 'basic_functionality', + old_value: isBasicFunctionalityEnabled, + new_value: !isBasicFunctionalityEnabled, + was_profile_syncing_on: isProfileSyncingEnabled, + }) + .build(), + ); }; const toggleProfileSyncing = async () => { @@ -47,12 +51,16 @@ const GeneralSettings = () => { } else { await enableProfileSyncing(); } - trackEvent(MetaMetricsEvents.SETTINGS_UPDATED, { - settings_group: 'onboarding_advanced_configuration', - settings_type: 'profile_syncing', - old_value: isProfileSyncingEnabled, - new_value: !isProfileSyncingEnabled, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.SETTINGS_UPDATED) + .addProperties({ + settings_group: 'onboarding_advanced_configuration', + settings_type: 'profile_syncing', + old_value: isProfileSyncingEnabled, + new_value: !isProfileSyncingEnabled, + }) + .build(), + ); }; return ( diff --git a/app/components/Views/Quiz/SRPQuiz/SRPQuiz.tsx b/app/components/Views/Quiz/SRPQuiz/SRPQuiz.tsx index fedfe828645..fbb7351fe7f 100644 --- a/app/components/Views/Quiz/SRPQuiz/SRPQuiz.tsx +++ b/app/components/Views/Quiz/SRPQuiz/SRPQuiz.tsx @@ -36,7 +36,7 @@ const SRPQuiz = () => { const { styles, theme } = useStyles(stylesheet, {}); const { colors } = theme; const navigation = useNavigation(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const dismissModal = (): void => { modalRef.current?.dismissModal(); @@ -76,16 +76,20 @@ const SRPQuiz = () => { ); const goToRevealPrivateCredential = useCallback((): void => { - trackEvent(MetaMetricsEvents.REVEAL_SRP_INITIATED); - trackEvent(MetaMetricsEvents.REVEAL_SRP_CTA); + trackEvent( + createEventBuilder(MetaMetricsEvents.REVEAL_SRP_INITIATED).build(), + ); + trackEvent(createEventBuilder(MetaMetricsEvents.REVEAL_SRP_CTA).build()); navigation.navigate(Routes.SETTINGS.REVEAL_PRIVATE_CREDENTIAL, { credentialName: 'seed_phrase', shouldUpdateNav: true, }); - }, [navigation, trackEvent]); + }, [navigation, trackEvent, createEventBuilder]); const introduction = useCallback(() => { - trackEvent(MetaMetricsEvents.SRP_REVEAL_QUIZ_PROMPT_SEEN, {}); + trackEvent( + createEventBuilder(MetaMetricsEvents.SRP_REVEAL_QUIZ_PROMPT_SEEN).build(), + ); return ( { { label: strings('srp_security_quiz.get_started'), onPress: () => { - trackEvent(MetaMetricsEvents.SRP_REVEAL_START_CTA_SELECTED, {}); + trackEvent( + createEventBuilder( + MetaMetricsEvents.SRP_REVEAL_START_CTA_SELECTED, + ).build(), + ); setStage(QuizStage.questionOne); }, testID: SrpQuizGetStartedSelectorsIDs.BUTTON, @@ -113,10 +121,14 @@ const SRPQuiz = () => { dismiss={dismissModal} /> ); - }, [trackEvent]); + }, [trackEvent, createEventBuilder]); const questionOne = useCallback((): React.ReactElement => { - trackEvent(MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_SEEN, {}); + trackEvent( + createEventBuilder( + MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_SEEN, + ).build(), + ); return ( { dismiss={dismissModal} /> ); - }, [trackEvent]); + }, [trackEvent, createEventBuilder]); const rightAnswerQuestionOne = useCallback((): React.ReactElement => { - trackEvent(MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_RIGHT_ASNWER, {}); + trackEvent( + createEventBuilder( + MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_RIGHT_ASNWER, + ).build(), + ); return ( { dismiss={dismissModal} /> ); - }, [rightAnswerIcon, styles.rightText, trackEvent]); + }, [rightAnswerIcon, styles.rightText, trackEvent, createEventBuilder]); const wrongAnswerQuestionOne = useCallback((): React.ReactElement => { - trackEvent(MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_WRONG_ANSWER, {}); + trackEvent( + createEventBuilder( + MetaMetricsEvents.SRP_REVEAL_FIRST_QUESTION_WRONG_ANSWER, + ).build(), + ); return ( { dismiss={dismissModal} /> ); - }, [styles.wrongText, wrongAnswerIcon, trackEvent]); + }, [styles.wrongText, wrongAnswerIcon, trackEvent, createEventBuilder]); const questionTwo = useCallback((): React.ReactElement => { - trackEvent(MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_SEEN, {}); + trackEvent( + createEventBuilder( + MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_SEEN, + ).build(), + ); return ( { dismiss={dismissModal} /> ); - }, [trackEvent]); + }, [trackEvent, createEventBuilder]); const rightAnswerQuestionTwo = useCallback((): React.ReactElement => { - trackEvent(MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_RIGHT_ASNWER, {}); + trackEvent( + createEventBuilder( + MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_RIGHT_ASNWER, + ).build(), + ); return ( { rightAnswerIcon, styles.rightText, trackEvent, + createEventBuilder, ]); const wrongAnswerQuestionTwo = useCallback((): React.ReactElement => { - trackEvent(MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_WRONG_ANSWER, {}); + trackEvent( + createEventBuilder( + MetaMetricsEvents.SRP_REVEAL_SECOND_QUESTION_WRONG_ANSWER, + ).build(), + ); return ( { dismiss={dismissModal} /> ); - }, [styles.wrongText, wrongAnswerIcon, trackEvent]); + }, [styles.wrongText, wrongAnswerIcon, trackEvent, createEventBuilder]); const quizPage = useCallback(() => { switch (stage) { diff --git a/app/components/Views/RestoreWallet/RestoreWallet.tsx b/app/components/Views/RestoreWallet/RestoreWallet.tsx index 256c2d99b76..b0f6e3fb3b6 100644 --- a/app/components/Views/RestoreWallet/RestoreWallet.tsx +++ b/app/components/Views/RestoreWallet/RestoreWallet.tsx @@ -44,7 +44,7 @@ export const createRestoreWalletNavDetailsNested = ); const RestoreWallet = () => { - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { colors } = useAppThemeFromContext(); const styles = createStyles(colors); @@ -59,17 +59,23 @@ const RestoreWallet = () => { useEffect(() => { trackEvent( - MetaMetricsEvents.VAULT_CORRUPTION_RESTORE_WALLET_SCREEN_VIEWED, - { ...deviceMetaData, previousScreen }, + createEventBuilder( + MetaMetricsEvents.VAULT_CORRUPTION_RESTORE_WALLET_SCREEN_VIEWED, + ) + .addProperties({ ...deviceMetaData, previousScreen }) + .build(), ); - }, [deviceMetaData, previousScreen, trackEvent]); + }, [deviceMetaData, previousScreen, trackEvent, createEventBuilder]); const handleOnNext = useCallback(async (): Promise => { setLoading(true); trackEvent( - MetaMetricsEvents.VAULT_CORRUPTION_RESTORE_WALLET_BUTTON_PRESSED, - deviceMetaData, + createEventBuilder( + MetaMetricsEvents.VAULT_CORRUPTION_RESTORE_WALLET_BUTTON_PRESSED, + ) + .addProperties({ ...deviceMetaData }) + .build(), ); const restoreResult = await EngineService.initializeVaultFromBackup(); if (restoreResult.success) { @@ -79,7 +85,7 @@ const RestoreWallet = () => { replace(...createWalletResetNeededNavDetails()); setLoading(false); } - }, [deviceMetaData, replace, trackEvent]); + }, [deviceMetaData, replace, trackEvent, createEventBuilder]); return ( diff --git a/app/components/Views/RestoreWallet/WalletResetNeeded.tsx b/app/components/Views/RestoreWallet/WalletResetNeeded.tsx index 2d8c0875397..57745747e46 100644 --- a/app/components/Views/RestoreWallet/WalletResetNeeded.tsx +++ b/app/components/Views/RestoreWallet/WalletResetNeeded.tsx @@ -27,7 +27,7 @@ export const createWalletResetNeededNavDetails = createNavigationDetails( const WalletResetNeeded = () => { const { colors } = useAppThemeFromContext(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); // TODO: Replace "any" with type @@ -38,32 +38,41 @@ const WalletResetNeeded = () => { useEffect(() => { trackEvent( - MetaMetricsEvents.VAULT_CORRUPTION_WALLET_RESET_NEEDED_SCREEN_VIEWED, - deviceMetaData, + createEventBuilder( + MetaMetricsEvents.VAULT_CORRUPTION_WALLET_RESET_NEEDED_SCREEN_VIEWED, + ) + .addProperties({ ...deviceMetaData }) + .build(), ); - }, [trackEvent, deviceMetaData]); + }, [trackEvent, deviceMetaData, createEventBuilder]); const handleCreateNewWallet = useCallback(async () => { trackEvent( - MetaMetricsEvents.VAULT_CORRUPTION_WALLET_RESET_NEEDED_CREATE_NEW_WALLET_BUTTON_PRESSED, - deviceMetaData, + createEventBuilder( + MetaMetricsEvents.VAULT_CORRUPTION_WALLET_RESET_NEEDED_CREATE_NEW_WALLET_BUTTON_PRESSED, + ) + .addProperties({ ...deviceMetaData }) + .build(), ); navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.MODAL.DELETE_WALLET, }); - }, [deviceMetaData, navigation, trackEvent]); + }, [deviceMetaData, navigation, trackEvent, createEventBuilder]); const handleTryAgain = useCallback(async () => { trackEvent( - MetaMetricsEvents.VAULT_CORRUPTION_WALLET_RESET_NEEDED_TRY_AGAIN_BUTTON_PRESSED, - deviceMetaData, + createEventBuilder( + MetaMetricsEvents.VAULT_CORRUPTION_WALLET_RESET_NEEDED_TRY_AGAIN_BUTTON_PRESSED, + ) + .addProperties({ ...deviceMetaData }) + .build(), ); navigation.replace( ...createRestoreWalletNavDetails({ previousScreen: Routes.VAULT_RECOVERY.WALLET_RESET_NEEDED, }), ); - }, [deviceMetaData, navigation, trackEvent]); + }, [deviceMetaData, navigation, trackEvent, createEventBuilder]); return ( diff --git a/app/components/Views/RestoreWallet/WalletRestored.tsx b/app/components/Views/RestoreWallet/WalletRestored.tsx index d2836c0531b..76b25d7c63e 100644 --- a/app/components/Views/RestoreWallet/WalletRestored.tsx +++ b/app/components/Views/RestoreWallet/WalletRestored.tsx @@ -32,7 +32,7 @@ export const createWalletRestoredNavDetails = createNavigationDetails( const WalletRestored = () => { const [loading, setLoading] = useState(false); const { colors } = useAppThemeFromContext(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const styles = createStyles(colors); // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -42,10 +42,13 @@ const WalletRestored = () => { useEffect(() => { trackEvent( - MetaMetricsEvents.VAULT_CORRUPTION_WALLET_SUCCESSFULLY_RESTORED_SCREEN_VIEWED, - deviceMetaData, + createEventBuilder( + MetaMetricsEvents.VAULT_CORRUPTION_WALLET_SUCCESSFULLY_RESTORED_SCREEN_VIEWED, + ) + .addProperties({ ...deviceMetaData }) + .build(), ); - }, [deviceMetaData, trackEvent]); + }, [deviceMetaData, trackEvent, createEventBuilder]); const finishWalletRestore = useCallback(async (): Promise => { try { @@ -64,11 +67,14 @@ const WalletRestored = () => { const handleOnNext = useCallback(async (): Promise => { setLoading(true); trackEvent( - MetaMetricsEvents.VAULT_CORRUPTION_WALLET_SUCCESSFULLY_RESTORED_CONTINUE_BUTTON_PRESSED, - deviceMetaData, + createEventBuilder( + MetaMetricsEvents.VAULT_CORRUPTION_WALLET_SUCCESSFULLY_RESTORED_CONTINUE_BUTTON_PRESSED, + ) + .addProperties({ ...deviceMetaData }) + .build(), ); await finishWalletRestore(); - }, [deviceMetaData, finishWalletRestore, trackEvent]); + }, [deviceMetaData, finishWalletRestore, trackEvent, createEventBuilder]); return ( diff --git a/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx b/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx index 9456c620599..c6818553941 100644 --- a/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx +++ b/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx @@ -115,7 +115,7 @@ const RevealPrivateCredential = ({ const dispatch = useDispatch(); const theme = useTheme(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { colors, themeAppearance } = theme; const styles = createStyles(theme); @@ -186,7 +186,9 @@ const RevealPrivateCredential = ({ updateNavBar(); // Track SRP Reveal screen rendered if (!isPrivateKey) { - trackEvent(MetaMetricsEvents.REVEAL_SRP_SCREEN); + trackEvent( + createEventBuilder(MetaMetricsEvents.REVEAL_SRP_SCREEN).build(), + ); } const unlockWithBiometrics = async () => { @@ -220,13 +222,21 @@ const RevealPrivateCredential = ({ const cancelReveal = () => { if (!unlocked) trackEvent( - isPrivateKey - ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_CANCELLED - : MetaMetricsEvents.REVEAL_SRP_CANCELLED, - { view: 'Enter password' }, + createEventBuilder( + isPrivateKey + ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_CANCELLED + : MetaMetricsEvents.REVEAL_SRP_CANCELLED, + ) + .addProperties({ + view: 'Enter password', + }) + .build(), ); - if (!isPrivateKey) trackEvent(MetaMetricsEvents.CANCEL_REVEAL_SRP_CTA); + if (!isPrivateKey) + trackEvent( + createEventBuilder(MetaMetricsEvents.CANCEL_REVEAL_SRP_CTA).build(), + ); if (cancel) return cancel(); navigateBack(); }; @@ -246,7 +256,9 @@ const RevealPrivateCredential = ({ if (!isPrivateKey) { const currentDate = new Date(); dispatch(recordSRPRevealTimestamp(currentDate.toString())); - trackEvent(MetaMetricsEvents.NEXT_REVEAL_SRP_CTA); + trackEvent( + createEventBuilder(MetaMetricsEvents.NEXT_REVEAL_SRP_CTA).build(), + ); } setIsModalVisible(true); setWarningIncorrectPassword(''); @@ -257,7 +269,8 @@ const RevealPrivateCredential = ({ }; const done = () => { - if (!isPrivateKey) trackEvent(MetaMetricsEvents.SRP_DONE_CTA); + if (!isPrivateKey) + trackEvent(createEventBuilder(MetaMetricsEvents.SRP_DONE_CTA).build()); navigateBack(); }; @@ -265,13 +278,19 @@ const RevealPrivateCredential = ({ privCredentialName: string, ) => { trackEvent( - privCredentialName === PRIVATE_KEY - ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_COMPLETED - : MetaMetricsEvents.REVEAL_SRP_COMPLETED, - { action: 'copied to clipboard' }, + createEventBuilder( + privCredentialName === PRIVATE_KEY + ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_COMPLETED + : MetaMetricsEvents.REVEAL_SRP_COMPLETED, + ) + .addProperties({ + action: 'copied to clipboard', + }) + .build(), ); - if (!isPrivateKey) trackEvent(MetaMetricsEvents.COPY_SRP); + if (!isPrivateKey) + trackEvent(createEventBuilder(MetaMetricsEvents.COPY_SRP).build()); await ClipboardManager.setStringExpire(clipboardPrivateCredential); @@ -322,22 +341,30 @@ const RevealPrivateCredential = ({ const onTabBarChange = (event: { i: number }) => { if (event.i === 0) { trackEvent( - isPrivateKey - ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_COMPLETED - : MetaMetricsEvents.REVEAL_SRP_COMPLETED, - { action: 'viewed SRP' }, + createEventBuilder( + isPrivateKey + ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_COMPLETED + : MetaMetricsEvents.REVEAL_SRP_COMPLETED, + ) + .addProperties({ action: 'viewed SRP' }) + .build(), ); - if (!isPrivateKey) trackEvent(MetaMetricsEvents.VIEW_SRP); + if (!isPrivateKey) + trackEvent(createEventBuilder(MetaMetricsEvents.VIEW_SRP).build()); } else if (event.i === 1) { trackEvent( - isPrivateKey - ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_COMPLETED - : MetaMetricsEvents.REVEAL_SRP_COMPLETED, - { action: 'viewed QR code' }, + createEventBuilder( + isPrivateKey + ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_COMPLETED + : MetaMetricsEvents.REVEAL_SRP_COMPLETED, + ) + .addProperties({ action: 'viewed QR code' }) + .build(), ); - if (!isPrivateKey) trackEvent(MetaMetricsEvents.VIEW_SRP_QR); + if (!isPrivateKey) + trackEvent(createEventBuilder(MetaMetricsEvents.VIEW_SRP_QR).build()); } }; @@ -438,13 +465,20 @@ const RevealPrivateCredential = ({ const closeModal = () => { trackEvent( - isPrivateKey - ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_CANCELLED - : MetaMetricsEvents.REVEAL_SRP_CANCELLED, - { view: 'Hold to reveal' }, + createEventBuilder( + isPrivateKey + ? MetaMetricsEvents.REVEAL_PRIVATE_KEY_CANCELLED + : MetaMetricsEvents.REVEAL_SRP_CANCELLED, + ) + .addProperties({ view: 'Hold to reveal' }) + .build(), ); - trackEvent(MetaMetricsEvents.SRP_DISMISS_HOLD_TO_REVEAL_DIALOG); + trackEvent( + createEventBuilder( + MetaMetricsEvents.SRP_DISMISS_HOLD_TO_REVEAL_DIALOG, + ).build(), + ); setIsModalVisible(false); }; diff --git a/app/components/Views/Settings/AdvancedSettings/index.js b/app/components/Views/Settings/AdvancedSettings/index.js index 6f7da7c6191..686a4cdd5a8 100644 --- a/app/components/Views/Settings/AdvancedSettings/index.js +++ b/app/components/Views/Settings/AdvancedSettings/index.js @@ -261,10 +261,15 @@ class AdvancedSettings extends PureComponent { smartTransactionsOptInStatus, ); - this.props.metrics.trackEvent(MetaMetricsEvents.SMART_TRANSACTION_OPT_IN, { - stx_opt_in: smartTransactionsOptInStatus, - location: 'Advanced Settings', - }); + this.props.metrics.trackEvent( + this.props.metrics + .createEventBuilder(MetaMetricsEvents.SMART_TRANSACTION_OPT_IN) + .addProperties({ + stx_opt_in: smartTransactionsOptInStatus, + location: 'Advanced Settings', + }) + .build(), + ); }; openLinkAboutStx = () => { diff --git a/app/components/Views/Settings/AutoDetectNFTSettings/__snapshots__/index.test.tsx.snap b/app/components/Views/Settings/AutoDetectNFTSettings/__snapshots__/index.test.tsx.snap index 16f08a0bb24..aee591dce11 100644 --- a/app/components/Views/Settings/AutoDetectNFTSettings/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/Settings/AutoDetectNFTSettings/__snapshots__/index.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AutoDetectNFTSettings should render correctly 1`] = ` +exports[`AutoDetectNFTSettings render matches snapshot 1`] = ` { - mockSetDisplayNftMedia.mockClear(); - mockSetUseNftDetection.mockClear(); - mockAddTraitsToUser.mockClear(); - mockTrackEvent.mockClear(); -}); const mockEngine = Engine; jest.mock('../../../../core/Engine', () => { mockSetDisplayNftMedia = jest.fn(); mockSetUseNftDetection = jest.fn(); - mockAddTraitsToUser = jest.fn(); - mockTrackEvent = jest.fn(); return { init: () => mockEngine.init({}), context: { @@ -52,15 +43,24 @@ jest.mock('@react-navigation/native', () => ({ useNavigation: jest.fn(() => mockNavigation), })); -jest.mock('../../../hooks/useMetrics', () => ({ - useMetrics: () => ({ - addTraitsToUser: mockAddTraitsToUser, - trackEvent: mockTrackEvent, - }), - MetaMetricsEvents: { - NFT_AUTO_DETECTION_ENABLED: 'NFT_AUTO_DETECTION_ENABLED', - }, -})); +jest.mock('../../../hooks/useMetrics'); + +const mockTrackEvent = jest.fn(); +const mockAddTraitsToUser = jest.fn(); + +(useMetrics as jest.MockedFn).mockReturnValue({ + trackEvent: mockTrackEvent, + createEventBuilder: MetricsEventBuilder.createEventBuilder, + enable: jest.fn(), + addTraitsToUser: mockAddTraitsToUser, + createDataDeletionTask: jest.fn(), + checkDataDeleteStatus: jest.fn(), + getDeleteRegulationCreationDate: jest.fn(), + getDeleteRegulationId: jest.fn(), + isDataRecorded: jest.fn(), + isEnabled: jest.fn(), + getMetaMetricsId: jest.fn(), +}); jest.mock('../../../../util/general', () => ({ timeoutFetch: jest.fn(), @@ -91,7 +91,7 @@ describe('AutoDetectNFTSettings', () => { }, }; - it('should render correctly', () => { + it('render matches snapshot', () => { const tree = renderWithProvider(, { state: initialState, }); @@ -99,7 +99,7 @@ describe('AutoDetectNFTSettings', () => { }); describe('NFT Autodetection', () => { - it('should render NFT autodetection switch', () => { + it('renders NFT autodetection switch', () => { const { getByTestId } = renderWithProvider(, { state: initialState, }); @@ -107,7 +107,7 @@ describe('AutoDetectNFTSettings', () => { expect(autoDetectSwitch).toBeTruthy(); }); - it('should toggle NFT autodetection when switch is pressed', () => { + it('toggles NFT autodetection when switch is pressed', () => { const { getByTestId } = renderWithProvider(, { state: initialState, }); @@ -124,15 +124,18 @@ describe('AutoDetectNFTSettings', () => { 'NFT Autodetection': 'ON', }); expect(mockTrackEvent).toHaveBeenCalledWith( - 'NFT_AUTO_DETECTION_ENABLED', - { - 'NFT Autodetection': 'ON', - location: 'app_settings', - }, + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.NFT_AUTO_DETECTION_ENABLED, + ) + .addProperties({ + 'NFT Autodetection': 'ON', + location: 'app_settings', + }) + .build(), ); }); - it('should not enable display NFT media when autodetection is turned off', () => { + it('does not enable display NFT media when autodetection is turned off', () => { const { getByTestId } = renderWithProvider(, { state: initialState, }); @@ -147,11 +150,14 @@ describe('AutoDetectNFTSettings', () => { 'NFT Autodetection': 'OFF', }); expect(mockTrackEvent).toHaveBeenCalledWith( - 'NFT_AUTO_DETECTION_ENABLED', - { - 'NFT Autodetection': 'OFF', - location: 'app_settings', - }, + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.NFT_AUTO_DETECTION_ENABLED, + ) + .addProperties({ + 'NFT Autodetection': 'OFF', + location: 'app_settings', + }) + .build(), ); }); }); diff --git a/app/components/Views/Settings/AutoDetectNFTSettings/index.tsx b/app/components/Views/Settings/AutoDetectNFTSettings/index.tsx index 0cc7ecbe88d..f656a2377e4 100644 --- a/app/components/Views/Settings/AutoDetectNFTSettings/index.tsx +++ b/app/components/Views/Settings/AutoDetectNFTSettings/index.tsx @@ -20,7 +20,7 @@ import createStyles from './index.styles'; import { NFT_AUTO_DETECT_MODE_SECTION } from './index.constants'; const AutoDetectNFTSettings = () => { - const { trackEvent, addTraitsToUser } = useMetrics(); + const { trackEvent, addTraitsToUser, createEventBuilder } = useMetrics(); const theme = useTheme(); const { colors } = theme; const styles = createStyles(); @@ -40,12 +40,16 @@ const AutoDetectNFTSettings = () => { : UserProfileProperty.OFF, }; addTraitsToUser(traits); - trackEvent(MetaMetricsEvents.NFT_AUTO_DETECTION_ENABLED, { - ...traits, - location: 'app_settings', - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NFT_AUTO_DETECTION_ENABLED) + .addProperties({ + ...traits, + location: 'app_settings', + }) + .build(), + ); }, - [addTraitsToUser, trackEvent], + [addTraitsToUser, trackEvent, createEventBuilder], ); return ( diff --git a/app/components/Views/Settings/GeneralSettings/index.js b/app/components/Views/Settings/GeneralSettings/index.js index 452ac319027..2fd34c94bc5 100644 --- a/app/components/Views/Settings/GeneralSettings/index.js +++ b/app/components/Views/Settings/GeneralSettings/index.js @@ -37,6 +37,7 @@ import Text, { TextColor, } from '../../../../component-library/components/Texts/Text'; import { MetaMetricsEvents } from '../../../../core/Analytics'; +import { MetricsEventBuilder } from '../../../../core/Analytics/MetricsEventBuilder'; import { UserProfileProperty } from '../../../../util/metrics/UserSettingsAnalyticsMetaData/UserProfileAnalyticsMetaData.types'; const diameter = 40; @@ -60,20 +61,30 @@ export const updateUserTraitsWithCurrentCurrency = (currency, metrics) => { // track event and add selected currency to user profile for analytics const traits = { [UserProfileProperty.CURRENT_CURRENCY]: currency }; metrics.addTraitsToUser(traits); - metrics.trackEvent(MetaMetricsEvents.CURRENCY_CHANGED, { - ...traits, - location: 'app_settings', - }); + metrics.trackEvent( + MetricsEventBuilder.createEventBuilder(MetaMetricsEvents.CURRENCY_CHANGED) + .addProperties({ + ...traits, + location: 'app_settings', + }) + .build(), + ); }; export const updateUserTraitsWithCurrencyType = (primaryCurrency, metrics) => { // track event and add primary currency preference (fiat/crypto) to user profile for analytics const traits = { [UserProfileProperty.PRIMARY_CURRENCY]: primaryCurrency }; metrics.addTraitsToUser(traits); - metrics.trackEvent(MetaMetricsEvents.PRIMARY_CURRENCY_TOGGLE, { - ...traits, - location: 'app_settings', - }); + metrics.trackEvent( + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.PRIMARY_CURRENCY_TOGGLE, + ) + .addProperties({ + ...traits, + location: 'app_settings', + }) + .build(), + ); }; const createStyles = (colors) => diff --git a/app/components/Views/Settings/GeneralSettings/index.test.tsx b/app/components/Views/Settings/GeneralSettings/index.test.tsx index 39af1c99027..98a4080df9a 100644 --- a/app/components/Views/Settings/GeneralSettings/index.test.tsx +++ b/app/components/Views/Settings/GeneralSettings/index.test.tsx @@ -10,6 +10,7 @@ import { AppThemeKey } from '../../../../util/theme/models'; import { backgroundState } from '../../../../util/test/initial-root-state'; import { MetaMetricsEvents } from '../../../../core/Analytics'; import { UserProfileProperty } from '../../../../util/metrics/UserSettingsAnalyticsMetaData/UserProfileAnalyticsMetaData.types'; +import { MetricsEventBuilder } from '../../../../core/Analytics/MetricsEventBuilder'; jest.mock('../../../../core/Analytics'); @@ -40,19 +41,15 @@ describe('GeneralSettings', () => { }); }); -describe('updateUserTraitsWithCurrentCurrency', () => { - let mockMetrics: { addTraitsToUser: () => void; trackEvent: () => void }; - - beforeEach(() => { - // Create a mock for the metrics object with spies on the required methods - mockMetrics = { - addTraitsToUser: jest.fn(), - trackEvent: jest.fn(), - }; - }); +const mockMetrics = { + addTraitsToUser: jest.fn(), + trackEvent: jest.fn(), + createEventBuilder: MetricsEventBuilder.createEventBuilder, +}; +describe('updateUserTraitsWithCurrentCurrency', () => { afterEach(() => { - jest.clearAllMocks(); // Clear mocks after each test to avoid interference + jest.clearAllMocks(); }); it('adds selected currency trait', () => { @@ -60,7 +57,6 @@ describe('updateUserTraitsWithCurrentCurrency', () => { updateUserTraitsWithCurrentCurrency(mockCurrency, mockMetrics); - // Check if addTraitsToUser was called with the correct argument expect(mockMetrics.addTraitsToUser).toHaveBeenCalledWith({ [UserProfileProperty.CURRENT_CURRENCY]: mockCurrency, }); @@ -73,11 +69,12 @@ describe('updateUserTraitsWithCurrentCurrency', () => { // Check if trackEvent was called with the correct event and properties expect(mockMetrics.trackEvent).toHaveBeenCalledWith( - MetaMetricsEvents.CURRENCY_CHANGED, - { - [UserProfileProperty.CURRENT_CURRENCY]: mockCurrency, - location: 'app_settings', - }, + MetricsEventBuilder.createEventBuilder(MetaMetricsEvents.CURRENCY_CHANGED) + .addProperties({ + [UserProfileProperty.CURRENT_CURRENCY]: mockCurrency, + location: 'app_settings', + }) + .build(), ); }); @@ -91,18 +88,8 @@ describe('updateUserTraitsWithCurrentCurrency', () => { }); describe('updateUserTraitsWithCurrencyType', () => { - let mockMetrics: { addTraitsToUser: () => void; trackEvent: () => void }; - - beforeEach(() => { - // Create a mock for the metrics object with spies on the required methods - mockMetrics = { - addTraitsToUser: jest.fn(), - trackEvent: jest.fn(), - }; - }); - afterEach(() => { - jest.clearAllMocks(); // Reset mocks after each test + jest.clearAllMocks(); }); it('adds the primary currency preference', () => { @@ -110,7 +97,6 @@ describe('updateUserTraitsWithCurrencyType', () => { updateUserTraitsWithCurrencyType(primaryCurrency, mockMetrics); - // Check if addTraitsToUser was called with the correct argument expect(mockMetrics.addTraitsToUser).toHaveBeenCalledWith({ [UserProfileProperty.PRIMARY_CURRENCY]: primaryCurrency, }); @@ -123,11 +109,14 @@ describe('updateUserTraitsWithCurrencyType', () => { // Check if trackEvent was called with the correct event and properties expect(mockMetrics.trackEvent).toHaveBeenCalledWith( - MetaMetricsEvents.PRIMARY_CURRENCY_TOGGLE, - { - [UserProfileProperty.PRIMARY_CURRENCY]: primaryCurrency, - location: 'app_settings', - }, + MetricsEventBuilder.createEventBuilder( + MetaMetricsEvents.PRIMARY_CURRENCY_TOGGLE, + ) + .addProperties({ + [UserProfileProperty.PRIMARY_CURRENCY]: primaryCurrency, + location: 'app_settings', + }) + .build(), ); }); diff --git a/app/components/Views/Settings/NotificationsSettings/NotificationOptionToggle/index.tsx b/app/components/Views/Settings/NotificationsSettings/NotificationOptionToggle/index.tsx index ad6ae7921a3..d8bae30d30a 100644 --- a/app/components/Views/Settings/NotificationsSettings/NotificationOptionToggle/index.tsx +++ b/app/components/Views/Settings/NotificationsSettings/NotificationOptionToggle/index.tsx @@ -34,7 +34,9 @@ interface NotificationOptionsToggleProps { disabledSwitch?: boolean; isLoading?: boolean; isEnabled: boolean; - updateAndfetchAccountSettings: () => Promise | undefined>; + updateAndfetchAccountSettings: () => Promise< + Record | undefined + >; } /** @@ -55,7 +57,7 @@ const NotificationOptionToggle = ({ const theme = useTheme(); const { colors } = theme; const styles = createStyles(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const { toggleAccount, loading: isUpdatingAccount } = useUpdateAccountSetting( address, @@ -65,13 +67,17 @@ const NotificationOptionToggle = ({ const loading = isLoading || isUpdatingAccount; const handleToggleAccountNotifications = useCallback(async () => { - trackEvent(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, { - settings_type: 'account_notifications', - old_value: isEnabled, - new_value: !isEnabled, - }); + trackEvent( + createEventBuilder(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED) + .addProperties({ + settings_type: 'account_notifications', + old_value: isEnabled, + new_value: !isEnabled, + }) + .build(), + ); await toggleAccount(!isEnabled); - }, [isEnabled, toggleAccount, trackEvent]); + }, [isEnabled, toggleAccount, trackEvent, createEventBuilder]); return ( diff --git a/app/components/Views/Settings/NotificationsSettings/index.tsx b/app/components/Views/Settings/NotificationsSettings/index.tsx index 20456dc6389..d51a8208306 100644 --- a/app/components/Views/Settings/NotificationsSettings/index.tsx +++ b/app/components/Views/Settings/NotificationsSettings/index.tsx @@ -12,7 +12,7 @@ import Text, { TextVariant, TextColor, } from '../../../../component-library/components/Texts/Text'; -import { AccountsList} from './AccountsList'; +import { AccountsList } from './AccountsList'; import { useAccounts } from '../../../../components/hooks/useAccounts'; import { useMetrics } from '../../../../components/hooks/useMetrics'; import { AvatarAccountType } from '../../../../component-library/components/Avatars/Avatar'; @@ -49,7 +49,9 @@ import { useAccountSettingsProps, useSwitchNotifications, } from '../../../../util/notifications/hooks/useSwitchNotifications'; -import styleSheet, { styles as navigationOptionsStyles } from './NotificationsSettings.styles'; +import styleSheet, { + styles as navigationOptionsStyles, +} from './NotificationsSettings.styles'; import AppConstants from '../../../../core/AppConstants'; import notificationsRows from './notificationsRows'; import { IconName } from '../../../../component-library/components/Icons/Icon'; @@ -105,7 +107,7 @@ const MainNotificationSettings = ({ }; const NotificationsSettings = ({ navigation, route }: Props) => { const { accounts } = useAccounts(); - const { trackEvent } = useMetrics(); + const { trackEvent, createEventBuilder } = useMetrics(); const theme = useTheme(); const isMetamaskNotificationsEnabled = useSelector( @@ -145,7 +147,9 @@ const NotificationsSettings = ({ navigation, route }: Props) => { const [uiNotificationStatus, setUiNotificationStatus] = React.useState(false); const [platformAnnouncementsState, setPlatformAnnouncementsState] = React.useState(isFeatureAnnouncementsEnabled); - const accountSettingsData = useSelector((state: RootState) => state.notifications); + const accountSettingsData = useSelector( + (state: RootState) => state.notifications, + ); const loading = enableLoading || disableLoading; const errorText = enablingError || disablingError; const loadingText = !uiNotificationStatus @@ -179,12 +183,21 @@ const NotificationsSettings = ({ navigation, route }: Props) => { const toggleCustomNotificationsEnabled = useCallback(async () => { setPlatformAnnouncementsState(!platformAnnouncementsState); await switchFeatureAnnouncements(!platformAnnouncementsState); - trackEvent(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, { - settings_type: 'product_announcements', - old_value: platformAnnouncementsState, - new_value: !platformAnnouncementsState, - }); - }, [platformAnnouncementsState, switchFeatureAnnouncements, trackEvent]); + trackEvent( + createEventBuilder(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED) + .addProperties({ + settings_type: 'product_announcements', + old_value: platformAnnouncementsState, + new_value: !platformAnnouncementsState, + }) + .build(), + ); + }, [ + platformAnnouncementsState, + switchFeatureAnnouncements, + trackEvent, + createEventBuilder, + ]); const goToLearnMore = () => { Linking.openURL(AppConstants.URLS.PROFILE_SYNC); @@ -194,7 +207,7 @@ const NotificationsSettings = ({ navigation, route }: Props) => { navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.SHEET.RESET_NOTIFICATIONS, }); - },[navigation]); + }, [navigation]); useEffect(() => { navigation.setOptions( @@ -208,15 +221,18 @@ const NotificationsSettings = ({ navigation, route }: Props) => { ); }, [colors, isFullScreenModal, navigation]); - const renderResetNotificationsBtn = useCallback(() => ( -