Skip to content

Commit 6db80f1

Browse files
authored
UX: Add event tracking for empty banner displays (#22654)
## **Description** Adds event tracking for the new "Buy", "Receive", and "NFT" banners. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/1949 ## **Manual testing steps** 1. Create a new wallet (will have 0 balance, 0 NFTs) 2. Open `ui/contexts/metametrics.js` and in the `trackEvent` function, add a `console.log` to see the events get recorded (or use Logpoints in the debugger) ## **Screenshots/Recordings** ### **Before** N/A ### **After** N/A ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've clearly explained what problem this PR is solving and how it is solved. - [ ] I've linked related issues - [ ] I've included manual testing steps - [ ] I've included screenshots/recordings if applicable - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I’ve properly set the pull request status: - [ ] In case it's not yet "ready for review", I've set it to "draft". - [ ] In case it's "ready for review", I've changed it from "draft" to "non-draft". ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
1 parent 39a8283 commit 6db80f1

File tree

7 files changed

+104
-20
lines changed

7 files changed

+104
-20
lines changed

shared/constants/metametrics.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,12 @@ export enum MetaMetricsEventName {
514514
DecryptionApproved = 'Decryption Approved',
515515
DecryptionRejected = 'Decryption Rejected',
516516
DecryptionRequested = 'Decryption Requested',
517+
EmptyBuyBannerDisplayed = 'Empty Buy Banner Displayed',
518+
EmptyBuyBannerClicked = 'Empty Buy Banner Clicked',
519+
EmptyReceiveBannerDisplayed = 'Empty Receive Banner Displayed',
520+
EmptyReceiveBannerClicked = 'Empty Receive Banner Clicked',
521+
EmptyNftsBannerDisplayed = 'Empty NFTs Banner Displayed',
522+
EmptyNftsBannerClicked = 'Empty NFTs Banner Clicked',
517523
EncryptionPublicKeyApproved = 'Encryption Approved',
518524
EncryptionPublicKeyRejected = 'Encryption Rejected',
519525
EncryptionPublicKeyRequested = 'Encryption Requested',

ui/components/app/asset-list/asset-list.buy-receive.test.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import { renderWithProvider } from '../../../../test/jest';
33
import configureStore from '../../../store/store';
44
import mockState from '../../../../test/data/mock-state.json';
5-
import { CHAIN_IDS } from '../../../../shared/constants/network';
5+
import { CHAIN_IDS, NETWORK_TYPES } from '../../../../shared/constants/network';
66
import { useIsOriginalNativeTokenSymbol } from '../../../hooks/useIsOriginalNativeTokenSymbol';
77
import AssetList from './asset-list';
88

@@ -24,7 +24,7 @@ const render = (
2424
...mockState,
2525
metamask: {
2626
...mockState.metamask,
27-
providerConfig: { chainId, ticker: 'ETH' },
27+
providerConfig: { chainId, ticker: 'ETH', type: NETWORK_TYPES.MAINNET },
2828
accountsByChainId: {
2929
[CHAIN_IDS.MAINNET]: {
3030
[selectedAddress]: { balance },
@@ -44,7 +44,6 @@ describe('AssetList Buy/Receive', () => {
4444
useIsOriginalNativeTokenSymbol.mockReturnValue(true);
4545

4646
it('shows Buy and Receive when the account is empty', () => {
47-
process.env.MULTICHAIN = 1;
4847
const { queryByText } = render(
4948
'0xc42edfcc21ed14dda456aa0756c153f7985d8813',
5049
'0x0',
@@ -54,7 +53,6 @@ describe('AssetList Buy/Receive', () => {
5453
});
5554

5655
it('shows only Receive when chainId is not buyable', () => {
57-
process.env.MULTICHAIN = 1;
5856
const { queryByText } = render(
5957
'0xc42edfcc21ed14dda456aa0756c153f7985d8813',
6058
'0x0',
@@ -65,7 +63,6 @@ describe('AssetList Buy/Receive', () => {
6563
});
6664

6765
it('shows neither when the account has a balance', () => {
68-
process.env.MULTICHAIN = 1;
6966
const { queryByText } = render();
7067
expect(queryByText('Buy')).not.toBeInTheDocument();
7168
expect(queryByText('Receive')).not.toBeInTheDocument();

ui/components/app/asset-list/asset-list.js

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useContext, useState } from 'react';
1+
import React, { useContext, useEffect, useState } from 'react';
22
import PropTypes from 'prop-types';
33
import { useSelector } from 'react-redux';
44
import TokenList from '../token-list';
@@ -12,7 +12,7 @@ import {
1212
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
1313
getShouldHideZeroBalanceTokens,
1414
getIsBuyableChain,
15-
getCurrentChainId,
15+
getCurrentNetwork,
1616
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
1717
getSwapsDefaultToken,
1818
///: END:ONLY_INCLUDE_IF
@@ -55,17 +55,20 @@ import {
5555
showSecondaryCurrency,
5656
} from '../../../../shared/modules/currency-display.utils';
5757
import { roundToDecimalPlacesRemovingExtraZeroes } from '../../../helpers/utils/util';
58+
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
59+
import { getCurrentLocale } from '../../../ducks/locale/locale';
5860

5961
const AssetList = ({ onClickAsset }) => {
6062
const [showDetectedTokens, setShowDetectedTokens] = useState(false);
6163
const selectedAccountBalance = useSelector(getSelectedAccountCachedBalance);
6264
const nativeCurrency = useSelector(getNativeCurrency);
6365
const showFiat = useSelector(getShouldShowFiat);
64-
const chainId = useSelector(getCurrentChainId);
66+
const currentNetwork = useSelector(getCurrentNetwork);
67+
const currentLocale = useSelector(getCurrentLocale);
6568
const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences);
6669
const { ticker, type } = useSelector(getProviderConfig);
6770
const isOriginalNativeSymbol = useIsOriginalNativeTokenSymbol(
68-
chainId,
71+
currentNetwork.chainId,
6972
ticker,
7073
type,
7174
);
@@ -123,6 +126,39 @@ const AssetList = ({ onClickAsset }) => {
123126
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
124127
///: END:ONLY_INCLUDE_IF
125128

129+
useEffect(() => {
130+
if (shouldShowBuy) {
131+
trackEvent({
132+
event: MetaMetricsEventName.EmptyBuyBannerDisplayed,
133+
category: MetaMetricsEventCategory.Navigation,
134+
properties: {
135+
chain_id: currentNetwork.chainId,
136+
locale: currentLocale,
137+
network: currentNetwork.nickname,
138+
referrer: ORIGIN_METAMASK,
139+
},
140+
});
141+
}
142+
if (shouldShowReceive) {
143+
trackEvent({
144+
event: MetaMetricsEventName.EmptyReceiveBannerDisplayed,
145+
category: MetaMetricsEventCategory.Navigation,
146+
properties: {
147+
chain_id: currentNetwork.chainId,
148+
locale: currentLocale,
149+
network: currentNetwork.nickname,
150+
referrer: ORIGIN_METAMASK,
151+
},
152+
});
153+
}
154+
}, [
155+
shouldShowBuy,
156+
shouldShowReceive,
157+
trackEvent,
158+
currentNetwork,
159+
currentLocale,
160+
]);
161+
126162
return (
127163
<>
128164
{detectedTokens.length > 0 &&
@@ -152,7 +188,7 @@ const AssetList = ({ onClickAsset }) => {
152188
properties: {
153189
location: 'Home',
154190
text: 'Buy',
155-
chain_id: chainId,
191+
chain_id: currentNetwork.chainId,
156192
token_symbol: defaultSwapsToken,
157193
},
158194
});

ui/components/app/asset-list/asset-list.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { screen, act, waitFor } from '@testing-library/react';
33
import { renderWithProvider } from '../../../../test/jest';
44
import configureStore from '../../../store/store';
55
import mockState from '../../../../test/data/mock-state.json';
6-
import { CHAIN_IDS } from '../../../../shared/constants/network';
6+
import { CHAIN_IDS, NETWORK_TYPES } from '../../../../shared/constants/network';
77
import { useIsOriginalNativeTokenSymbol } from '../../../hooks/useIsOriginalNativeTokenSymbol';
88
import { getTokenSymbol } from '../../../store/actions';
99
import AssetList from './asset-list';
@@ -72,7 +72,7 @@ const render = (
7272
...mockState,
7373
metamask: {
7474
...mockState.metamask,
75-
providerConfig: { chainId, ticker: 'ETH' },
75+
providerConfig: { chainId, ticker: 'ETH', type: NETWORK_TYPES.MAINNET },
7676
currencyRates: {
7777
ETH: {
7878
conversionRate: CONVERSION_RATE,

ui/components/app/nfts-tab/nfts-tab.js

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useContext, useEffect } from 'react';
22
import { useDispatch, useSelector } from 'react-redux';
33
import { useHistory } from 'react-router-dom';
44
import {
@@ -15,7 +15,11 @@ import { SECURITY_ROUTE } from '../../../helpers/constants/routes';
1515
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
1616
import { useI18nContext } from '../../../hooks/useI18nContext';
1717
import { useNftsCollections } from '../../../hooks/useNftsCollections';
18-
import { getIsMainnet, getUseNftDetection } from '../../../selectors';
18+
import {
19+
getCurrentNetwork,
20+
getIsMainnet,
21+
getUseNftDetection,
22+
} from '../../../selectors';
1923
import {
2024
checkAndUpdateAllNftsOwnershipStatus,
2125
detectNfts,
@@ -26,13 +30,21 @@ import NFTsDetectionNoticeNFTsTab from '../nfts-detection-notice-nfts-tab/nfts-d
2630
import NftsItems from '../nfts-items';
2731
import { AssetListConversionButton } from '../../multichain';
2832
import { ASSET_LIST_CONVERSION_BUTTON_VARIANT_TYPES } from '../../multichain/asset-list-conversion-button/asset-list-conversion-button';
33+
import { MetaMetricsContext } from '../../../contexts/metametrics';
34+
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
35+
import {
36+
MetaMetricsEventCategory,
37+
MetaMetricsEventName,
38+
} from '../../../../shared/constants/metametrics';
39+
import { getCurrentLocale } from '../../../ducks/locale/locale';
2940

3041
export default function NftsTab() {
3142
const useNftDetection = useSelector(getUseNftDetection);
3243
const isMainnet = useSelector(getIsMainnet);
3344
const history = useHistory();
3445
const t = useI18nContext();
3546
const dispatch = useDispatch();
47+
const trackEvent = useContext(MetaMetricsContext);
3648

3749
const { nftsLoading, collections, previouslyOwnedCollection } =
3850
useNftsCollections();
@@ -50,6 +62,23 @@ export default function NftsTab() {
5062

5163
const hasAnyNfts = Object.keys(collections).length > 0;
5264
const showNftBanner = hasAnyNfts === false;
65+
const currentNetwork = useSelector(getCurrentNetwork);
66+
const currentLocale = useSelector(getCurrentLocale);
67+
useEffect(() => {
68+
if (!showNftBanner) {
69+
return;
70+
}
71+
trackEvent({
72+
event: MetaMetricsEventName.EmptyNftsBannerDisplayed,
73+
category: MetaMetricsEventCategory.Navigation,
74+
properties: {
75+
chain_id: currentNetwork.chainId,
76+
locale: currentLocale,
77+
network: currentNetwork.nickname,
78+
referrer: ORIGIN_METAMASK,
79+
},
80+
});
81+
}, [showNftBanner, trackEvent, currentNetwork, currentLocale]);
5382

5483
if (nftsLoading) {
5584
return <div className="nfts-tab__loading">{t('loadingNFTs')}</div>;
@@ -78,9 +107,18 @@ export default function NftsTab() {
78107
>
79108
<AssetListConversionButton
80109
variant={ASSET_LIST_CONVERSION_BUTTON_VARIANT_TYPES.NFT}
81-
onClick={() =>
82-
global.platform.openTab({ url: ZENDESK_URLS.NFT_TOKENS })
83-
}
110+
onClick={() => {
111+
global.platform.openTab({ url: ZENDESK_URLS.NFT_TOKENS });
112+
trackEvent({
113+
event: MetaMetricsEventName.EmptyNftsBannerClicked,
114+
properties: {
115+
chain_id: currentNetwork.chainId,
116+
locale: currentLocale,
117+
network: currentNetwork.nickname,
118+
referrer: ORIGIN_METAMASK,
119+
},
120+
});
121+
}}
84122
/>
85123
</Box>
86124
) : null}

ui/components/app/nfts-tab/nfts-tab.test.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import configureStore from '../../../store/store';
66
import { renderWithProvider } from '../../../../test/jest';
77
import { SECURITY_ROUTE } from '../../../helpers/constants/routes';
88
import { setBackgroundConnection } from '../../../store/background-connection';
9+
import { NETWORK_TYPES } from '../../../../shared/constants/network';
910
import NftsTab from '.';
1011

1112
const NFTS = [
@@ -164,7 +165,7 @@ const render = ({
164165
[chainId]: nftContracts,
165166
},
166167
},
167-
providerConfig: { chainId },
168+
providerConfig: { chainId, type: NETWORK_TYPES.MAINNET },
168169
selectedAddress,
169170
internalAccounts: {
170171
accounts: {
@@ -317,7 +318,6 @@ describe('NFT Items', () => {
317318

318319
describe('nft conversion banner', () => {
319320
it('shows the NFT conversion banner when there are no NFTs', () => {
320-
process.env.MULTICHAIN = 1;
321321
const { queryByText } = render({
322322
selectedAddress: ACCOUNT_1,
323323
nfts: [],
@@ -327,7 +327,6 @@ describe('NFT Items', () => {
327327
});
328328

329329
it('does not show the NFT conversion banner when there are NFTs', () => {
330-
process.env.MULTICHAIN = 1;
331330
const { queryByText } = render({
332331
selectedAddress: ACCOUNT_1,
333332
nfts: NFTS,

ui/pages/routes/routes.component.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,19 @@ describe('Routes Component', () => {
138138
pendingApprovals: {},
139139
approvalFlows: [],
140140
announcements: {},
141+
providerConfig: {
142+
chainId: CHAIN_IDS.MAINNET,
143+
ticker: 'ETH',
144+
type: NETWORK_TYPES.MAINNET,
145+
},
141146
},
142147
send: {
143148
...mockSendState.send,
144149
stage: SEND_STAGES.INACTIVE,
145150
},
151+
localeMessages: {
152+
currentLocale: 'en',
153+
},
146154
});
147155
const { getByTestId } = renderWithProvider(<Routes />, store);
148156
expect(getByTestId('account-menu-icon')).not.toBeDisabled();

0 commit comments

Comments
 (0)