Skip to content

Commit 06877a8

Browse files
authored
feat(ramps): add analytics events for token unavailable modal (MetaMask#27287)
## **Description** Adds analytics tracking for all user interactions with the `TokenNotAvailableModal` — the bottom sheet shown in Unified Buy v2 when the selected token is not supported by the selected provider. Previously only the "Change Provider" button was tracked. This PR adds: - `Ramps Screen Viewed` on modal mount (`location: 'Token Unavailable Modal'`) - `Ramps Change Token Button Clicked` (new event) when user taps "Change Token" - `Ramps Close Button Clicked` when user taps X to dismiss This gives full funnel visibility into what users do when they hit a token-unavailability state. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TRAM-3311 ## **Manual testing steps** ```gherkin Feature: Token unavailable modal analytics Scenario: user sees the modal and taps Change Token Given the user is on the Amount Input screen with a provider selected And the selected token is not supported by that provider When the token unavailable modal appears Then a "Ramps Screen Viewed" event fires with location "Token Unavailable Modal" When user taps "Change Token" Then a "Ramps Change Token Button Clicked" event fires with current_provider set Scenario: user dismisses the modal Given the token unavailable modal is visible When user taps the X close button Then a "Ramps Close Button Clicked" event fires with location "Token Unavailable Modal" Scenario: user taps Change Provider (no regression) Given the token unavailable modal is visible When user taps "Change Provider" Then a "Ramps Change Provider Button Clicked" event fires (unchanged behaviour) ``` ## **Screenshots/Recordings** ### **Before** Only `Ramps Change Provider Button Clicked` fired from this modal. ### **After** All four interaction surfaces tracked. Verify via Segment debugger or analytics logs. ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Analytics-only changes plus a small close-button behavior tweak (close vs navigate) confined to a single modal and covered by updated tests. > > **Overview** > Adds MetaMetrics tracking to the Unified Buy v2 `TokenNotAvailableModal`, firing `RAMPS_SCREEN_VIEWED` on mount and logging button interactions for **Change token** and the modal **close (X)**. > > Introduces a new analytics event constant `RAMPS_CHANGE_TOKEN_BUTTON_CLICKED` in `MetaMetrics.events.ts`, wires the close button to a new `handleClose` (close only, no navigation), and updates/extends tests to mock `useAnalytics` and assert the new event payloads. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit cb0455f. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 0696104 commit 06877a8

3 files changed

Lines changed: 107 additions & 8 deletions

File tree

app/components/UI/Ramp/Views/Modals/TokenNotAvailableModal/TokenNotAvailableModal.test.tsx

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ import TokenNotAvailableModal from './TokenNotAvailableModal';
44
import { renderScreen } from '../../../../../../util/test/renderWithProvider';
55
import { backgroundState } from '../../../../../../util/test/initial-root-state';
66
import Routes from '../../../../../../constants/navigation/Routes';
7+
import { MetaMetricsEvents } from '../../../../../../core/Analytics';
8+
9+
const mockTrackEvent = jest.fn();
10+
const mockAddProperties = jest.fn().mockReturnThis();
11+
const mockBuild = jest.fn(() => ({ name: 'test-event' }));
12+
const mockCreateEventBuilder = jest.fn(() => ({
13+
addProperties: mockAddProperties,
14+
build: mockBuild,
15+
}));
16+
17+
jest.mock('../../../../../hooks/useAnalytics/useAnalytics', () => ({
18+
useAnalytics: () => ({
19+
trackEvent: mockTrackEvent,
20+
createEventBuilder: mockCreateEventBuilder,
21+
}),
22+
}));
723

824
const MOCK_ASSET_ID =
925
'eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
@@ -143,16 +159,13 @@ describe('TokenNotAvailableModal', () => {
143159
);
144160
});
145161

