1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

UX: Multichain: Analytics (#18674)

This commit is contained in:
David Walsh 2023-04-27 09:28:08 -05:00 committed by GitHub
parent bbb35dbe8d
commit e339afce7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 379 additions and 57 deletions

View File

@ -458,6 +458,7 @@ export enum MetaMetricsEventName {
AppInstalled = 'App Installed', AppInstalled = 'App Installed',
AppUnlocked = 'App Unlocked', AppUnlocked = 'App Unlocked',
AppUnlockedFailed = 'App Unlocked Failed', AppUnlockedFailed = 'App Unlocked Failed',
AppLocked = 'App Locked',
AppWindowExpanded = 'App Window Expanded', AppWindowExpanded = 'App Window Expanded',
BridgeLinkClicked = 'Bridge Link Clicked', BridgeLinkClicked = 'Bridge Link Clicked',
DecryptionApproved = 'Decryption Approved', DecryptionApproved = 'Decryption Approved',
@ -541,6 +542,17 @@ export enum MetaMetricsEventName {
///: BEGIN:ONLY_INCLUDE_IN(build-mmi) ///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
UserClickedDeepLink = 'User clicked deeplink', UserClickedDeepLink = 'User clicked deeplink',
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
AccountDetailMenuOpened = 'Account Details Menu Opened',
BlockExplorerLinkClicked = 'Block Explorer Clicked',
AccountRemoved = 'Account Removed',
TestNetworksDisplayed = 'Test Networks Displayed',
AddNetworkButtonClick = 'Add Network Button Clicked',
CustomNetworkAdded = 'Custom Network Added',
TokenDetailsOpened = 'Token Details Opened',
NftScreenOpened = 'NFT Screen Opened',
ActivityScreenOpened = 'Activity Screen Opened',
WhatsNewViewed = `What's New Viewed`,
WhatsNewClicked = `What's New Link Clicked`,
} }
export enum MetaMetricsEventAccountType { export enum MetaMetricsEventAccountType {
@ -583,6 +595,7 @@ export enum MetaMetricsEventCategory {
///: BEGIN:ONLY_INCLUDE_IN(build-mmi) ///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
MMI = 'Institutional', MMI = 'Institutional',
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
Tokens = 'Tokens',
} }
export enum MetaMetricsEventLinkType { export enum MetaMetricsEventLinkType {

View File

@ -34,7 +34,7 @@ describe('Portfolio site', function () {
// Verify site // Verify site
assert.equal( assert.equal(
await driver.getCurrentUrl(), await driver.getCurrentUrl(),
'http://127.0.0.1:8080/?metamaskEntry=ext', 'http://127.0.0.1:8080/?metamaskEntry=ext&metametricsId=null',
); );
}, },
); );

View File

@ -6,7 +6,10 @@ import classNames from 'classnames';
import Box from '../../../ui/box/box'; import Box from '../../../ui/box/box';
import Button from '../../../ui/button'; import Button from '../../../ui/button';
import { useI18nContext } from '../../../../hooks/useI18nContext'; import { useI18nContext } from '../../../../hooks/useI18nContext';
import { getDetectedTokensInCurrentNetwork } from '../../../../selectors'; import {
getCurrentChainId,
getDetectedTokensInCurrentNetwork,
} from '../../../../selectors';
import { MetaMetricsContext } from '../../../../contexts/metametrics'; import { MetaMetricsContext } from '../../../../contexts/metametrics';
import { import {
MetaMetricsEventCategory, MetaMetricsEventCategory,
@ -23,13 +26,16 @@ const DetectedTokensLink = ({ className = '', setShowDetectedTokens }) => {
({ address, symbol }) => `${symbol} - ${address}`, ({ address, symbol }) => `${symbol} - ${address}`,
); );
const chainId = useSelector(getCurrentChainId);
const onClick = () => { const onClick = () => {
setShowDetectedTokens(true); setShowDetectedTokens(true);
trackEvent({ trackEvent({
event: MetaMetricsEventName.TokenImportClicked, event: MetaMetricsEventName.TokenImportClicked,
category: MetaMetricsEventCategory.Wallet, category: MetaMetricsEventCategory.Wallet,
properties: { properties: {
source: MetaMetricsTokenEventSource.Detected, source_connection_method: MetaMetricsTokenEventSource.Detected,
chain_id: chainId,
tokens: detectedTokensDetails, tokens: detectedTokensDetails,
}, },
}); });

View File

@ -12,6 +12,7 @@ import { TypographyVariant } from '../../../../helpers/constants/design-system';
import SecurityProviderBannerMessage from '../../security-provider-banner-message/security-provider-banner-message'; import SecurityProviderBannerMessage from '../../security-provider-banner-message/security-provider-banner-message';
import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../../security-provider-banner-message/security-provider-banner-message.constants'; import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../../security-provider-banner-message/security-provider-banner-message.constants';
import { getPortfolioUrl } from '../../../../helpers/utils/portfolio';
import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.'; import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.';
export default class ConfirmPageContainerContent extends Component { export default class ConfirmPageContainerContent extends Component {
@ -54,6 +55,7 @@ export default class ConfirmPageContainerContent extends Component {
transactionType: PropTypes.string, transactionType: PropTypes.string,
isBuyableChain: PropTypes.bool, isBuyableChain: PropTypes.bool,
txData: PropTypes.object, txData: PropTypes.object,
metaMetricsId: PropTypes.string,
}; };
renderContent() { renderContent() {
@ -159,6 +161,7 @@ export default class ConfirmPageContainerContent extends Component {
transactionType, transactionType,
isBuyableChain, isBuyableChain,
txData, txData,
metaMetricsId,
} = this.props; } = this.props;
const { t } = this.context; const { t } = this.context;
@ -222,9 +225,12 @@ export default class ConfirmPageContainerContent extends Component {
type="inline" type="inline"
className="confirm-page-container-content__link" className="confirm-page-container-content__link"
onClick={() => { onClick={() => {
const portfolioUrl = process.env.PORTFOLIO_URL;
global.platform.openTab({ global.platform.openTab({
url: `${portfolioUrl}/buy?metamaskEntry=ext_buy_button`, url: getPortfolioUrl(
'buy',
'ext_buy_button',
metaMetricsId,
),
}); });
}} }}
key={`${nativeCurrency}-buy-button`} key={`${nativeCurrency}-buy-button`}

View File

@ -39,6 +39,7 @@ import {
getIsBuyableChain, getIsBuyableChain,
getMetadataContractName, getMetadataContractName,
getMetaMaskIdentities, getMetaMaskIdentities,
getMetaMetricsId,
getNetworkIdentifier, getNetworkIdentifier,
getSwapsDefaultToken, getSwapsDefaultToken,
} from '../../../selectors'; } from '../../../selectors';
@ -116,6 +117,8 @@ const ConfirmPageContainer = (props) => {
getMetadataContractName(state, toAddress), getMetadataContractName(state, toAddress),
); );
const metaMetricsId = useSelector(getMetaMetricsId);
// TODO: Move useRamps hook to the confirm-transaction-base parent component. // TODO: Move useRamps hook to the confirm-transaction-base parent component.
// TODO: openBuyCryptoInPdapp should be passed to this component as a custom prop. // TODO: openBuyCryptoInPdapp should be passed to this component as a custom prop.
// We try to keep this component for layout purpose only, we need to move this hook to the confirm-transaction-base parent // We try to keep this component for layout purpose only, we need to move this hook to the confirm-transaction-base parent
@ -198,6 +201,7 @@ const ConfirmPageContainer = (props) => {
)} )}
{contentComponent || ( {contentComponent || (
<ConfirmPageContainerContent <ConfirmPageContainerContent
metaMetricsId={metaMetricsId}
action={action} action={action}
title={title} title={title}
image={image} image={image}

View File

@ -9,7 +9,10 @@ import {
MetaMetricsEventName, MetaMetricsEventName,
MetaMetricsTokenEventSource, MetaMetricsTokenEventSource,
} from '../../../../../shared/constants/metametrics'; } from '../../../../../shared/constants/metametrics';
import { getDetectedTokensInCurrentNetwork } from '../../../../selectors'; import {
getCurrentChainId,
getDetectedTokensInCurrentNetwork,
} from '../../../../selectors';
import Popover from '../../../ui/popover'; import Popover from '../../../ui/popover';
import Box from '../../../ui/box'; import Box from '../../../ui/box';
@ -27,6 +30,8 @@ const DetectedTokenSelectionPopover = ({
const t = useI18nContext(); const t = useI18nContext();
const trackEvent = useContext(MetaMetricsContext); const trackEvent = useContext(MetaMetricsContext);
const chainId = useSelector(getCurrentChainId);
const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork); const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork);
const { selected: selectedTokens = [] } = const { selected: selectedTokens = [] } =
sortingBasedOnTokenSelection(tokensListDetected); sortingBasedOnTokenSelection(tokensListDetected);
@ -44,7 +49,8 @@ const DetectedTokenSelectionPopover = ({
event: MetaMetricsEventName.TokenImportCanceled, event: MetaMetricsEventName.TokenImportCanceled,
category: MetaMetricsEventCategory.Wallet, category: MetaMetricsEventCategory.Wallet,
properties: { properties: {
source: MetaMetricsTokenEventSource.Detected, source_connection_method: MetaMetricsTokenEventSource.Detected,
chain_id: chainId,
tokens: eventTokensDetails, tokens: eventTokensDetails,
}, },
}); });

View File

@ -8,7 +8,10 @@ import {
ignoreTokens, ignoreTokens,
setNewTokensImported, setNewTokensImported,
} from '../../../store/actions'; } from '../../../store/actions';
import { getDetectedTokensInCurrentNetwork } from '../../../selectors'; import {
getCurrentChainId,
getDetectedTokensInCurrentNetwork,
} from '../../../selectors';
import { MetaMetricsContext } from '../../../contexts/metametrics'; import { MetaMetricsContext } from '../../../contexts/metametrics';
import { import {
@ -47,6 +50,7 @@ const DetectedToken = ({ setShowDetectedTokens }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const trackEvent = useContext(MetaMetricsContext); const trackEvent = useContext(MetaMetricsContext);
const chainId = useSelector(getCurrentChainId);
const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork); const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork);
const [tokensListDetected, setTokensListDetected] = useState(() => const [tokensListDetected, setTokensListDetected] = useState(() =>
@ -69,9 +73,11 @@ const DetectedToken = ({ setShowDetectedTokens }) => {
token_symbol: importedToken.symbol, token_symbol: importedToken.symbol,
token_contract_address: importedToken.address, token_contract_address: importedToken.address,
token_decimal_precision: importedToken.decimals, token_decimal_precision: importedToken.decimals,
source: MetaMetricsTokenEventSource.Detected, source_connection_method: MetaMetricsTokenEventSource.Detected,
token_standard: TokenStandard.ERC20, token_standard: TokenStandard.ERC20,
asset_type: AssetType.token, asset_type: AssetType.token,
token_added_type: 'detected',
chain_id: chainId,
}, },
}); });
}); });

