Skip to content

Commit 6137dfd

Browse files
authored
feat(Rewards): Add events for Benefits Tracker with needed params (#30076)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> More specific events were needed for Benefits Tracker in Rewards tab. This PR is a companion to the following PR in segment-schema: Consensys/segment-schema#568 ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: n/a ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <img width="1010" height="222" alt="rbdv" src="https://github.com/user-attachments/assets/8955f7a6-54e7-4489-bfa5-8416510e4bd2" /> <img width="1012" height="188" alt="rbbc" src="https://github.com/user-attachments/assets/cc1664dc-2c9f-46c4-8e08-67e0e05f37d6" /> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [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. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] 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** > Low risk: adds new MetaMetrics events and test coverage, with no changes to rewards business logic beyond emitting analytics on mount and button press. > > **Overview** > Adds dedicated MetaMetrics tracking for the Rewards benefit detail screen: emits `REWARDS_BENEFIT_DETAIL_VIEWED` on mount and `REWARDS_BENEFIT_BUTTON_CLICKED` when the claim CTA is pressed, including `benefit_id`, `benefit_name`, and optionally `benefit_type` (plus `button_type: 'claim'` for clicks). > > Registers the two new events in `MetaMetrics.events.ts` and updates `BenefitFullView` tests to mock `useAnalytics` and assert event payloads, including the omission of `benefit_type` when the type name is missing. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 641c5b6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent e0a180e commit 6137dfd

3 files changed

Lines changed: 125 additions & 2 deletions

File tree

app/components/UI/Rewards/Views/BenefitFullView.test.tsx

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@ import BenefitFullView from './BenefitFullView';
44
import Routes from '../../../../constants/navigation/Routes';
55
import { REWARDS_VIEW_SELECTORS } from './RewardsView.constants';
66
import { formatDateRemaining } from '../utils/formatUtils';
7+
import { MetaMetricsEvents } from '../../../../core/Analytics';
8+
import { useAnalytics } from '../../../hooks/useAnalytics/useAnalytics';
9+
import {
10+
createMockEventBuilder,
11+
createMockUseAnalyticsHook,
12+
} from '../../../../util/test/analyticsMock';
713

814
const mockGoBack = jest.fn();
915
const mockNavigate = jest.fn();
1016
const mockPostBenefitImpression = jest.fn().mockResolvedValue(undefined);
1117
const mockUseSelector = jest.fn();
18+
const mockTrackEvent = jest.fn();
19+
const mockCreateEventBuilder = jest.fn(() => createMockEventBuilder());
1220
const mockFormatDateRemaining = formatDateRemaining as jest.MockedFunction<
1321
typeof formatDateRemaining
1422
>;
@@ -60,6 +68,8 @@ jest.mock('../../../../core/Engine', () => ({
6068
},
6169
}));
6270

