Skip to content

Commit

Permalink
Track token lists (#6303)
Browse files Browse the repository at this point in the history
  • Loading branch information
greg-schrammel authored Dec 20, 2024
1 parent f1d3e7c commit 05364a9
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 34 deletions.
49 changes: 32 additions & 17 deletions src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import { useFavorites } from '@/resources/favorites';
import { useSwapsStore } from '@/state/swaps/swapsStore';
import { isAddress } from '@ethersproject/address';
import { rankings } from 'match-sorter';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { runOnJS, useAnimatedReaction } from 'react-native-reanimated';
import { useDebouncedCallback } from 'use-debounce';
import { TokenToBuyListItem } from '../components/TokenList/TokenToBuyList';
import { useSwapContext } from '../providers/swap-provider';
import { RecentSwap } from '@/__swaps__/types/swap';
import { useTokenDiscovery } from '../resources/search';
import { analyticsV2 } from '@/analytics';

export type AssetToBuySectionId = 'bridge' | 'recent' | 'favorites' | 'verified' | 'unverified' | 'other_networks' | 'popular';

Expand Down Expand Up @@ -422,28 +423,29 @@ export function useSearchCurrencyLists() {
}
);

return useMemo(() => {
const searchCurrencyLists = useMemo(() => {
const toChainId = selectedOutputChainId.value ?? ChainId.mainnet;
const bridgeResult = memoizedData.filteredBridgeAsset ?? undefined;
const crosschainMatches = query === '' ? undefined : verifiedAssets?.filter(asset => asset.chainId !== toChainId);
const verifiedResults = query === '' ? verifiedAssets : verifiedAssets?.filter(asset => asset.chainId === toChainId);
const unverifiedResults = memoizedData.enableUnverifiedSearch ? unverifiedAssets : undefined;

return {
results: buildListSectionsData({
combinedData: {
bridgeAsset: bridgeResult,
crosschainExactMatches: crosschainMatches,
unverifiedAssets: unverifiedResults,
verifiedAssets: verifiedResults,
recentSwaps: recentsForChain,
popularAssets: popularAssetsForChain,
},
favoritesList,
filteredBridgeAssetAddress: memoizedData.filteredBridgeAsset?.address,
}),
isLoading: isLoadingVerifiedAssets || isLoadingUnverifiedAssets || isLoadingPopularAssets,
};
const results = buildListSectionsData({
combinedData: {
bridgeAsset: bridgeResult,
crosschainExactMatches: crosschainMatches,
unverifiedAssets: unverifiedResults,
verifiedAssets: verifiedResults,
recentSwaps: recentsForChain,
popularAssets: popularAssetsForChain,
},
favoritesList,
filteredBridgeAssetAddress: memoizedData.filteredBridgeAsset?.address,
});

const isLoading = isLoadingVerifiedAssets || isLoadingUnverifiedAssets || isLoadingPopularAssets;

return { results, isLoading };
}, [
favoritesList,
isLoadingUnverifiedAssets,
Expand All @@ -458,4 +460,17 @@ export function useSearchCurrencyLists() {
recentsForChain,
popularAssetsForChain,
]);

useEffect(() => {
if (searchCurrencyLists.isLoading) return;
const params = { screen: 'swap' as const, total_tokens: 0, no_icon: 0, query };
for (const assetOrHeader of searchCurrencyLists.results) {
if (assetOrHeader.listItemType === 'header') continue;
if (!assetOrHeader.icon_url) params.no_icon += 1;
params.total_tokens += 1;
}
analyticsV2.track(analyticsV2.event.tokenList, params);
}, [searchCurrencyLists.results, searchCurrencyLists.isLoading, query]);

return searchCurrencyLists;
}
11 changes: 11 additions & 0 deletions src/analytics/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ export const event = {
tokenDetailsErc20: 'token_details.erc20',
tokenDetailsNFT: 'token_details.nft',

// token lists (wallet, swap, send)
tokenList: 'token_list',

// trending tokens
viewTrendingToken: 'trending_tokens.view_trending_token',
viewRankedCategory: 'trending_tokens.view_ranked_category',
Expand Down Expand Up @@ -717,6 +720,14 @@ export type EventProperties = {
available_data: { description: boolean; image_url: boolean; floorPrice: boolean };
};

[event.tokenList]: {
screen: 'wallet' | 'swap' | 'send' | 'discover';
total_tokens: number;
no_icon: number;
no_price?: number;
query?: string; // query is only sent for the swap screen
};

[event.viewTrendingToken]: {
address: TrendingToken['address'];
chainId: TrendingToken['chainId'];
Expand Down
26 changes: 25 additions & 1 deletion src/components/Discover/DiscoverSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import deviceUtils from '@/utils/deviceUtils';
import CurrencySelectionList from '@/components/CurrencySelectionList';
import { Row } from '@/components/layout';
import { useDiscoverScreenContext } from '@/components/Discover/DiscoverScreenContext';
import { analytics } from '@/analytics';
import { analytics, analyticsV2 } from '@/analytics';
import { PROFILES, useExperimentalFlag } from '@/config';
import { useAccountSettings, useSearchCurrencyList, usePrevious, useHardwareBackOnFocus } from '@/hooks';
import { useNavigation } from '@/navigation';
Expand All @@ -25,6 +25,7 @@ import { useTheme } from '@/theme';
import { EnrichedExchangeAsset } from '@/components/ExchangeAssetList';
import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
import { ChainId, Network } from '@/state/backendNetworks/types';
import { useTimeoutEffect } from '@/hooks/useTimeout';

export const SearchContainer = styled(Row)({
height: '100%',
Expand Down Expand Up @@ -270,6 +271,29 @@ export default function DiscoverSearch() {
});
}, [sectionListRef, isSearching]);

useTimeoutEffect(
() => {
const assets = currencyList
.filter(a => a.key !== 'profiles')
.map(asset => asset.data)
.flat();
if (assets.length === 0) return;
const params = {
screen: 'discover' as const,
no_icon: 0,
no_price: 0,
total_tokens: assets.length,
query: searchQueryForSearch,
};
for (const asset of assets) {
if (!asset.icon_url) params.no_icon += 1;
if (!isNaN(asset.price?.value)) params.no_price += 1;
}
analyticsV2.track(analyticsV2.event.tokenList, params);
},
{ timeout: 3000, enabled: !isLoading }
);

return (
<View key={currencyListDataKey} style={{ height: deviceUtils.dimensions.height - TOP_OFFSET - marginBottom }}>
<SearchContainer>
Expand Down
7 changes: 3 additions & 4 deletions src/components/expanded-state/UniqueTokenExpandedState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -417,18 +417,17 @@ const UniqueTokenExpandedState = ({ asset: passedAsset, external }: UniqueTokenE

const hideNftMarketplaceAction = isPoap || !slug;

const mountedAt = useRef(Date.now());
useTimeoutEffect(
() => {
({ elapsedTime }) => {
const { address, chainId } = getAddressAndChainIdFromUniqueId(uniqueId);
const { name, description, image_url } = asset;
analyticsV2.track(analyticsV2.event.tokenDetailsNFT, {
eventSentAfterMs: Date.now() - mountedAt.current,
eventSentAfterMs: elapsedTime,
token: { isPoap, isParty: !!isParty, isENS, address, chainId, name, image_url },
available_data: { description: !!description, image_url: !!image_url, floorPrice: !!offer?.floorPrice },
});
},
5 * 1000 // 5s
{ timeout: 5 * 1000 }
);
return (
<>
Expand Down
7 changes: 3 additions & 4 deletions src/components/expanded-state/asset/ChartExpandedState.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,17 +257,16 @@ export default function ChartExpandedState({ asset }) {
[nativeCurrency]
);

const mountedAt = useRef(Date.now());
useTimeoutEffect(
() => {
({ elapsedTime }) => {
const { address, chainId, symbol, name, icon_url, price } = assetWithPrice;
analyticsV2.track(analyticsV2.event.tokenDetailsErc20, {
eventSentAfterMs: Date.now() - mountedAt.current,
eventSentAfterMs: elapsedTime,
token: { address, chainId, symbol, name, icon_url, price },
available_data: { chart: showChart, description: !!data?.description, iconUrl: !!icon_url },
});
},
5 * 1000 // 5s
{ timeout: 5 * 1000 }
);

return (
Expand Down
24 changes: 18 additions & 6 deletions src/hooks/useTimeout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,32 @@ export default function useTimeout(): [(func: () => void, ms?: number) => void,
return [start, stop, handle];
}

export function useTimeoutEffect(onTimeout: (cancelled: boolean) => void, delay: number) {
export function useTimeoutEffect(
onTimeout: (e: { cancelled: boolean; elapsedTime: number }) => void,
{ timeout, enabled = true }: { timeout: number; enabled?: boolean }
) {
const callback = useRef(onTimeout);
useLayoutEffect(() => {
callback.current = onTimeout;
}, [onTimeout]);

const timeoutRef = useRef<NodeJS.Timeout>();
useEffect(() => {
if (!enabled) return;
const startedAt = Date.now();
timeoutRef.current = setTimeout(() => callback.current(false), delay);
const timeout = timeoutRef.current;
timeoutRef.current = setTimeout(() => {
callback.current({
cancelled: false,
elapsedTime: Date.now() - startedAt,
});
}, timeout);
return () => {
clearTimeout(timeout);
if (Date.now() - startedAt < delay) callback.current(true);
if (!timeoutRef.current) return;
clearTimeout(timeoutRef.current);
const elapsedTime = Date.now() - startedAt;
if (elapsedTime < timeout) {
callback.current({ cancelled: true, elapsedTime });
}
};
}, [delay]);
}, [timeout, enabled]);
}
10 changes: 10 additions & 0 deletions src/hooks/useWalletSectionsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ export default function useWalletSectionsData({

const { isCoinListEdited } = useCoinListEdited();

useEffect(() => {
if (isLoadingUserAssets || type !== 'wallet') return;
const params = { screen: 'wallet' as const, no_icon: 0, no_price: 0, total_tokens: sortedAssets.length };
for (const asset of sortedAssets) {
if (!asset.icon_url) params.no_icon += 1;
if (!asset.price?.relative_change_24h) params.no_price += 1;
}
analyticsV2.track(analyticsV2.event.tokenList, params);
}, [isLoadingUserAssets, sortedAssets, type]);

const walletSections = useMemo(() => {
const accountInfo = {
hiddenAssets,
Expand Down
13 changes: 12 additions & 1 deletion src/screens/SendSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { SendAssetForm, SendAssetList, SendContactList, SendHeader } from '../co
import { SheetActionButton } from '../components/sheet';
import { getDefaultCheckboxes } from './SendConfirmationSheet';
import { WrappedAlert as Alert } from '@/helpers/alert';
import { analytics } from '@/analytics';
import { analytics, analyticsV2 } from '@/analytics';
import { PROFILES, useExperimentalFlag } from '@/config';
import { AssetTypes, NewTransaction, ParsedAddressAsset, TransactionStatus, UniqueAsset } from '@/entities';
import { isNativeAsset } from '@/handlers/assets';
Expand Down Expand Up @@ -129,6 +129,7 @@ type OnSubmitProps = {
export default function SendSheet() {
const { goBack, navigate } = useNavigation();
const sortedAssets = useUserAssetsStore(state => state.legacyUserAssets);
const isLoadingUserAssets = useUserAssetsStore(state => state.isLoadingUserAssets);
const {
gasFeeParamsBySpeed,
gasLimit,
Expand Down Expand Up @@ -894,6 +895,16 @@ export default function SendSheet() {
isUniqueAsset,
]);

useEffect(() => {
if (isLoadingUserAssets || !sortedAssets) return;
const params = { screen: 'send' as const, no_icon: 0, no_price: 0, total_tokens: sortedAssets.length };
for (const asset of sortedAssets) {
if (!asset.icon_url) params.no_icon += 1;
if (!asset.price?.relative_change_24h) params.no_price += 1;
}
analyticsV2.track(analyticsV2.event.tokenList, params);
}, [isLoadingUserAssets, sortedAssets]);

const sendContactListDataKey = useMemo(() => `${ensSuggestions?.[0]?.address || '_'}`, [ensSuggestions]);

const isEmptyWallet = !sortedAssets?.length && !sendableUniqueTokens?.length;
Expand Down
7 changes: 6 additions & 1 deletion src/screens/WalletScreen/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ function WalletScreen() {
const { wallets } = useWallets();

const walletReady = useSelector(({ appState: { walletReady } }: AppState) => walletReady);
const { isWalletEthZero, isLoadingUserAssets, isLoadingBalance, briefSectionsData: walletBriefSectionsData } = useWalletSectionsData();
const {
isWalletEthZero,
isLoadingUserAssets,
isLoadingBalance,
briefSectionsData: walletBriefSectionsData,
} = useWalletSectionsData({ type: 'wallet' });

useEffect(() => {
if (!wallets) return;
Expand Down

0 comments on commit 05364a9

Please sign in to comment.