146-
it('navigates to token selection when the close button is pressed', () => {
162+
it('closes the bottom sheet when the close button is pressed', () => {
147163
const { getByTestId } = render(TokenNotAvailableModal);
148164
const closeButton = getByTestId('bottomsheetheader-close-button');
149165

150166
fireEvent.press(closeButton);
151167

152-
expect(mockOnCloseBottomSheet).toHaveBeenCalledWith(expect.any(Function));
153-
expect(mockNavigate).toHaveBeenCalledWith(Routes.RAMP.TOKEN_SELECTION, {
154-
screen: Routes.RAMP.TOKEN_SELECTION,
155-
});
168+
expect(mockOnCloseBottomSheet).toHaveBeenCalled();
156169
});
157170

158171
it('navigates to token selection when modal is dismissed without a pending action', () => {
@@ -181,4 +194,54 @@ describe('TokenNotAvailableModal', () => {
181194

182195
expect(toJSON()).toMatchSnapshot();
183196
});
197+
198+
it('fires RAMPS_SCREEN_VIEWED analytics event on mount', () => {
199+
render(TokenNotAvailableModal);
200+
201+
expect(mockCreateEventBuilder).toHaveBeenCalledWith(
202+
MetaMetricsEvents.RAMPS_SCREEN_VIEWED,
203+
);
204+
expect(mockAddProperties).toHaveBeenCalledWith({
205+
location: 'Token Unavailable Modal',
206+
ramp_type: 'UNIFIED_BUY_2',
207+
});
208+
expect(mockTrackEvent).toHaveBeenCalledTimes(1);
209+
});
210+
211+
it('fires RAMPS_CHANGE_TOKEN_BUTTON_CLICKED analytics event when Change token is pressed', () => {
212+
const { getByText } = render(TokenNotAvailableModal);
213+
mockTrackEvent.mockClear();
214+
mockCreateEventBuilder.mockClear();
215+
mockAddProperties.mockClear();
216+
217+
fireEvent.press(getByText('Change token'));
218+
219+
expect(mockCreateEventBuilder).toHaveBeenCalledWith(
220+
MetaMetricsEvents.RAMPS_CHANGE_TOKEN_BUTTON_CLICKED,
221+
);
222+
expect(mockAddProperties).toHaveBeenCalledWith({
223+
current_provider: 'Transak',
224+
location: 'Token Unavailable Modal',
225+
ramp_type: 'UNIFIED_BUY_2',
226+
});
227+
expect(mockTrackEvent).toHaveBeenCalledTimes(1);
228+
});
229+
230+
it('fires RAMPS_CLOSE_BUTTON_CLICKED analytics event when close button is pressed', () => {
231+
const { getByTestId } = render(TokenNotAvailableModal);
232+
mockTrackEvent.mockClear();
233+
mockCreateEventBuilder.mockClear();
234+
mockAddProperties.mockClear();
235+
236+
fireEvent.press(getByTestId('bottomsheetheader-close-button'));
237+
238+
expect(mockCreateEventBuilder).toHaveBeenCalledWith(
239+
MetaMetricsEvents.RAMPS_CLOSE_BUTTON_CLICKED,
240+
);
241+
expect(mockAddProperties).toHaveBeenCalledWith({
242+
location: 'Token Unavailable Modal',
243+
ramp_type: 'UNIFIED_BUY_2',
244+
});
245+
expect(mockTrackEvent).toHaveBeenCalledTimes(1);
246+
});
184247
});

app/components/UI/Ramp/Views/Modals/TokenNotAvailableModal/TokenNotAvailableModal.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useRef } from 'react';
1+
import React, { useCallback, useEffect, useRef } from 'react';
22
import { View } from 'react-native';
33
import { useNavigation } from '@react-navigation/native';
44
import Text, {
@@ -49,13 +49,33 @@ function TokenNotAvailableModal() {
4949
const tokenName = selectedToken?.name ?? '';
5050
const providerName = selectedProvider?.name ?? '';
5151

52+
useEffect(() => {
53+
trackEvent(
54+
createEventBuilder(MetaMetricsEvents.RAMPS_SCREEN_VIEWED)
55+
.addProperties({
56+
location: 'Token Unavailable Modal',
57+
ramp_type: 'UNIFIED_BUY_2',
58+
})
59+
.build(),
60+
);
61+
}, [trackEvent, createEventBuilder]);
62+
5263
const handleChangeToken = useCallback(() => {
64+
trackEvent(
65+
createEventBuilder(MetaMetricsEvents.RAMPS_CHANGE_TOKEN_BUTTON_CLICKED)
66+
.addProperties({
67+
current_provider: selectedProvider?.name,
68+
location: 'Token Unavailable Modal',
69+
ramp_type: 'UNIFIED_BUY_2',
70+
})
71+
.build(),
72+
);
5373
sheetRef.current?.onCloseBottomSheet(() => {
5474
navigation.navigate(Routes.RAMP.TOKEN_SELECTION, {
5575
screen: Routes.RAMP.TOKEN_SELECTION,
5676
});
5777
});
58-
}, [navigation]);
78+
}, [navigation, selectedProvider?.name, trackEvent, createEventBuilder]);
5979

6080
const handleChangeProvider = useCallback(() => {
6181
trackEvent(
@@ -83,6 +103,18 @@ function TokenNotAvailableModal() {
83103
createEventBuilder,
84104
]);
85105

106+
const handleClose = useCallback(() => {
107+
trackEvent(
108+
createEventBuilder(MetaMetricsEvents.RAMPS_CLOSE_BUTTON_CLICKED)
109+
.addProperties({
110+
location: 'Token Unavailable Modal',
111+
ramp_type: 'UNIFIED_BUY_2',
112+
})
113+
.build(),
114+
);
115+
sheetRef.current?.onCloseBottomSheet();
116+
}, [trackEvent, createEventBuilder]);
117+
86118
const handleDismiss = useCallback(
87119
(hasPendingAction?: boolean) => {
88120
if (!hasPendingAction) {
@@ -102,7 +134,7 @@ function TokenNotAvailableModal() {
102134
testID="token-unavailable-for-provider-modal"
103135
>
104136
<BottomSheetHeader
105-
onClose={handleChangeToken}
137+
onClose={handleClose}
106138
closeButtonProps={{ testID: 'bottomsheetheader-close-button' }}
107139
>
108140
<Text variant={TextVariant.HeadingMD}>

app/core/Analytics/MetaMetrics.events.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ enum EVENT_NAME {
318318
RAMPS_PAYMENT_METHOD_SELECTOR_CLICKED = 'Ramps Payment Method Selector Clicked',
319319
RAMPS_QUICK_AMOUNT_CLICKED = 'Ramps Quick Amount Clicked',
320320
RAMPS_CHANGE_PROVIDER_BUTTON_CLICKED = 'Ramps Change Provider Button Clicked',
321+
RAMPS_CHANGE_TOKEN_BUTTON_CLICKED = 'Ramps Change Token Button Clicked',
321322
RAMPS_PROVIDER_SELECTED = 'Ramps Provider Selected',
322323
RAMPS_CONTINUE_BUTTON_CLICKED = 'Ramps Continue Button Clicked',
323324
RAMPS_TERMS_CONSENT_CLICKED = 'Ramps Terms Consent Clicked',
@@ -1117,6 +1118,9 @@ const events = {
11171118
RAMPS_CHANGE_PROVIDER_BUTTON_CLICKED: generateOpt(
11181119
EVENT_NAME.RAMPS_CHANGE_PROVIDER_BUTTON_CLICKED,
11191120
),
1121+
RAMPS_CHANGE_TOKEN_BUTTON_CLICKED: generateOpt(
1122+
EVENT_NAME.RAMPS_CHANGE_TOKEN_BUTTON_CLICKED,
1123+
),
11201124
RAMPS_PROVIDER_SELECTED: generateOpt(EVENT_NAME.RAMPS_PROVIDER_SELECTED),
11211125
RAMPS_CONTINUE_BUTTON_CLICKED: generateOpt(
11221126
EVENT_NAME.RAMPS_CONTINUE_BUTTON_CLICKED,

0 commit comments

Comments
 (0)