71+
jest.mock('../../../hooks/useAnalytics/useAnalytics');
72+
6373
jest.mock('../utils/formatUtils', () => ({
6474
...jest.requireActual('../utils/formatUtils'),
6575
formatDateRemaining: jest.fn(),
@@ -108,6 +118,12 @@ describe('BenefitFullView', () => {
108118
mockRouteBenefit = mockBenefit;
109119
mockUseSelector.mockReturnValue('subscription-123');
110120
mockFormatDateRemaining.mockReturnValue('1mo 3d');
121+
jest.mocked(useAnalytics).mockReturnValue(
122+
createMockUseAnalyticsHook({
123+
trackEvent: mockTrackEvent,
124+
createEventBuilder: mockCreateEventBuilder,
125+
}),
126+
);
111127
});
112128

113129
it('renders title, description, and action button', () => {
@@ -162,6 +178,43 @@ describe('BenefitFullView', () => {
162178
});
163179
});
164180

181+
it('tracks benefit detail views with benefit metadata', async () => {
182+
render(<BenefitFullView />);
183+
184+
await waitFor(() => {
185+
expect(mockCreateEventBuilder).toHaveBeenCalledWith(
186+
MetaMetricsEvents.REWARDS_BENEFIT_DETAIL_VIEWED,
187+
);
188+
});
189+
const builder = mockCreateEventBuilder.mock.results[0].value;
190+
expect(builder.addProperties).toHaveBeenCalledWith({
191+
benefit_id: mockBenefit.id.toString(),
192+
benefit_name: mockBenefit.longTitle,
193+
benefit_type: mockBenefit.type.name,
194+
});
195+
expect(mockTrackEvent).toHaveBeenCalledWith(builder.build());
196+
});
197+
198+
it('omits benefit type tracking property from detail views when benefit type name is missing', async () => {
199+
mockRouteBenefit = {
200+
...mockBenefit,
201+
type: { id: 7, name: '' },
202+
};
203+
204+
render(<BenefitFullView />);
205+
206+
await waitFor(() => {
207+
expect(mockCreateEventBuilder).toHaveBeenCalledWith(
208+
MetaMetricsEvents.REWARDS_BENEFIT_DETAIL_VIEWED,
209+
);
210+
});
211+
const builder = mockCreateEventBuilder.mock.results[0].value;
212+
expect(builder.addProperties).toHaveBeenCalledWith({
213+
benefit_id: mockBenefit.id.toString(),
214+
benefit_name: mockBenefit.longTitle,
215+
});
216+
});
217+
165218
it('navigates to browser when claim action is pressed and url exists', () => {
166219
const { getByTestId } = render(<BenefitFullView />);
167220

@@ -177,6 +230,43 @@ describe('BenefitFullView', () => {
177230
});
178231
});
179232

233+
it('tracks benefit claim button clicks with benefit metadata', () => {
234+
const { getByTestId } = render(<BenefitFullView />);
235+
jest.clearAllMocks();
236+
237+
fireEvent.press(getByTestId(REWARDS_VIEW_SELECTORS.DETAIL_BENEFIT_ACTION));
238+
239+
expect(mockCreateEventBuilder).toHaveBeenCalledWith(
240+
MetaMetricsEvents.REWARDS_BENEFIT_BUTTON_CLICKED,
241+
);
242+
const builder = mockCreateEventBuilder.mock.results[0].value;
243+
expect(builder.addProperties).toHaveBeenCalledWith({
244+
button_type: 'claim',
245+
benefit_id: mockBenefit.id.toString(),
246+
benefit_name: mockBenefit.longTitle,
247+
benefit_type: mockBenefit.type.name,
248+
});
249+
expect(mockTrackEvent).toHaveBeenCalledWith(builder.build());
250+
});
251+
252+
it('omits benefit type tracking property when benefit type name is missing', () => {
253+
mockRouteBenefit = {
254+
...mockBenefit,
255+
type: { id: 7, name: '' },
256+
};
257+
const { getByTestId } = render(<BenefitFullView />);
258+
jest.clearAllMocks();
259+
260+
fireEvent.press(getByTestId(REWARDS_VIEW_SELECTORS.DETAIL_BENEFIT_ACTION));
261+
262+
const builder = mockCreateEventBuilder.mock.results[0].value;
263+
expect(builder.addProperties).toHaveBeenCalledWith({
264+
button_type: 'claim',
265+
benefit_id: mockBenefit.id.toString(),
266+
benefit_name: mockBenefit.longTitle,
267+
});
268+
});
269+
180270
it('does not navigate when claim action is pressed and url is missing', () => {
181271
mockRouteBenefit = { ...mockBenefit, url: '' };
182272

app/components/UI/Rewards/Views/BenefitFullView.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React, { useEffect, useMemo } from 'react';
2-
import useTrackRewardsPageView from '../hooks/useTrackRewardsPageView';
32
import { Image, ScrollView } from 'react-native';
43
import { useNavigation, useRoute } from '@react-navigation/native';
54
import { useTailwind } from '@metamask/design-system-twrnc-preset';
@@ -30,15 +29,30 @@ import Routes from '../../../../constants/navigation/Routes.ts';
3029
import { useSelector } from 'react-redux';
3130
import { selectRewardsSubscriptionId } from '../../../../selectors/rewards';
3231
import Engine from '../../../../core/Engine';
32+
import { useAnalytics } from '../../../hooks/useAnalytics/useAnalytics';
33+
import { MetaMetricsEvents } from '../../../../core/Analytics';
34+
35+
const BENEFIT_CLAIM_BUTTON_TYPE = 'claim';
3336

3437
const BenefitFullView = () => {
3538
const tw = useTailwind();
3639
const navigation = useNavigation();
3740
const route = useRoute<BenefitFullViewRouteProp>();
3841
const { benefit } = route.params;
3942
const subscriptionId = useSelector(selectRewardsSubscriptionId);
43+
const { trackEvent, createEventBuilder } = useAnalytics();
4044

41-
useTrackRewardsPageView({ page_type: 'benefit_detail' });
45+
useEffect(() => {
46+
trackEvent(
47+
createEventBuilder(MetaMetricsEvents.REWARDS_BENEFIT_DETAIL_VIEWED)
48+
.addProperties({
49+
benefit_id: benefit.id.toString(),
50+
benefit_name: benefit.longTitle,
51+
...(benefit.type?.name ? { benefit_type: benefit.type.name } : {}),
52+
})
53+
.build(),
54+
);
55+
}, [benefit, createEventBuilder, trackEvent]);
4256

4357
useEffect(() => {
4458
if (!subscriptionId) return;
@@ -53,6 +67,17 @@ const BenefitFullView = () => {
5367
}, [benefit, subscriptionId]);
5468

5569
const handleClaim = () => {
70+
trackEvent(
71+
createEventBuilder(MetaMetricsEvents.REWARDS_BENEFIT_BUTTON_CLICKED)
72+
.addProperties({
73+
button_type: BENEFIT_CLAIM_BUTTON_TYPE,
74+
benefit_id: benefit.id.toString(),
75+
benefit_name: benefit.longTitle,
76+
...(benefit.type?.name ? { benefit_type: benefit.type.name } : {}),
77+
})
78+
.build(),
79+
);
80+
5681
if (benefit.url) {
5782
navigation.navigate(Routes.BROWSER.HOME, {
5883
screen: Routes.BROWSER.VIEW,

app/core/Analytics/MetaMetrics.events.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,8 @@ enum EVENT_NAME {
632632
REWARDS_ACCOUNT_LINKING_COMPLETED = 'Rewards Account Linking Completed',
633633
REWARDS_ACCOUNT_LINKING_FAILED = 'Rewards Account Linking Failed',
634634
REWARDS_ACTIVE_BOOST_CLICKED = 'Rewards Active Boost Clicked',
635+
REWARDS_BENEFIT_BUTTON_CLICKED = 'Rewards Benefit Button Clicked',
636+
REWARDS_BENEFIT_DETAIL_VIEWED = 'Rewards Benefit Detail Viewed',
635637
REWARDS_DASHBOARD_TAB_VIEWED = 'Rewards Dashboard Tab Viewed',
636638
REWARDS_DASHBOARD_VIEWED = 'Rewards Dashboard Viewed',
637639
REWARDS_ONBOARDING_STARTED = 'Rewards Onboarding Started',
@@ -1757,6 +1759,12 @@ const events = {
17571759
REWARDS_ACTIVE_BOOST_CLICKED: generateOpt(
17581760
EVENT_NAME.REWARDS_ACTIVE_BOOST_CLICKED,
17591761
),
1762+
REWARDS_BENEFIT_BUTTON_CLICKED: generateOpt(
1763+
EVENT_NAME.REWARDS_BENEFIT_BUTTON_CLICKED,
1764+
),
1765+
REWARDS_BENEFIT_DETAIL_VIEWED: generateOpt(
1766+
EVENT_NAME.REWARDS_BENEFIT_DETAIL_VIEWED,
1767+
),
17601768
REWARDS_DASHBOARD_TAB_VIEWED: generateOpt(
17611769
EVENT_NAME.REWARDS_DASHBOARD_TAB_VIEWED,
17621770
),

0 commit comments

Comments
 (0)