Skip to content

Commit 0f7e479

Browse files
committed
chore: added metrics
1 parent 6ecaa59 commit 0f7e479

5 files changed

Lines changed: 163 additions & 20 deletions

File tree

app/components/Views/TrendingView/Views/ExploreSectionResultsFullView/ExploreSectionResultsFullView.tsx

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback } from 'react';
1+
import React, { useCallback, useRef } from 'react';
22
import { Platform } from 'react-native';
33
import { FlashList, ListRenderItem } from '@shopify/flash-list';
44
import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
@@ -16,6 +16,8 @@ import {
1616
FontWeight,
1717
} from '@metamask/design-system-react-native';
1818
import { SECTIONS_CONFIG, type SectionId } from '../../sections.config';
19+
import { MetaMetricsEvents } from '../../../../../core/Analytics/MetaMetrics.events';
20+
import { TapView, trackExploreEvent } from '../../utils/exploreSearch';
1921

2022
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
2123
type ExploreSectionResultsFullViewParams = {
@@ -38,21 +40,47 @@ const SectionContent: React.FC<SectionContentProps> = ({
3840
const tw = useTailwind();
3941
const navigation = useNavigation();
4042
const section = SECTIONS_CONFIG[sectionId];
43+
const hasScrollTracked = useRef(false);
4144

4245
const { data, isLoading } = section.useSectionData(searchQuery);
4346

47+
const handleScroll = useCallback(() => {
48+
if (hasScrollTracked.current) return;
49+
hasScrollTracked.current = true;
50+
trackExploreEvent(MetaMetricsEvents.EXPLORE_SEARCH_VIEW_ALL_SCROLLED, {
51+
searchQuery,
52+
sectionName: section.title,
53+
});
54+
}, [searchQuery, section.title]);
55+
4456
const renderItem: ListRenderItem<unknown> = useCallback(
4557
({ item, index }) => {
4658
const RowItemComponent = section.OverrideRowItemSearch ?? section.RowItem;
59+
const { getItemIdentifier } = section;
60+
const handleItemTouch = getItemIdentifier
61+
? () => {
62+
trackExploreEvent(
63+
MetaMetricsEvents.EXPLORE_SEARCH_VIEW_ALL_ITEM_CLICKED,
64+
{
65+
searchQuery,
66+
sectionName: section.title,
67+
itemClicked: getItemIdentifier(item),
68+
},
69+
);
70+
}
71+
: undefined;
72+
4773
return (
48-
<RowItemComponent
49-
item={item}
50-
index={index}
51-
navigation={navigation as never}
52-
/>
74+
<TapView onTap={handleItemTouch}>
75+
<RowItemComponent
76+
item={item}
77+
index={index}
78+
navigation={navigation as never}
79+
/>
80+
</TapView>
5381
);
5482
},
55-
[section, navigation],
83+
[section, navigation, searchQuery],
5684
);
5785

5886
const keyExtractor = useCallback(
@@ -79,6 +107,8 @@ const SectionContent: React.FC<SectionContentProps> = ({
79107
keyExtractor={keyExtractor}
80108
contentContainerStyle={tw.style('px-4')}
81109
showsVerticalScrollIndicator={false}
110+
onScroll={handleScroll}
111+
scrollEventThrottle={400}
82112
/>
83113
);
84114
};

app/components/Views/TrendingView/components/ExploreSearchResults/ExploreSearchResults.tsx

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
import { useTailwind } from '@metamask/design-system-twrnc-preset';
1818
import { Pressable } from 'react-native';
1919
import { SECTIONS_CONFIG, type SectionId } from '../../sections.config';
20+
import { MetaMetricsEvents } from '../../../../../core/Analytics/MetaMetrics.events';
21+
import { TapView, trackExploreEvent } from '../../utils/exploreSearch';
2022
import { useExploreSearch } from '../../hooks/useExploreSearch';
2123
import { selectBasicFunctionalityEnabled } from '../../../../../selectors/settings';
2224
import SitesSearchFooter from '../../../../UI/Sites/components/SitesSearchFooter/SitesSearchFooter';
@@ -64,8 +66,14 @@ const ExploreSearchResults: React.FC<ExploreSearchResultsProps> = ({
6466
selectBasicFunctionalityEnabled,
6567
);
6668

69+
const hasScrollTracked = useRef(false);
70+
6771
const handleViewMore = useCallback(
6872
(sectionId: SectionId, title: string) => {
73+
trackExploreEvent(MetaMetricsEvents.EXPLORE_SEARCH_VIEW_ALL_CLICKED, {
74+
searchQuery,
75+
sectionName: title,
76+
});
6977
(
7078
navigation as never as {
7179
navigate: (
@@ -86,6 +94,18 @@ const ExploreSearchResults: React.FC<ExploreSearchResultsProps> = ({
8694
[navigation, searchQuery],
8795
);
8896

97+
const handleScroll = useCallback(() => {
98+
if (hasScrollTracked.current) return;
99+
hasScrollTracked.current = true;
100+
trackExploreEvent(MetaMetricsEvents.EXPLORE_SEARCH_SCROLLED, {
101+
searchQuery,
102+
});
103+
}, [searchQuery]);
104+
105+
useEffect(() => {
106+
hasScrollTracked.current = false;
107+
}, [searchQuery]);
108+
89109
const renderSectionHeader = useCallback(
90110
(item: ListItemHeader) => (
91111
<Box
@@ -213,25 +233,29 @@ const ExploreSearchResults: React.FC<ExploreSearchResultsProps> = ({
213233
return <section.Skeleton />;
214234
}
215235

216-
if (section.OverrideRowItemSearch) {
217-
return (
218-
<section.OverrideRowItemSearch
236+
const RowComponent = section.OverrideRowItemSearch ?? section.RowItem;
237+
const { getItemIdentifier } = section;
238+
const handleItemTouch = getItemIdentifier
239+
? () => {
240+
trackExploreEvent(MetaMetricsEvents.EXPLORE_SEARCH_RESULT_CLICKED, {
241+
searchQuery,
242+
sectionName: section.title,
243+
itemClicked: getItemIdentifier(item.data),
244+
});
245+
}
246+
: undefined;
247+
248+
return (
249+
<TapView onTap={handleItemTouch}>
250+
<RowComponent
219251
item={item.data}
220252
index={index}
221253
navigation={navigation}
222254
/>
223-
);
224-
}
225-
226-
return (
227-
<section.RowItem
228-
item={item.data}
229-
index={index}
230-
navigation={navigation}
231-
/>
255+
</TapView>
232256
);
233257
},
234-
[navigation, renderSectionHeader],
258+
[navigation, renderSectionHeader, searchQuery],
235259
);
236260

237261
const keyExtractor = useCallback((item: FlatListItem, index: number) => {
@@ -256,6 +280,8 @@ const ExploreSearchResults: React.FC<ExploreSearchResultsProps> = ({
256280
keyboardShouldPersistTaps="handled"
257281
testID="trending-search-results-list"
258282
ListFooterComponent={renderFooter}
283+
onScroll={handleScroll}
284+
scrollEventThrottle={400}
259285
/>
260286
</Box>
261287
);

app/components/Views/TrendingView/sections.config.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ interface SectionConfig {
5555
title: string;
5656
icon: SectionIcon;
5757
viewAllAction: (navigation: NavigationProp<ParamListBase>) => void;
58+
/** Returns a stable identifier for an item (e.g. assetId, symbol, url) used in analytics */
59+
getItemIdentifier?: (item: unknown) => string;
5860
RowItem: React.ComponentType<{
5961
item: unknown;
6062
index: number;
@@ -171,6 +173,7 @@ export const SECTIONS_CONFIG: Record<SectionId, SectionConfig> = {
171173
viewAllAction: (navigation) => {
172174
navigation.navigate(Routes.WALLET.TRENDING_TOKENS_FULL_VIEW);
173175
},
176+
getItemIdentifier: (item) => (item as TrendingAsset).assetId,
174177
RowItem: ({ item, index }) => (
175178
<TrendingTokenRowItem
176179
token={item as TrendingAsset}
@@ -219,6 +222,7 @@ export const SECTIONS_CONFIG: Record<SectionId, SectionConfig> = {
219222
},
220223
});
221224
},
225+
getItemIdentifier: (item) => (item as PerpsMarketData).symbol,
222226
RowItem: ({ item, index: _index, navigation }) => (
223227
<PerpsMarketRowItem
224228
market={item as PerpsMarketData}
@@ -273,6 +277,7 @@ export const SECTIONS_CONFIG: Record<SectionId, SectionConfig> = {
273277
viewAllAction: (navigation) => {
274278
navigation.navigate(Routes.WALLET.RWA_TOKENS_FULL_VIEW);
275279
},
280+
getItemIdentifier: (item) => (item as TrendingAsset).assetId,
276281
RowItem: ({ item, index }) => (
277282
<TrendingTokenRowItem
278283
token={item as TrendingAsset}
@@ -303,6 +308,7 @@ export const SECTIONS_CONFIG: Record<SectionId, SectionConfig> = {
303308
screen: Routes.PREDICT.MARKET_LIST,
304309
});
305310
},
311+
getItemIdentifier: (item) => (item as PredictMarketType).id,
306312
RowItem: ({ item, index: _index }) => (
307313
<PredictMarketRowItem market={item as PredictMarketType} />
308314
),
@@ -335,6 +341,7 @@ export const SECTIONS_CONFIG: Record<SectionId, SectionConfig> = {
335341
viewAllAction: (navigation) => {
336342
navigation.navigate(Routes.SITES_FULL_VIEW);
337343
},
344+
getItemIdentifier: (item) => (item as SiteData).url,
338345
RowItem: ({ item, index: _index, navigation }) => (
339346
<SiteRowItemWrapper site={item as SiteData} navigation={navigation} />
340347
),
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React, { useRef } from 'react';
2+
import { View } from 'react-native';
3+
import type { IMetaMetricsEvent } from '../../../../core/Analytics/MetaMetrics.types';
4+
import { analytics } from '../../../../util/analytics/analytics';
5+
import { AnalyticsEventBuilder } from '../../../../util/analytics/AnalyticsEventBuilder';
6+
7+
/**
8+
* Minimum vertical movement (in pixels) to consider a touch gesture a scroll
9+
* rather than a tap. Absorbs micro-jitter from real taps while staying far
10+
* below any intentional scroll distance.
11+
*/
12+
const SCROLL_THRESHOLD = 8;
13+
14+
/**
15+
* Wraps children and fires `onTap` only when the touch ends without a scroll
16+
* gesture. Uses raw touch events (onTouchStart/Move/End) which fire
17+
* independently of the scroll responder system, so movement is reliably
18+
* detected even while a parent FlashList is absorbing a scroll.
19+
*/
20+
export const TapView: React.FC<{
21+
onTap?: () => void;
22+
children: React.ReactNode;
23+
}> = ({ onTap, children }) => {
24+
const startY = useRef(0);
25+
const didScroll = useRef(false);
26+
return (
27+
<View
28+
onTouchStart={(e) => {
29+
startY.current = e.nativeEvent.pageY;
30+
didScroll.current = false;
31+
}}
32+
onTouchMove={(e) => {
33+
if (Math.abs(e.nativeEvent.pageY - startY.current) > SCROLL_THRESHOLD) {
34+
didScroll.current = true;
35+
}
36+
}}
37+
onTouchEnd={() => {
38+
if (!didScroll.current) onTap?.();
39+
}}
40+
>
41+
{children}
42+
</View>
43+
);
44+
};
45+
46+
/**
47+
* Thin wrapper around the analytics event builder pattern.
48+
* Reduces the 5-line boilerplate at every call site to a single line.
49+
*/
50+
export const trackExploreEvent = (
51+
event: IMetaMetricsEvent,
52+
properties: Record<string, string>,
53+
): void => {
54+
analytics.trackEvent(
55+
AnalyticsEventBuilder.createEventBuilder(event)
56+
.addProperties(properties)
57+
.build(),
58+
);
59+
};

app/core/Analytics/MetaMetrics.events.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,13 @@ enum EVENT_NAME {
623623
// Trending
624624
TRENDING_FEED_VIEWED = 'Trending Feed Viewed',
625625

626+
// Explore Search
627+
EXPLORE_SEARCH_VIEW_ALL_CLICKED = 'Explore Search View All Clicked',
628+
EXPLORE_SEARCH_RESULT_CLICKED = 'Explore Search Result Clicked',
629+
EXPLORE_SEARCH_VIEW_ALL_ITEM_CLICKED = 'Explore Search View All Item Clicked',
630+
EXPLORE_SEARCH_SCROLLED = 'Explore Search Scrolled',
631+
EXPLORE_SEARCH_VIEW_ALL_SCROLLED = 'Explore Search View All Scrolled',
632+
626633
// Market Insights
627634
MARKET_INSIGHTS_OPENED = 'Market Insights Opened',
628635
MARKET_INSIGHTS_VIEWED = 'Market Insights Viewed',
@@ -1655,6 +1662,20 @@ const events = {
16551662

16561663
TRENDING_FEED_VIEWED: generateOpt(EVENT_NAME.TRENDING_FEED_VIEWED),
16571664

1665+
EXPLORE_SEARCH_VIEW_ALL_CLICKED: generateOpt(
1666+
EVENT_NAME.EXPLORE_SEARCH_VIEW_ALL_CLICKED,
1667+
),
1668+
EXPLORE_SEARCH_RESULT_CLICKED: generateOpt(
1669+
EVENT_NAME.EXPLORE_SEARCH_RESULT_CLICKED,
1670+
),
1671+
EXPLORE_SEARCH_VIEW_ALL_ITEM_CLICKED: generateOpt(
1672+
EVENT_NAME.EXPLORE_SEARCH_VIEW_ALL_ITEM_CLICKED,
1673+
),
1674+
EXPLORE_SEARCH_SCROLLED: generateOpt(EVENT_NAME.EXPLORE_SEARCH_SCROLLED),
1675+
EXPLORE_SEARCH_VIEW_ALL_SCROLLED: generateOpt(
1676+
EVENT_NAME.EXPLORE_SEARCH_VIEW_ALL_SCROLLED,
1677+
),
1678+
16581679
// Share
16591680
SHARE_ACTION: generateOpt(EVENT_NAME.SHARE_ACTION),
16601681

0 commit comments

Comments
 (0)