View File

@ -30,6 +30,8 @@ import {
getIsBuyableChain, getIsBuyableChain,
getNativeCurrencyImage, getNativeCurrencyImage,
getSelectedAccountCachedBalance, getSelectedAccountCachedBalance,
getCurrentChainId,
getMetaMetricsId,
} from '../../../selectors'; } from '../../../selectors';
import { setSwapsFromToken } from '../../../ducks/swaps/swaps'; import { setSwapsFromToken } from '../../../ducks/swaps/swaps';
import IconButton from '../../ui/icon-button'; import IconButton from '../../ui/icon-button';
@ -52,6 +54,7 @@ import {
} from '../../component-library'; } from '../../component-library';
import { IconColor } from '../../../helpers/constants/design-system'; import { IconColor } from '../../../helpers/constants/design-system';
import useRamps from '../../../hooks/experiences/useRamps'; import useRamps from '../../../hooks/experiences/useRamps';
import { getPortfolioUrl } from '../../../helpers/utils/portfolio';
import WalletOverview from './wallet-overview'; import WalletOverview from './wallet-overview';
const EthOverview = ({ className }) => { const EthOverview = ({ className }) => {
@ -70,6 +73,8 @@ const EthOverview = ({ className }) => {
const isBuyableChain = useSelector(getIsBuyableChain); const isBuyableChain = useSelector(getIsBuyableChain);
const primaryTokenImage = useSelector(getNativeCurrencyImage); const primaryTokenImage = useSelector(getNativeCurrencyImage);
const defaultSwapsToken = useSelector(getSwapsDefaultToken); const defaultSwapsToken = useSelector(getSwapsDefaultToken);
const chainId = useSelector(getCurrentChainId);
const metaMetricsId = useSelector(getMetaMetricsId);
///: BEGIN:ONLY_INCLUDE_IN(build-mmi) ///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
const mmiPortfolioEnabled = useSelector(getMmiPortfolioEnabled); const mmiPortfolioEnabled = useSelector(getMmiPortfolioEnabled);
@ -166,9 +171,13 @@ const EthOverview = ({ className }) => {
ariaLabel={t('portfolio')} ariaLabel={t('portfolio')}
size={ButtonIconSize.Lg} size={ButtonIconSize.Lg}
onClick={() => { onClick={() => {
const portfolioUrl = process.env.PORTFOLIO_URL; const portfolioUrl = getPortfolioUrl(
'',
'ext',
metaMetricsId,
);
global.platform.openTab({ global.platform.openTab({
url: `${portfolioUrl}?metamaskEntry=ext`, url: portfolioUrl,
}); });
trackEvent( trackEvent(
{ {
@ -226,6 +235,8 @@ const EthOverview = ({ className }) => {
properties: { properties: {
location: 'Home', location: 'Home',
text: 'Buy', text: 'Buy',
chain_id: chainId,
token_symbol: defaultSwapsToken,
}, },
}); });
}} }}
@ -257,6 +268,7 @@ const EthOverview = ({ className }) => {
token_symbol: 'ETH', token_symbol: 'ETH',
location: 'Home', location: 'Home',
text: 'Send', text: 'Send',
chain_id: chainId,
}, },
}); });
dispatch( dispatch(
@ -284,6 +296,7 @@ const EthOverview = ({ className }) => {
token_symbol: 'ETH', token_symbol: 'ETH',
location: MetaMetricsSwapsEventSource.MainView, location: MetaMetricsSwapsEventSource.MainView,
text: 'Swap', text: 'Swap',
chain_id: chainId,
}, },
}); });
dispatch(setSwapsFromToken(defaultSwapsToken)); dispatch(setSwapsFromToken(defaultSwapsToken));
@ -320,10 +333,13 @@ const EthOverview = ({ className }) => {
label={t('bridge')} label={t('bridge')}
onClick={() => { onClick={() => {
if (isBridgeChain) { if (isBridgeChain) {
const portfolioUrl = process.env.PORTFOLIO_URL; const portfolioUrl = getPortfolioUrl(
const bridgeUrl = `${portfolioUrl}/bridge`; 'bridge',
'ext_bridge_button',
metaMetricsId,
);
global.platform.openTab({ global.platform.openTab({
url: `${bridgeUrl}?metamaskEntry=ext_bridge_button${ url: `${portfolioUrl}${
location.pathname.includes('asset') ? '&token=native' : '' location.pathname.includes('asset') ? '&token=native' : ''
}`, }`,
}); });
@ -333,6 +349,8 @@ const EthOverview = ({ className }) => {
properties: { properties: {
location: 'Home', location: 'Home',
text: 'Bridge', text: 'Bridge',
chain_id: chainId,
token_symbol: 'ETH',
}, },
}); });
} }

View File

@ -20,6 +20,8 @@ import {
getIsSwapsChain, getIsSwapsChain,
getIsBuyableChain, getIsBuyableChain,
getIsBridgeToken, getIsBridgeToken,
getCurrentChainId,
getMetaMetricsId,
} from '../../../selectors'; } from '../../../selectors';
import IconButton from '../../ui/icon-button'; import IconButton from '../../ui/icon-button';
@ -39,6 +41,7 @@ import { ButtonIcon, Icon, IconName } from '../../component-library';
import { IconColor } from '../../../helpers/constants/design-system'; import { IconColor } from '../../../helpers/constants/design-system';
import { BUTTON_ICON_SIZES } from '../../component-library/button-icon/deprecated'; import { BUTTON_ICON_SIZES } from '../../component-library/button-icon/deprecated';
import { getPortfolioUrl } from '../../../helpers/utils/portfolio';
import WalletOverview from './wallet-overview'; import WalletOverview from './wallet-overview';
const TokenOverview = ({ className, token }) => { const TokenOverview = ({ className, token }) => {
@ -56,9 +59,11 @@ const TokenOverview = ({ className, token }) => {
balanceToRender, balanceToRender,
token.symbol, token.symbol,
); );
const chainId = useSelector(getCurrentChainId);
const isSwapsChain = useSelector(getIsSwapsChain); const isSwapsChain = useSelector(getIsSwapsChain);
const isBridgeToken = useSelector(getIsBridgeToken(token.address)); const isBridgeToken = useSelector(getIsBridgeToken(token.address));
const isBuyableChain = useSelector(getIsBuyableChain); const isBuyableChain = useSelector(getIsBuyableChain);
const metaMetricsId = useSelector(getMetaMetricsId);
const { openBuyCryptoInPdapp } = useRamps(); const { openBuyCryptoInPdapp } = useRamps();
@ -92,9 +97,9 @@ const TokenOverview = ({ className, token }) => {
ariaLabel={t('portfolio')} ariaLabel={t('portfolio')}
size={BUTTON_ICON_SIZES.LG} size={BUTTON_ICON_SIZES.LG}
onClick={() => { onClick={() => {
const portfolioUrl = process.env.PORTFOLIO_URL; const portfolioUrl = getPortfolioUrl('', 'ext', metaMetricsId);
global.platform.openTab({ global.platform.openTab({
url: `${portfolioUrl}?metamaskEntry=ext`, url: portfolioUrl,
}); });
trackEvent( trackEvent(
{ {
@ -137,6 +142,8 @@ const TokenOverview = ({ className, token }) => {
properties: { properties: {
location: 'Token Overview', location: 'Token Overview',
text: 'Buy', text: 'Buy',
chain_id: chainId,
token_symbol: token.symbol,
}, },
}); });
}} }}
@ -152,6 +159,7 @@ const TokenOverview = ({ className, token }) => {
token_symbol: token.symbol, token_symbol: token.symbol,
location: MetaMetricsSwapsEventSource.TokenView, location: MetaMetricsSwapsEventSource.TokenView,
text: 'Send', text: 'Send',
chain_id: chainId,
}, },
}); });
try { try {
@ -195,6 +203,7 @@ const TokenOverview = ({ className, token }) => {
token_symbol: token.symbol, token_symbol: token.symbol,
location: MetaMetricsSwapsEventSource.TokenView, location: MetaMetricsSwapsEventSource.TokenView,
text: 'Swap', text: 'Swap',
chain_id: chainId,
}, },
}); });
dispatch( dispatch(
@ -225,11 +234,13 @@ const TokenOverview = ({ className, token }) => {
} }
label={t('bridge')} label={t('bridge')}
onClick={() => { onClick={() => {
const portfolioUrl = process.env.PORTFOLIO_URL; const portfolioUrl = getPortfolioUrl(
'bridge',
const bridgeUrl = `${portfolioUrl}/bridge`; 'ext_bridge_button',
metaMetricsId,
);
global.platform.openTab({ global.platform.openTab({
url: `${bridgeUrl}?metamaskEntry=ext_bridge_button&token=${token.address}`, url: `${portfolioUrl}&token=${token.address}`,
}); });
trackEvent({ trackEvent({
category: MetaMetricsEventCategory.Navigation, category: MetaMetricsEventCategory.Navigation,
@ -237,6 +248,9 @@ const TokenOverview = ({ className, token }) => {
properties: { properties: {
location: 'Token Overview', location: 'Token Overview',
text: 'Bridge', text: 'Bridge',
url: portfolioUrl,
chain_id: chainId,
token_symbol: token.symbol,
}, },
}); });
}} }}

View File

@ -307,7 +307,7 @@ describe('TokenOverview', () => {
await waitFor(() => await waitFor(() =>
expect(openTabSpy).toHaveBeenCalledWith({ expect(openTabSpy).toHaveBeenCalledWith({
url: expect.stringContaining( url: expect.stringContaining(
'/bridge?metamaskEntry=ext_bridge_button&token=0x7ceb23fd6bc0add59e62ac25578270cff1B9f619', '/bridge?metamaskEntry=ext_bridge_button&metametricsId=&token=0x7ceb23fd6bc0add59e62ac25578270cff1B9f619',
), ),
}), }),
); );

View File

@ -20,6 +20,11 @@ import {
} from '../../../helpers/constants/routes'; } from '../../../helpers/constants/routes';
import { TextVariant } from '../../../helpers/constants/design-system'; import { TextVariant } from '../../../helpers/constants/design-system';
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
function getActionFunctionById(id, history) { function getActionFunctionById(id, history) {
const actionFunctions = { const actionFunctions = {
@ -107,9 +112,16 @@ const renderDescription = (description) => {
); );
}; };
const renderFirstNotification = (notification, idRefMap, history, isLast) => { const renderFirstNotification = (
notification,
idRefMap,
history,
isLast,
trackEvent,
) => {
const { id, date, title, description, image, actionText } = notification; const { id, date, title, description, image, actionText } = notification;
const actionFunction = getActionFunctionById(id, history); const actionFunction = getActionFunctionById(id, history);
const imageComponent = image && ( const imageComponent = image && (
<img <img
className="whats-new-popup__notification-image" className="whats-new-popup__notification-image"
@ -144,7 +156,13 @@ const renderFirstNotification = (notification, idRefMap, history, isLast) => {
<Button <Button
type="primary" type="primary"
className="whats-new-popup__button" className="whats-new-popup__button"
onClick={actionFunction} onClick={() => {
actionFunction();
trackEvent({
category: MetaMetricsEventCategory.Home,
event: MetaMetricsEventName.WhatsNewClicked,
});
}}
> >
{actionText} {actionText}
</Button> </Button>
@ -218,6 +236,8 @@ export default function WhatsNewPopup({ onClose }) {
[memoizedNotifications], [memoizedNotifications],
); );
const trackEvent = useContext(MetaMetricsContext);
const handleScrollDownClick = (e) => { const handleScrollDownClick = (e) => {
e.stopPropagation(); e.stopPropagation();
idRefMap[notifications[notifications.length - 1].id].current.scrollIntoView( idRefMap[notifications[notifications.length - 1].id].current.scrollIntoView(
@ -267,6 +287,14 @@ export default function WhatsNewPopup({ onClose }) {
className="whats-new-popup__popover" className="whats-new-popup__popover"
onClose={() => { onClose={() => {
updateViewedNotifications(seenNotifications); updateViewedNotifications(seenNotifications);
trackEvent({
category: MetaMetricsEventCategory.Home,
event: MetaMetricsEventName.WhatsNewViewed,
properties: {
number_viewed: Object.keys(seenNotifications).pop(),
completed_all: true,
},
});
onClose(); onClose();
}} }}
popoverRef={popoverRef} popoverRef={popoverRef}
@ -280,7 +308,13 @@ export default function WhatsNewPopup({ onClose }) {
// Display the swaps notification with full image // Display the swaps notification with full image
// Displays the NFTs & OpenSea notifications 18,19 with full image // Displays the NFTs & OpenSea notifications 18,19 with full image
return index === 0 || id === 1 || id === 18 || id === 19 return index === 0 || id === 1 || id === 18 || id === 19
? renderFirstNotification(notification, idRefMap, history, isLast) ? renderFirstNotification(
notification,
idRefMap,
history,
isLast,
trackEvent,
)
: renderSubsequentNotification( : renderSubsequentNotification(
notification, notification,
idRefMap, idRefMap,

View File

@ -9,7 +9,10 @@ import {
getRpcPrefsForCurrentProvider, getRpcPrefsForCurrentProvider,
getBlockExplorerLinkText, getBlockExplorerLinkText,
getCurrentChainId, getCurrentChainId,
getHardwareWalletType,
getAccountTypeForKeyring,
} from '../../../selectors'; } from '../../../selectors';
import { findKeyringForAddress } from '../../../ducks/metamask/metamask';
import { NETWORKS_ROUTE } from '../../../helpers/constants/routes'; import { NETWORKS_ROUTE } from '../../../helpers/constants/routes';
import { Menu, MenuItem } from '../../ui/menu'; import { Menu, MenuItem } from '../../ui/menu';
import { Text, IconName } from '../../component-library'; import { Text, IconName } from '../../component-library';
@ -21,6 +24,7 @@ import {
import { getURLHostName } from '../../../helpers/utils/util'; import { getURLHostName } from '../../../helpers/utils/util';
import { showModal } from '../../../store/actions'; import { showModal } from '../../../store/actions';
import { TextVariant } from '../../../helpers/constants/design-system'; import { TextVariant } from '../../../helpers/constants/design-system';
import { formatAccountType } from '../../../helpers/utils/metrics';
export const AccountListItemMenu = ({ export const AccountListItemMenu = ({
anchorElement, anchorElement,
@ -39,6 +43,13 @@ export const AccountListItemMenu = ({
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider); const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
const addressLink = getAccountLink(identity.address, chainId, rpcPrefs); const addressLink = getAccountLink(identity.address, chainId, rpcPrefs);
const deviceName = useSelector(getHardwareWalletType);
const keyring = useSelector((state) =>
findKeyringForAddress(state, identity.address),
);
const accountType = formatAccountType(getAccountTypeForKeyring(keyring));
const blockExplorerLinkText = useSelector(getBlockExplorerLinkText); const blockExplorerLinkText = useSelector(getBlockExplorerLinkText);
const openBlockExplorer = () => { const openBlockExplorer = () => {
trackEvent({ trackEvent({
@ -50,6 +61,7 @@ export const AccountListItemMenu = ({
url_domain: getURLHostName(addressLink), url_domain: getURLHostName(addressLink),
}, },
}); });
global.platform.openTab({ global.platform.openTab({
url: addressLink, url: addressLink,
}); });
@ -67,11 +79,20 @@ export const AccountListItemMenu = ({
onHide={onClose} onHide={onClose}
> >
<MenuItem <MenuItem
onClick={ onClick={() => {
blockExplorerLinkText.firstPart === 'addBlockExplorer' blockExplorerLinkText.firstPart === 'addBlockExplorer'
? routeToAddBlockExplorerUrl ? routeToAddBlockExplorerUrl()
: openBlockExplorer : openBlockExplorer();
}
trackEvent({
event: MetaMetricsEventName.BlockExplorerLinkClicked,
category: MetaMetricsEventCategory.Accounts,
properties: {
location: 'Account Options',
chain_id: chainId,
},
});
}}
subtitle={blockExplorerUrlSubTitle || null} subtitle={blockExplorerUrlSubTitle || null}
iconName={IconName.Export} iconName={IconName.Export}
data-testid="account-list-menu-open-explorer" data-testid="account-list-menu-open-explorer"
@ -105,6 +126,15 @@ export const AccountListItemMenu = ({
identity, identity,
}), }),
); );
trackEvent({
event: MetaMetricsEventName.AccountRemoved,
category: MetaMetricsEventCategory.Accounts,
properties: {
account_hardware_type: deviceName,
chain_id: chainId,
account_type: accountType,
},
});
onClose(); onClose();
}} }}
iconName={IconName.Trash} iconName={IconName.Trash}

View File

@ -1,4 +1,4 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef, useContext } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
@ -38,6 +38,11 @@ import UserPreferencedCurrencyDisplay from '../../app/user-preferenced-currency-
import { SECONDARY, PRIMARY } from '../../../helpers/constants/common'; import { SECONDARY, PRIMARY } from '../../../helpers/constants/common';
import { findKeyringForAddress } from '../../../ducks/metamask/metamask'; import { findKeyringForAddress } from '../../../ducks/metamask/metamask';
import Tooltip from '../../ui/tooltip/tooltip'; import Tooltip from '../../ui/tooltip/tooltip';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
import { MetaMetricsContext } from '../../../contexts/metametrics';
const MAXIMUM_CURRENCY_DECIMALS = 3; const MAXIMUM_CURRENCY_DECIMALS = 3;
const MAXIMUM_CHARACTERS_WITHOUT_TOOLTIP = 17; const MAXIMUM_CHARACTERS_WITHOUT_TOOLTIP = 17;
@ -82,6 +87,8 @@ export const AccountListItem = ({
const { blockExplorerUrl } = rpcPrefs; const { blockExplorerUrl } = rpcPrefs;
const blockExplorerUrlSubTitle = getURLHostName(blockExplorerUrl); const blockExplorerUrlSubTitle = getURLHostName(blockExplorerUrl);
const trackEvent = useContext(MetaMetricsContext);
return ( return (
<Box <Box
display={DISPLAY.FLEX} display={DISPLAY.FLEX}
@ -207,6 +214,13 @@ export const AccountListItem = ({
size={IconSize.Sm} size={IconSize.Sm}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
trackEvent({
event: MetaMetricsEventName.AccountDetailMenuOpened,
category: MetaMetricsEventCategory.Navigation,
properties: {
location: 'Account Options',
},
});
setAccountOptionsMenuOpen(true); setAccountOptionsMenuOpen(true);
}} }}
data-testid="account-list-item-menu-button" data-testid="account-list-item-menu-button"

View File

@ -1,4 +1,4 @@
import React, { useContext, useState, useRef } from 'react'; import React, { useContext, useState, useRef, useCallback } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import browser from 'webextension-polyfill'; import browser from 'webextension-polyfill';
@ -30,6 +30,7 @@ import {
} from '../../component-library'; } from '../../component-library';
import { import {
getCurrentChainId,
getCurrentNetwork, getCurrentNetwork,
getOnboardedInThisUISession, getOnboardedInThisUISession,
getOriginOfCurrentTab, getOriginOfCurrentTab,
@ -60,6 +61,7 @@ export const AppHeader = ({ onClick }) => {
const history = useHistory(); const history = useHistory();
const isUnlocked = useSelector((state) => state.metamask.isUnlocked); const isUnlocked = useSelector((state) => state.metamask.isUnlocked);
const t = useI18nContext(); const t = useI18nContext();
const chainId = useSelector(getCurrentChainId);
// Used for account picker // Used for account picker
const identity = useSelector(getSelectedIdentity); const identity = useSelector(getSelectedIdentity);
@ -71,7 +73,7 @@ export const AppHeader = ({ onClick }) => {
// Used for network icon / dropdown // Used for network icon / dropdown
const currentNetwork = useSelector(getCurrentNetwork); const currentNetwork = useSelector(getCurrentNetwork);
// used to get the environment and connection status // Used to get the environment and connection status
const popupStatus = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP; const popupStatus = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP;
const showStatus = const showStatus =
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP && getEnvironmentType() === ENVIRONMENT_TYPE_POPUP &&
@ -83,6 +85,19 @@ export const AppHeader = ({ onClick }) => {
.querySelector('[dir]') .querySelector('[dir]')
?.getAttribute('dir'); ?.getAttribute('dir');
// Callback for network dropdown
const networkOpenCallback = useCallback(() => {
dispatch(toggleNetworkMenu());
trackEvent({
event: MetaMetricsEventName.NavNetworkMenuOpened,
category: MetaMetricsEventCategory.Navigation,
properties: {
location: 'App header',
chain_id: chainId,
},
});
}, [chainId, dispatch, trackEvent]);
return ( return (
<> <>
{isUnlocked && !popupStatus ? ( {isUnlocked && !popupStatus ? (
@ -140,14 +155,14 @@ export const AppHeader = ({ onClick }) => {
name={currentNetwork?.nickname} name={currentNetwork?.nickname}
src={currentNetwork?.rpcPrefs?.imageUrl} src={currentNetwork?.rpcPrefs?.imageUrl}
size={Size.SM} size={Size.SM}
onClick={() => dispatch(toggleNetworkMenu())} onClick={networkOpenCallback}
display={[DISPLAY.FLEX, DISPLAY.NONE]} // show on popover hide on desktop display={[DISPLAY.FLEX, DISPLAY.NONE]} // show on popover hide on desktop
/> />
<PickerNetwork <PickerNetwork
margin={2} margin={2}
label={currentNetwork?.nickname} label={currentNetwork?.nickname}
src={currentNetwork?.rpcPrefs?.imageUrl} src={currentNetwork?.rpcPrefs?.imageUrl}
onClick={() => dispatch(toggleNetworkMenu())} onClick={networkOpenCallback}
display={[DISPLAY.NONE, DISPLAY.FLEX]} // show on desktop hide on popover display={[DISPLAY.NONE, DISPLAY.FLEX]} // show on desktop hide on popover
/> />
{showProductTour && {showProductTour &&
@ -171,7 +186,17 @@ export const AppHeader = ({ onClick }) => {
<AccountPicker <AccountPicker
address={identity.address} address={identity.address}
name={identity.name} name={identity.name}
onClick={() => dispatch(toggleAccountMenu())} onClick={() => {
dispatch(toggleAccountMenu());
trackEvent({
event: MetaMetricsEventName.NavAccountMenuOpened,
category: MetaMetricsEventCategory.Navigation,
properties: {
location: 'Home',
},
});
}}
/> />
<Box <Box
display={DISPLAY.FLEX} display={DISPLAY.FLEX}
@ -181,7 +206,13 @@ export const AppHeader = ({ onClick }) => {
{showStatus ? ( {showStatus ? (
<Box ref={menuRef}> <Box ref={menuRef}>
<ConnectedStatusIndicator <ConnectedStatusIndicator
onClick={() => history.push(CONNECTED_ACCOUNTS_ROUTE)} onClick={() => {
history.push(CONNECTED_ACCOUNTS_ROUTE);
trackEvent({
event: MetaMetricsEventName.NavConnectedSitesOpened,
category: MetaMetricsEventCategory.Navigation,
});
}}
/> />
</Box> </Box>
) : null}{' '} ) : null}{' '}

View File

@ -4,7 +4,10 @@ import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { useI18nContext } from '../../../hooks/useI18nContext'; import { useI18nContext } from '../../../hooks/useI18nContext';
import { getDetectedTokensInCurrentNetwork } from '../../../selectors'; import {
getCurrentChainId,
getDetectedTokensInCurrentNetwork,
} from '../../../selectors';
import { MetaMetricsContext } from '../../../contexts/metametrics'; import { MetaMetricsContext } from '../../../contexts/metametrics';
import { import {
MetaMetricsEventCategory, MetaMetricsEventCategory,
@ -26,14 +29,17 @@ export const DetectedTokensBanner = ({
({ address, symbol }) => `${symbol} - ${address}`, ({ address, symbol }) => `${symbol} - ${address}`,
); );
const chainId = useSelector(getCurrentChainId);
const handleOnClick = () => { const handleOnClick = () => {
actionButtonOnClick(); actionButtonOnClick();
trackEvent({ trackEvent({
event: MetaMetricsEventName.TokenImportClicked, event: MetaMetricsEventName.TokenImportClicked,
category: MetaMetricsEventCategory.Wallet, category: MetaMetricsEventCategory.Wallet,
properties: { properties: {
source: MetaMetricsTokenEventSource.Detected, source_connection_method: MetaMetricsTokenEventSource.Detected,
tokens: detectedTokensDetails, tokens: detectedTokensDetails,
chain_id: chainId,
}, },
}); });
}; };

View File

@ -1,7 +1,7 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { import {
CONNECTED_ROUTE, CONNECTED_ROUTE,
SETTINGS_ROUTE, SETTINGS_ROUTE,
@ -21,12 +21,15 @@ import {
MetaMetricsEventCategory, MetaMetricsEventCategory,
MetaMetricsContextProp, MetaMetricsContextProp,
} from '../../../../shared/constants/metametrics'; } from '../../../../shared/constants/metametrics';
import { getPortfolioUrl } from '../../../helpers/utils/portfolio';
import { getMetaMetricsId } from '../../../selectors';
export const GlobalMenu = ({ closeMenu, anchorElement }) => { export const GlobalMenu = ({ closeMenu, anchorElement }) => {
const t = useI18nContext(); const t = useI18nContext();
const dispatch = useDispatch(); const dispatch = useDispatch();
const trackEvent = useContext(MetaMetricsContext); const trackEvent = useContext(MetaMetricsContext);
const history = useHistory(); const history = useHistory();
const metaMetricsId = useSelector(getMetaMetricsId);
return ( return (
<Menu anchorElement={anchorElement} onHide={closeMenu}> <Menu anchorElement={anchorElement} onHide={closeMenu}>
@ -38,7 +41,7 @@ export const GlobalMenu = ({ closeMenu, anchorElement }) => {
event: MetaMetricsEventName.NavConnectedSitesOpened, event: MetaMetricsEventName.NavConnectedSitesOpened,
category: MetaMetricsEventCategory.Navigation, category: MetaMetricsEventCategory.Navigation,
properties: { properties: {
location: 'Account Options', location: 'Global Menu',
}, },
}); });
closeMenu(); closeMenu();
@ -49,9 +52,9 @@ export const GlobalMenu = ({ closeMenu, anchorElement }) => {
<MenuItem <MenuItem
iconName={IconName.Diagram} iconName={IconName.Diagram}
onClick={() => { onClick={() => {
const portfolioUrl = process.env.PORTFOLIO_URL; const portfolioUrl = getPortfolioUrl('', 'ext', metaMetricsId);
global.platform.openTab({ global.platform.openTab({
url: `${portfolioUrl}?metamaskEntry=ext`, url: portfolioUrl,
}); });
trackEvent( trackEvent(
{ {
@ -59,6 +62,7 @@ export const GlobalMenu = ({ closeMenu, anchorElement }) => {
event: MetaMetricsEventName.PortfolioLinkClicked, event: MetaMetricsEventName.PortfolioLinkClicked,
properties: { properties: {
url: portfolioUrl, url: portfolioUrl,
location: 'Global Menu',
}, },
}, },
{ {
@ -82,7 +86,7 @@ export const GlobalMenu = ({ closeMenu, anchorElement }) => {
event: MetaMetricsEventName.AppWindowExpanded, event: MetaMetricsEventName.AppWindowExpanded,
category: MetaMetricsEventCategory.Navigation, category: MetaMetricsEventCategory.Navigation,
properties: { properties: {
location: 'Account Options', location: 'Global Menu',
}, },
}); });
closeMenu(); closeMenu();
@ -102,6 +106,7 @@ export const GlobalMenu = ({ closeMenu, anchorElement }) => {
event: MetaMetricsEventName.SupportLinkClicked, event: MetaMetricsEventName.SupportLinkClicked,
properties: { properties: {
url: SUPPORT_LINK, url: SUPPORT_LINK,
location: 'Global Menu',
}, },
}, },
{ {
@ -124,7 +129,7 @@ export const GlobalMenu = ({ closeMenu, anchorElement }) => {
category: MetaMetricsEventCategory.Navigation, category: MetaMetricsEventCategory.Navigation,
event: MetaMetricsEventName.NavSettingsOpened, event: MetaMetricsEventName.NavSettingsOpened,
properties: { properties: {
location: 'Main Menu', location: 'Global Menu',
}, },
}); });
closeMenu(); closeMenu();
@ -137,6 +142,13 @@ export const GlobalMenu = ({ closeMenu, anchorElement }) => {
onClick={() => { onClick={() => {
dispatch(lockMetamask()); dispatch(lockMetamask());
history.push(DEFAULT_ROUTE); history.push(DEFAULT_ROUTE);
trackEvent({
category: MetaMetricsEventCategory.Navigation,
event: MetaMetricsEventName.AppLocked,
properties: {
location: 'Global Menu',
},
});
closeMenu(); closeMenu();
}} }}
data-testid="global-menu-lock" data-testid="global-menu-lock"

View File

@ -38,7 +38,7 @@ describe('AccountListItem', () => {
fireEvent.click(getByTestId('global-menu-portfolio')); fireEvent.click(getByTestId('global-menu-portfolio'));
await waitFor(() => { await waitFor(() => {
expect(global.platform.openTab).toHaveBeenCalledWith({ expect(global.platform.openTab).toHaveBeenCalledWith({
url: `${process.env.PORTFOLIO_URL}?metamaskEntry=ext`, url: `/?metamaskEntry=ext&metametricsId=`,
}); });
}); });
}); });

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useContext } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import classnames from 'classnames'; import classnames from 'classnames';
@ -21,9 +21,14 @@ import {
Text, Text,
} from '../../component-library'; } from '../../component-library';
import Box from '../../ui/box/box'; import Box from '../../ui/box/box';
import { getNativeCurrencyImage } from '../../../selectors'; import { getCurrentChainId, getNativeCurrencyImage } from '../../../selectors';
import Tooltip from '../../ui/tooltip'; import Tooltip from '../../ui/tooltip';
import { useI18nContext } from '../../../hooks/useI18nContext'; import { useI18nContext } from '../../../hooks/useI18nContext';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
export const MultichainTokenListItem = ({ export const MultichainTokenListItem = ({
className, className,
@ -37,6 +42,9 @@ export const MultichainTokenListItem = ({
const t = useI18nContext(); const t = useI18nContext();
const primaryTokenImage = useSelector(getNativeCurrencyImage); const primaryTokenImage = useSelector(getNativeCurrencyImage);
const dataTheme = document.documentElement.getAttribute('data-theme'); const dataTheme = document.documentElement.getAttribute('data-theme');
const trackEvent = useContext(MetaMetricsContext);
const chainId = useSelector(getCurrentChainId);
return ( return (
<Box <Box
className={classnames('multichain-token-list-item', className)} className={classnames('multichain-token-list-item', className)}
@ -56,6 +64,15 @@ export const MultichainTokenListItem = ({
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
onClick(); onClick();
trackEvent({
category: MetaMetricsEventCategory.Tokens,
event: MetaMetricsEventName.TokenDetailsOpened,
properties: {
location: 'Home',
chain_id: chainId,
token_symbol: tokenSymbol,
},
});
}} }}
> >
<BadgeWrapper <BadgeWrapper

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useContext } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
@ -28,6 +28,11 @@ import { Button, BUTTON_VARIANT, Text } from '../../component-library';
import { ADD_POPULAR_CUSTOM_NETWORK } from '../../../helpers/constants/routes'; import { ADD_POPULAR_CUSTOM_NETWORK } from '../../../helpers/constants/routes';
import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../shared/constants/app'; import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../shared/constants/app';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
const UNREMOVABLE_CHAIN_IDS = [CHAIN_IDS.MAINNET, ...TEST_CHAINS]; const UNREMOVABLE_CHAIN_IDS = [CHAIN_IDS.MAINNET, ...TEST_CHAINS];
@ -38,6 +43,7 @@ export const NetworkListMenu = ({ onClose }) => {
const currentChainId = useSelector(getCurrentChainId); const currentChainId = useSelector(getCurrentChainId);
const dispatch = useDispatch(); const dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
const trackEvent = useContext(MetaMetricsContext);
const environmentType = getEnvironmentType(); const environmentType = getEnvironmentType();
const isFullScreen = environmentType === ENVIRONMENT_TYPE_FULLSCREEN; const isFullScreen = environmentType === ENVIRONMENT_TYPE_FULLSCREEN;
@ -65,6 +71,16 @@ export const NetworkListMenu = ({ onClose }) => {
} else { } else {
dispatch(setActiveNetwork(network.id)); dispatch(setActiveNetwork(network.id));
} }
trackEvent({
event: MetaMetricsEventName.NavNetworkSwitched,
category: MetaMetricsEventCategory.Network,
properties: {
location: 'Network Menu',
chain_id: currentChainId,
from_network: currentChainId,
to_network: network.id || network.chainId,
},
});
}} }}
onDeleteClick={ onDeleteClick={
canDeleteNetwork canDeleteNetwork
@ -92,7 +108,16 @@ export const NetworkListMenu = ({ onClose }) => {
<Text>{t('showTestnetNetworks')}</Text> <Text>{t('showTestnetNetworks')}</Text>
<ToggleButton <ToggleButton
value={showTestNetworks} value={showTestNetworks}
onToggle={(value) => dispatch(setShowTestNetworks(!value))} onToggle={(value) => {
const shouldShowTestNetworks = !value;
dispatch(setShowTestNetworks(shouldShowTestNetworks));
if (shouldShowTestNetworks) {
trackEvent({
event: MetaMetricsEventName.TestNetworksDisplayed,
category: MetaMetricsEventCategory.Network,
});
}
}}
/> />
</Box> </Box>
<Box padding={4}> <Box padding={4}>
@ -106,6 +131,10 @@ export const NetworkListMenu = ({ onClose }) => {
ADD_POPULAR_CUSTOM_NETWORK, ADD_POPULAR_CUSTOM_NETWORK,
); );
dispatch(toggleNetworkMenu()); dispatch(toggleNetworkMenu());
trackEvent({
event: MetaMetricsEventName.AddNetworkButtonClick,
category: MetaMetricsEventCategory.Network,
});
}} }}
> >
{t('addNetwork')} {t('addNetwork')}

View File

@ -8,3 +8,11 @@ export function getMethodName(camelCase) {
.replace(/([A-Z])([a-z])/gu, ' $1$2') .replace(/([A-Z])([a-z])/gu, ' $1$2')
.replace(/ +/gu, ' '); .replace(/ +/gu, ' ');
} }
export function formatAccountType(accountType) {
if (accountType === 'default') {
return 'metamask';
}
return accountType;
}

View File

@ -0,0 +1,8 @@
export function getPortfolioUrl(
endpoint = '',
metamaskEntry = '',
metaMetricsId = '',
) {
const portfolioUrl = process.env.PORTFOLIO_URL || '';
return `${portfolioUrl}/${endpoint}?metamaskEntry=${metamaskEntry}&metametricsId=${metaMetricsId}`;
}

View File

@ -112,7 +112,7 @@ export default function AddNft() {
tokenId: tokenId.toString(), tokenId: tokenId.toString(),
asset_type: AssetType.NFT, asset_type: AssetType.NFT,
token_standard: tokenDetails?.standard, token_standard: tokenDetails?.standard,
source: MetaMetricsTokenEventSource.Custom, source_connection_method: MetaMetricsTokenEventSource.Custom,
}, },
}); });

View File

@ -129,7 +129,7 @@ const ConfirmAddSuggestedToken = () => {
token_contract_address: asset.address, token_contract_address: asset.address,
token_decimal_precision: asset.decimals, token_decimal_precision: asset.decimals,
unlisted: asset.unlisted, unlisted: asset.unlisted,
source: MetaMetricsTokenEventSource.Dapp, source_connection_method: MetaMetricsTokenEventSource.Dapp,
token_standard: TokenStandard.ERC20, token_standard: TokenStandard.ERC20,
asset_type: AssetType.token, asset_type: AssetType.token,
}, },

View File

@ -51,7 +51,7 @@ const ConfirmImportToken = () => {
token_contract_address: pendingToken.address, token_contract_address: pendingToken.address,
token_decimal_precision: pendingToken.decimals, token_decimal_precision: pendingToken.decimals,
unlisted: pendingToken.unlisted, unlisted: pendingToken.unlisted,
source: pendingToken.isCustom source_connection_method: pendingToken.isCustom
? MetaMetricsTokenEventSource.Custom ? MetaMetricsTokenEventSource.Custom
: MetaMetricsTokenEventSource.List, : MetaMetricsTokenEventSource.List,
token_standard: TokenStandard.ERC20, token_standard: TokenStandard.ERC20,

View File

@ -48,10 +48,12 @@ export default function NewAccountImportForm() {
navigateToMostRecentOverviewPage(); navigateToMostRecentOverviewPage();
} else { } else {
dispatch(actions.displayWarning(t('importAccountError'))); dispatch(actions.displayWarning(t('importAccountError')));
trackImportEvent(strategy, false);
} }
}) })
.catch((error) => translateWarning(error.message)); .catch((error) => {
trackImportEvent(strategy, error.message);
translateWarning(error.message);
});
} }
function trackImportEvent(strategy, wasSuccessful) { function trackImportEvent(strategy, wasSuccessful) {

View File

@ -35,6 +35,7 @@ export default class NewAccountCreateForm extends Component {
event: MetaMetricsEventName.AccountAdded, event: MetaMetricsEventName.AccountAdded,
properties: { properties: {
account_type: MetaMetricsEventAccountType.Default, account_type: MetaMetricsEventAccountType.Default,
location: 'Home',
}, },
}); });
history.push(mostRecentOverviewPage); history.push(mostRecentOverviewPage);

View File

@ -674,7 +674,24 @@ export default class Home extends PureComponent {
<Tabs <Tabs
t={this.context.t} t={this.context.t}
defaultActiveTabKey={defaultHomeActiveTabName} defaultActiveTabKey={defaultHomeActiveTabName}
onTabClick={onTabClick} onTabClick={(tabName) => {
onTabClick(tabName);
let event;
switch (tabName) {
case 'nfts':
event = MetaMetricsEventName.NftScreenOpened;
break;
case 'activity':
event = MetaMetricsEventName.ActivityScreenOpened;
break;
default:
event = MetaMetricsEventName.TokenScreenOpened;
}
this.context.trackEvent({
category: MetaMetricsEventCategory.Home,
event,
});
}}
tabsClassName="home__tabs" tabsClassName="home__tabs"
> >
<Tab <Tab

View File

@ -1,4 +1,10 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, {
useCallback,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import validUrl from 'valid-url'; import validUrl from 'valid-url';
@ -23,12 +29,17 @@ import {
} from '../../../../store/actions'; } from '../../../../store/actions';
import fetchWithCache from '../../../../../shared/lib/fetch-with-cache'; import fetchWithCache from '../../../../../shared/lib/fetch-with-cache';
import { usePrevious } from '../../../../hooks/usePrevious'; import { usePrevious } from '../../../../hooks/usePrevious';
import { MetaMetricsNetworkEventSource } from '../../../../../shared/constants/metametrics'; import {
MetaMetricsEventCategory,
MetaMetricsEventName,
MetaMetricsNetworkEventSource,
} from '../../../../../shared/constants/metametrics';
import { import {
infuraProjectId, infuraProjectId,
FEATURED_RPCS, FEATURED_RPCS,
} from '../../../../../shared/constants/network'; } from '../../../../../shared/constants/network';
import { decimalToHex } from '../../../../../shared/modules/conversion.utils'; import { decimalToHex } from '../../../../../shared/modules/conversion.utils';
import { MetaMetricsContext } from '../../../../contexts/metametrics';
/** /**
* Attempts to convert the given chainId to a decimal string, for display * Attempts to convert the given chainId to a decimal string, for display
@ -96,6 +107,8 @@ const NetworksForm = ({
const [isEditing, setIsEditing] = useState(Boolean(addNewNetwork)); const [isEditing, setIsEditing] = useState(Boolean(addNewNetwork));
const [previousNetwork, setPreviousNetwork] = useState(selectedNetwork); const [previousNetwork, setPreviousNetwork] = useState(selectedNetwork);
const trackEvent = useContext(MetaMetricsContext);
const resetForm = useCallback(() => { const resetForm = useCallback(() => {
setNetworkName(selectedNetworkName || ''); setNetworkName(selectedNetworkName || '');
setRpcUrl(selectedNetwork.rpcUrl); setRpcUrl(selectedNetwork.rpcUrl);
@ -543,6 +556,19 @@ const NetworksForm = ({
}), }),
); );
trackEvent({
event: MetaMetricsEventName.CustomNetworkAdded,
category: MetaMetricsEventCategory.Network,
properties: {
block_explorer_url: blockExplorerUrl,
chain_id: prefixedChainId,
network_name: networkName,
source_connection_method:
MetaMetricsNetworkEventSource.CustomNetworkForm,
token_symbol: ticker,
},
});
submitCallback?.(); submitCallback?.();
} }
} catch (error) { } catch (error) {

View File

@ -119,6 +119,11 @@ export function getCurrentChainId(state) {
return chainId; return chainId;
} }
export function getMetaMetricsId(state) {
const { metaMetricsId } = state.metamask;
return metaMetricsId;
}
export function isCurrentProviderCustom(state) { export function isCurrentProviderCustom(state) {
const provider = getProvider(state); const provider = getProvider(state);
return ( return (
@ -233,7 +238,15 @@ export function getHardwareWalletType(state) {
export function getAccountType(state) { export function getAccountType(state) {
const currentKeyring = getCurrentKeyring(state); const currentKeyring = getCurrentKeyring(state);
const type = currentKeyring && currentKeyring.type; return getAccountTypeForKeyring(currentKeyring);
}
export function getAccountTypeForKeyring(keyring) {
if (!keyring) {
return '';
}
const { type } = keyring;
///: BEGIN:ONLY_INCLUDE_IN(build-mmi) ///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
if (type.startsWith('Custody')) { if (type.startsWith('Custody')) {
@ -245,6 +258,7 @@ export function getAccountType(state) {
case KeyringType.trezor: case KeyringType.trezor:
case KeyringType.ledger: case KeyringType.ledger:
case KeyringType.lattice: case KeyringType.lattice:
case KeyringType.qr:
return 'hardware'; return 'hardware';
case KeyringType.imported: case KeyringType.imported:
return 'imported'; return 'imported';