Skip to content

Commit 6e0f698

Browse files
authored
chore: adds market insights metric to Perps view entry point cp-7.71.0 (MetaMask#27814)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Adds two missing metric events to the Perps Market Details view to bring it to parity with the token details flow. `MARKET_INSIGHTS_OPENED` now fires whenever a user taps the Market Insights entry card, and `PERPS_SCREEN_VIEWED` now includes a `market_insights_displayed` boolean property that reflects whether a report was actually shown, with the event held until the insights fetch resolves so the value is fully accurate. ## **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: ## **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** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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** > Low risk: changes are limited to analytics instrumentation and event timing gated on Market Insights loading, with added unit tests to validate tracking and navigation behavior. > > **Overview** > Adds missing Market Insights instrumentation to the Perps market details screen. > > `PerpsMarketDetailsView` now fires `MetaMetricsEvents.MARKET_INSIGHTS_OPENED` (with `perps_market`) when the Market Insights entry card is tapped, and delays `MetaMetricsEvents.PERPS_SCREEN_VIEWED` until insights loading completes so it can include an accurate `market_insights_displayed` boolean. > > Updates/extends `PerpsMarketDetailsView.test.tsx` with mocks for `useMarketInsights`/feature flags and new tests covering the new tracking payloads and navigation to `Routes.MARKET_INSIGHTS.VIEW` with `isPerps: true`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit dc97116. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 1ed96f7 commit 6e0f698

2 files changed

Lines changed: 174 additions & 3 deletions

File tree

app/components/UI/Perps/Views/PerpsMarketDetailsView/PerpsMarketDetailsView.test.tsx

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
import { PerpsConnectionProvider } from '../../providers/PerpsConnectionProvider';
1111
import { useDefaultPayWithTokenWhenNoPerpsBalance } from '../../hooks/useDefaultPayWithTokenWhenNoPerpsBalance';
1212
import { Linking } from 'react-native';
13+
import { MetaMetricsEvents } from '../../../../../core/Analytics';
14+
import Routes from '../../../../../constants/navigation/Routes';
1315

1416
// Mock Linking
1517
jest.mock('react-native/Libraries/Linking/Linking', () => ({
@@ -394,6 +396,34 @@ jest.mock('../../hooks/usePerpsEventTracking', () => ({
394396
})),
395397
}));
396398

399+
const mockUseMarketInsights = jest.fn(
400+
(_assetId?: string | null, _isEnabled?: boolean) => ({
401+
report: null as Record<string, unknown> | null,
402+
isLoading: false,
403+
error: null,
404+
timeAgo: '',
405+
}),
406+
);
407+
408+
jest.mock('../../../MarketInsights', () => ({
409+
useMarketInsights: (assetId: string | null | undefined, isEnabled: boolean) =>
410+
mockUseMarketInsights(assetId, isEnabled),
411+
MarketInsightsEntryCard: ({ onPress }: { onPress: () => void }) => {
412+
const { TouchableOpacity } = jest.requireActual('react-native');
413+
return (
414+
<TouchableOpacity testID="market-insights-entry-card" onPress={onPress} />
415+
);
416+
},
417+
selectMarketInsightsEnabled: jest.fn(),
418+
}));
419+
420+
jest.mock(
421+
'../../../../../selectors/featureFlagController/marketInsights',
422+
() => ({
423+
selectMarketInsightsPerpsEnabled: jest.fn(),
424+
}),
425+
);
426+
397427
jest.mock('../../hooks/usePerpsPrices', () => ({
398428
usePerpsPrices: jest.fn(() => ({})),
399429
}));
@@ -3258,4 +3288,134 @@ describe('PerpsMarketDetailsView', () => {
32583288
expect(queryByText('25x')).toBeNull();
32593289
});
32603290
});
3291+
3292+
describe('Market Insights analytics', () => {
3293+
const mockReport = {
3294+
summary: 'BTC momentum is building with increased buying pressure.',
3295+
sentiment: 'bullish',
3296+
generatedAt: new Date().toISOString(),
3297+
};
3298+
3299+
// Stable track mock reference set up in beforeEach via mockImplementation
3300+
const mockTrack = jest.fn();
3301+
3302+
beforeEach(() => {
3303+
// Override usePerpsEventTracking to expose a capturable track mock
3304+
const { usePerpsEventTracking: mockUsePerpsEventTrackingFn } =
3305+
jest.requireMock('../../hooks/usePerpsEventTracking');
3306+
mockUsePerpsEventTrackingFn.mockImplementation(() => ({
3307+
track: mockTrack,
3308+
}));
3309+
3310+
// Enable perps market insights feature flag
3311+
const { useSelector } = jest.requireMock('react-redux');
3312+
const { selectPerpsEligibility } = jest.requireMock(
3313+
'../../selectors/perpsController',
3314+
);
3315+
const { selectMarketInsightsPerpsEnabled } = jest.requireMock(
3316+
'../../../../../selectors/featureFlagController/marketInsights',
3317+
);
3318+
useSelector.mockImplementation((selector: unknown) => {
3319+
if (selector === selectPerpsEligibility) return true;
3320+
if (selector === selectMarketInsightsPerpsEnabled) return true;
3321+
return undefined;
3322+
});
3323+
3324+
// Default: a report is available and loading is complete
3325+
mockUseMarketInsights.mockReturnValue({
3326+
report: mockReport,
3327+
isLoading: false,
3328+
error: null,
3329+
timeAgo: '5m ago',
3330+
});
3331+
});
3332+
3333+
afterEach(() => {
3334+
mockTrack.mockClear();
3335+
});
3336+
3337+
it('fires MARKET_INSIGHTS_OPENED with perps_market when entry card is pressed', () => {
3338+
const { getByTestId } = renderWithProvider(
3339+
<PerpsConnectionProvider>
3340+
<PerpsMarketDetailsView />
3341+
</PerpsConnectionProvider>,
3342+
{ state: initialState },
3343+
);
3344+
3345+
fireEvent.press(getByTestId('market-insights-entry-card'));
3346+
3347+
expect(mockTrack).toHaveBeenCalledWith(
3348+
MetaMetricsEvents.MARKET_INSIGHTS_OPENED,
3349+
expect.objectContaining({ perps_market: 'BTC' }),
3350+
);
3351+
});
3352+
3353+
it('navigates to MarketInsightsView with isPerps flag when entry card is pressed', () => {
3354+
const { getByTestId } = renderWithProvider(
3355+
<PerpsConnectionProvider>
3356+
<PerpsMarketDetailsView />
3357+
</PerpsConnectionProvider>,
3358+
{ state: initialState },
3359+
);
3360+
3361+
fireEvent.press(getByTestId('market-insights-entry-card'));
3362+
3363+
expect(mockNavigate).toHaveBeenCalledWith(
3364+
Routes.MARKET_INSIGHTS.VIEW,
3365+
expect.objectContaining({
3366+
assetIdentifier: 'BTC',
3367+
isPerps: true,
3368+
}),
3369+
);
3370+
});
3371+
3372+
it('passes market_insights_displayed: true to PERPS_SCREEN_VIEWED when a report is available', () => {
3373+
renderWithProvider(
3374+
<PerpsConnectionProvider>
3375+
<PerpsMarketDetailsView />
3376+
</PerpsConnectionProvider>,
3377+
{ state: initialState },
3378+
);
3379+
3380+
const { usePerpsEventTracking: mockUsePerpsEventTrackingFn } =
3381+
jest.requireMock('../../hooks/usePerpsEventTracking');
3382+
3383+
expect(mockUsePerpsEventTrackingFn).toHaveBeenCalledWith(
3384+
expect.objectContaining({
3385+
eventName: MetaMetricsEvents.PERPS_SCREEN_VIEWED,
3386+
properties: expect.objectContaining({
3387+
market_insights_displayed: true,
3388+
}),
3389+
}),
3390+
);
3391+
});
3392+
3393+
it('passes market_insights_displayed: false to PERPS_SCREEN_VIEWED when no report is returned', () => {
3394+
mockUseMarketInsights.mockReturnValue({
3395+
report: null,
3396+
isLoading: false,
3397+
error: null,
3398+
timeAgo: '',
3399+
});
3400+
3401+
renderWithProvider(
3402+
<PerpsConnectionProvider>
3403+
<PerpsMarketDetailsView />
3404+
</PerpsConnectionProvider>,
3405+
{ state: initialState },
3406+
);
3407+
3408+
const { usePerpsEventTracking: mockUsePerpsEventTrackingFn } =
3409+
jest.requireMock('../../hooks/usePerpsEventTracking');
3410+
3411+
expect(mockUsePerpsEventTrackingFn).toHaveBeenCalledWith(
3412+
expect.objectContaining({
3413+
eventName: MetaMetricsEvents.PERPS_SCREEN_VIEWED,
3414+
properties: expect.objectContaining({
3415+
market_insights_displayed: false,
3416+
}),
3417+
}),
3418+
);
3419+
});
3420+
});
32613421
});

