Skip to content

Commit 5ee4f82

Browse files
chore(runway): cherry-pick chore: adds market insights metric to Perps view entry point cp-7.71.0 (#27821)
- chore: adds market insights metric to Perps view entry point cp-7.71.0 (#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 --> [6e0f698](6e0f698) Co-authored-by: António Regadas <antonio.regadas@consensys.net>
1 parent b7b8475 commit 5ee4f82

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
}));
@@ -3264,4 +3294,134 @@ describe('PerpsMarketDetailsView', () => {
32643294
expect(queryByText('25x')).toBeNull();
32653295
});
32663296
});
3297+
3298+
describe('Market Insights analytics', () => {
3299+
const mockReport = {
3300+
summary: 'BTC momentum is building with increased buying pressure.',
3301+
sentiment: 'bullish',
3302+
generatedAt: new Date().toISOString(),
3303+
};
3304+
3305+
// Stable track mock reference set up in beforeEach via mockImplementation
3306+
const mockTrack = jest.fn();
3307+
3308+
beforeEach(() => {
3309+
// Override usePerpsEventTracking to expose a capturable track mock
3310+
const { usePerpsEventTracking: mockUsePerpsEventTrackingFn } =
3311+
jest.requireMock('../../hooks/usePerpsEventTracking');
3312+
mockUsePerpsEventTrackingFn.mockImplementation(() => ({
3313+
track: mockTrack,
3314+
}));
3315+
3316+
// Enable perps market insights feature flag
3317+
const { useSelector } = jest.requireMock('react-redux');
3318+
const { selectPerpsEligibility } = jest.requireMock(
3319+
'../../selectors/perpsController',
3320+
);
3321+
const { selectMarketInsightsPerpsEnabled } = jest.requireMock(
3322+
'../../../../../selectors/featureFlagController/marketInsights',
3323+
);
3324+
useSelector.mockImplementation((selector: unknown) => {
3325+
if (selector === selectPerpsEligibility) return true;
3326+
if (selector === selectMarketInsightsPerpsEnabled) return true;
3327+
return undefined;
3328+
});
3329+
3330+
// Default: a report is available and loading is complete
3331+
mockUseMarketInsights.mockReturnValue({
3332+
report: mockReport,
3333+
isLoading: false,
3334+
error: null,
3335+
timeAgo: '5m ago',
3336+
});
3337+
});
3338+
3339+
afterEach(() => {
3340+
mockTrack.mockClear();
3341+
});
3342+
3343+
it('fires MARKET_INSIGHTS_OPENED with perps_market when entry card is pressed', () => {
3344+
const { getByTestId } = renderWithProvider(
3345+
<PerpsConnectionProvider>
3346+
<PerpsMarketDetailsView />
3347+
</PerpsConnectionProvider>,
3348+
{ state: initialState },
3349+
);
3350+
3351+
fireEvent.press(getByTestId('market-insights-entry-card'));
3352+
3353+
expect(mockTrack).toHaveBeenCalledWith(
3354+
MetaMetricsEvents.MARKET_INSIGHTS_OPENED,
3355+
expect.objectContaining({ perps_market: 'BTC' }),
3356+
);
3357+
});
3358+
3359+
it('navigates to MarketInsightsView with isPerps flag when entry card is pressed', () => {
3360+
const { getByTestId } = renderWithProvider(
3361+
<PerpsConnectionProvider>
3362+
<PerpsMarketDetailsView />
3363+
</PerpsConnectionProvider>,
3364+
{ state: initialState },
3365+
);
3366+
3367+
fireEvent.press(getByTestId('market-insights-entry-card'));
3368+
3369+
expect(mockNavigate).toHaveBeenCalledWith(
3370+
Routes.MARKET_INSIGHTS.VIEW,
3371+
expect.objectContaining({
3372+
assetIdentifier: 'BTC',
3373+
isPerps: true,
3374+
}),
3375+
);
3376+
});
3377+
3378+
it('passes market_insights_displayed: true to PERPS_SCREEN_VIEWED when a report is available', () => {
3379+
renderWithProvider(
3380+
<PerpsConnectionProvider>
3381+
<PerpsMarketDetailsView />
3382+
</PerpsConnectionProvider>,
3383+
{ state: initialState },
3384+
);
3385+
3386+
const { usePerpsEventTracking: mockUsePerpsEventTrackingFn } =
3387+
jest.requireMock('../../hooks/usePerpsEventTracking');
3388+
3389+
expect(mockUsePerpsEventTrackingFn).toHaveBeenCalledWith(
3390+
expect.objectContaining({
3391+
eventName: MetaMetricsEvents.PERPS_SCREEN_VIEWED,
3392+
properties: expect.objectContaining({
3393+
market_insights_displayed: true,
3394+
}),
3395+
}),
3396+
);
3397+
});
3398+
3399+
it('passes market_insights_displayed: false to PERPS_SCREEN_VIEWED when no report is returned', () => {
3400+
mockUseMarketInsights.mockReturnValue({
3401+
report: null,
3402+
isLoading: false,
3403+
error: null,
3404+
timeAgo: '',
3405+
});
3406+
3407+
renderWithProvider(
3408+
<PerpsConnectionProvider>
3409+
<PerpsMarketDetailsView />
3410+
</PerpsConnectionProvider>,
3411+
{ state: initialState },
3412+
);
3413+
3414+
const { usePerpsEventTracking: mockUsePerpsEventTrackingFn } =
3415+
jest.requireMock('../../hooks/usePerpsEventTracking');
3416+
3417+
expect(mockUsePerpsEventTrackingFn).toHaveBeenCalledWith(
3418+
expect.objectContaining({
3419+
eventName: MetaMetricsEvents.PERPS_SCREEN_VIEWED,
3420+
properties: expect.objectContaining({
3421+
market_insights_displayed: false,
3422+
}),
3423+
}),
3424+
);
3425+
});
3426+
});
32673427
});

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

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

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

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

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

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

0 commit comments

Comments
 (0)