Skip to content

Commit 3ad8ed3

Browse files
committed
feat: track Token Details Shared analytics event on share
- Add TOKEN_DETAILS_SHARED ('Token Details Shared') to MetaMetrics.events.ts - Fire the event from handleShare with chain_id, token_symbol, and token_address to identify the most-shared assets in Segment - Add unit tests for onSharePress wiring, URL encoding, and event properties
1 parent ce1dc8e commit 3ad8ed3

3 files changed

Lines changed: 52 additions & 22 deletions

File tree

app/components/UI/TokenDetails/Views/TokenDetails.test.tsx

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,16 @@ describe('TokenDetails', () => {
840840
jest.restoreAllMocks();
841841
});
842842

843+
const invokeSharePress = async () => {
844+
const lastCall = mockTokenDetailsInlineHeader.mock.calls.at(-1);
845+
const { onSharePress } = (lastCall?.[0] ?? {}) as {
846+
onSharePress: () => void;
847+
};
848+
await act(async () => {
849+
onSharePress();
850+
});
851+
};
852+
843853
it('always passes onSharePress to the header', () => {
844854
render(<TokenDetails />);
845855

@@ -850,15 +860,7 @@ describe('TokenDetails', () => {
850860

851861
it('calls Share.share with an encoded CAIP-19 URL when onSharePress is invoked', async () => {
852862
render(<TokenDetails />);
853-
854-
const lastCall = mockTokenDetailsInlineHeader.mock.calls.at(-1);
855-
const { onSharePress } = (lastCall?.[0] ?? {}) as {
856-
onSharePress: () => void;
857-
};
858-
859-
await act(async () => {
860-
onSharePress();
861-
});
863+
await invokeSharePress();
862864

863865
expect(Share.share).toHaveBeenCalledWith(
864866
expect.objectContaining({
@@ -871,24 +873,31 @@ describe('TokenDetails', () => {
871873

872874
it('does not include unencoded colons or slashes in the query param', async () => {
873875
render(<TokenDetails />);
874-
875-
const lastCall2 = mockTokenDetailsInlineHeader.mock.calls.at(-1);
876-
const { onSharePress } = (lastCall2?.[0] ?? {}) as {
877-
onSharePress: () => void;
878-
};
879-
880-
await act(async () => {
881-
onSharePress();
882-
});
876+
await invokeSharePress();
883877

884878
const [{ url }] = (Share.share as jest.Mock).mock.calls[0];
885879
const assetId = new URL(url as string).searchParams.get('assetId') ?? '';
886-
// The raw assetId should round-trip through decodeURIComponent cleanly
887-
expect(decodeURIComponent(encodeURIComponent(assetId))).toBe(assetId);
888-
// And the raw URL must not contain unencoded colons after the scheme
889880
const queryString = (url as string).split('?')[1] ?? '';
881+
expect(decodeURIComponent(encodeURIComponent(assetId))).toBe(assetId);
890882
expect(queryString).not.toContain(':');
891883
});
884+
885+
it('fires TOKEN_DETAILS_SHARED with chain_id, token_symbol and token_address', async () => {
886+
render(<TokenDetails />);
887+
await invokeSharePress();
888+
889+
expect(mockCreateEventBuilder).toHaveBeenCalledWith(
890+
MetaMetricsEvents.TOKEN_DETAILS_SHARED,
891+
);
892+
expect(mockAddProperties).toHaveBeenCalledWith(
893+
expect.objectContaining({
894+
chain_id: '0x1',
895+
token_symbol: 'DAI',
896+
token_address: '0x6b175474e89094c44da98b954eedeac495271d0f',
897+
}),
898+
);
899+
expect(mockTrackEvent).toHaveBeenCalled();
900+
});
892901
});
893902

894903
describe('TOKEN_DETAILS_CLOSED app state tracking', () => {

app/components/UI/TokenDetails/Views/TokenDetails.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ const TokenDetails: React.FC<{
166166
const { themeAppearance } = useTheme();
167167
const isLightMode = themeAppearance === AppThemeKey.light;
168168
const navigation = useNavigation();
169+
const { trackEvent, createEventBuilder } = useAnalytics();
169170
const [isInsightsDisclaimerVisible, setIsInsightsDisclaimerVisible] =
170171
useState(false);
171172
const { onQuickBuyPress, quickBuySheet } = useStickyQuickBuy({
@@ -197,13 +198,31 @@ const TokenDetails: React.FC<{
197198
const url = caip19AssetId
198199
? `https://link.metamask.io/asset?assetId=${encodeURIComponent(caip19AssetId)}`
199200
: undefined;
201+
202+
trackEvent(
203+
createEventBuilder(MetaMetricsEvents.TOKEN_DETAILS_SHARED)
204+
.addProperties({
205+
chain_id: token.chainId,
206+
token_symbol: token.symbol,
207+
token_address: token.address,
208+
})
209+
.build(),
210+
);
211+
200212
// iOS renders `url` as a rich link preview; Android needs it in `message`
201213
Share.share(
202214
Platform.OS === 'ios'
203215
? { message: token.symbol, url }
204216
: { message: url ?? token.symbol },
205217
);
206-
}, [caip19AssetId, token.symbol]);
218+
}, [
219+
caip19AssetId,
220+
createEventBuilder,
221+
token.address,
222+
token.chainId,
223+
token.symbol,
224+
trackEvent,
225+
]);
207226

208227
const {
209228
securityData,

app/core/Analytics/MetaMetrics.events.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ enum EVENT_NAME {
8989
TOKEN_LIST_ITEM_CLICKED = 'Token List Item Clicked',
9090
TOKEN_DETAILS_OPENED = 'Token Details Opened',
9191
TOKEN_DETAILS_CLOSED = 'Token Details Closed',
92+
TOKEN_DETAILS_SHARED = 'Token Details Shared',
9293
TOKEN_DETAILS_CTA_CLICKED = 'Token Details CTA Clicked',
9394
TOKEN_DETAILS_ACTION_CLICKED = 'Token Details Action Clicked',
9495
/**
@@ -1667,6 +1668,7 @@ const events = {
16671668
),
16681669
TOKEN_DETAILS_OPENED: generateOpt(EVENT_NAME.TOKEN_DETAILS_OPENED),
16691670
TOKEN_DETAILS_CLOSED: generateOpt(EVENT_NAME.TOKEN_DETAILS_CLOSED),
1671+
TOKEN_DETAILS_SHARED: generateOpt(EVENT_NAME.TOKEN_DETAILS_SHARED),
16701672
TOKEN_DETAILS_CTA_CLICKED: generateOpt(EVENT_NAME.TOKEN_DETAILS_CTA_CLICKED),
16711673
TOKEN_DETAILS_ACTION_CLICKED: generateOpt(
16721674
EVENT_NAME.TOKEN_DETAILS_ACTION_CLICKED,

0 commit comments

Comments
 (0)