app/components/UI/Perps/Views/PerpsMarketDetailsView/PerpsMarketDetailsView.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,11 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
230230

231231
// Feature flag for Market Insights in Perps
232232
const isPerpsInsightsEnabled = useSelector(selectMarketInsightsPerpsEnabled);
233-
const { report: perpsInsightsReport, timeAgo: perpsInsightsTimeAgo } =
234-
useMarketInsights(market?.symbol, isPerpsInsightsEnabled);
233+
const {
234+
report: perpsInsightsReport,
235+
timeAgo: perpsInsightsTimeAgo,
236+
isLoading: isPerpsInsightsLoading,
237+
} = useMarketInsights(market?.symbol, isPerpsInsightsEnabled);
235238

236239
// Check if current market is in watchlist
237240
const selectIsWatchlist = useMemo(
@@ -543,13 +546,16 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
543546
});
544547

545548
// Track asset screen viewed event - declarative (main's event name)
549+
// Waits for market insights to finish loading so market_insights_displayed
550+
// reflects the actual display state rather than a loading-time snapshot.
546551
usePerpsEventTracking({
547552
eventName: MetaMetricsEvents.PERPS_SCREEN_VIEWED,
548553
conditions: [
549554
!!market,
550555
!!marketStats,
551556
!isLoadingHistory,
552557
!isLoadingPosition,
558+
!isPerpsInsightsLoading,
553559
],
554560
properties: {
555561
[PERPS_EVENT_PROPERTY.SCREEN_TYPE]:
@@ -559,6 +565,8 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
559565
source || PERPS_EVENT_VALUE.SOURCE.PERP_MARKETS,
560566
[PERPS_EVENT_PROPERTY.OPEN_POSITION]: existingPosition ? 1 : 0,
561567
[PERPS_EVENT_PROPERTY.OPEN_ORDER]: openOrders.length,
568+
market_insights_displayed:
569+
isPerpsInsightsEnabled && Boolean(perpsInsightsReport),
562570
// A/B Test context (TAT-1937) - for baseline exposure tracking
563571
...(isButtonColorTestEnabled && {
564572
[PERPS_EVENT_PROPERTY.AB_TEST_BUTTON_COLOR]: buttonColorVariant,
@@ -1022,6 +1030,9 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
10221030
// Handler for market insights card tap - navigates to full market insights view
10231031
const handleMarketInsightsPress = useCallback(() => {
10241032
if (!market?.symbol) return;
1033+
track(MetaMetricsEvents.MARKET_INSIGHTS_OPENED, {
1034+
perps_market: market.symbol,
1035+
});
10251036
trace({
10261037
name: TraceName.MarketInsightsViewLoad,
10271038
op: TraceOperation.MarketInsightsLoad,
@@ -1031,7 +1042,7 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
10311042
assetIdentifier: market.symbol,
10321043
isPerps: true,
10331044
});
1034-
}, [market?.symbol, navigation]);
1045+
}, [market?.symbol, navigation, track]);
10351046

10361047
// Handler for order selection - navigates to order details
10371048
const handleOrderSelect = useCallback(

0 commit comments

Comments
 (0)