Skip to content

Commit 43e2312

Browse files
feat(perps): add missing analytics properties and fix source propagation (#27493)
## **Description** Adds missing Segment/Mixpanel analytics data across Perps events and fixes source propagation issues to ensure accurate attribution in analytics dashboards. ### Motivation Several Perps analytics parameters were missing or inconsistent in Mixpanel: - `PERP SCREEN VIEWED` had many `(not set)` source values - Transaction events (`TRADE`, `CLOSE`) lacked source and order value tracking - `PERP RISK MANAGEMENT` didn't differentiate TP vs SL vs both - Reusable components navigated without passing `source`, leading to implicit/missing attribution - `source` was sometimes propagated from earlier screens in the navigation chain instead of reflecting the current screen ### Solution 1. **New analytics properties**: `open_order`, `order_value`, `market_category`, `error_type`, `action` (tp/sl/tpsl, flip variants), and `explore` source 2. **Source propagation fix**: Screens now always set themselves as the `source` rather than forwarding from previous screens (e.g., Market List → Market Details uses `perp_markets`, not `explore`) 3. **Explicit source passing**: Reusable components (`PerpsMarketTypeSection`, `PerpsWatchlistMarkets`, `PerpsCard`) now accept `source` as a prop from parent screens instead of navigating without it 4. **Tooltip context**: `button_location` for tooltip "Got it" clicks now includes screen-specific context 5. **Documentation**: Updated `docs/perps/perps-metametrics-reference.md` with all new properties and source ownership best practices ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TAT-2370 ## **Manual testing steps** ```gherkin Feature: Perps analytics source tracking Scenario: Source reflects current screen when navigating through multiple screens Given the user is on the Explore page When the user taps "View All" on the Perps section to open Market List And taps a market to open Market Details And taps Long to open the Trading screen Then PERPS_SCREEN_VIEWED for Market List has source = "explore" And PERPS_SCREEN_VIEWED for Market Details has source = "perp_markets" And PERPS_SCREEN_VIEWED for Trading has source = "perp_asset_screen" Scenario: Reusable components pass source from parent screen Given the user is on Perps Home When the user taps a market in the Crypto section Then PERPS_SCREEN_VIEWED for Market Details has source = "perps_home" Scenario: Trade transaction includes order value and source Given the user is on the Trading screen (from Market Details) When the user places a market order for 0.1 BTC at $60,000 Then PERPS_TRADE_TRANSACTION includes source = "perp_asset_screen" And PERPS_TRADE_TRANSACTION includes order_value = 6000 Scenario: Risk management event differentiates TP/SL Given the user has an open BTC position When the user sets both take profit and stop loss Then PERPS_RISK_MANAGEMENT includes action = "tpsl" Scenario: open_order parameter on screen viewed events Given the user has 3 open orders When the user views the Perps Home screen Then PERPS_SCREEN_VIEWED includes open_order = 3 ``` ## **Screenshots/Recordings** N/A — Analytics-only changes, no UI modifications. ### **Before** N/A ### **After** N/A ## **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] > **Medium Risk** > Mostly analytics instrumentation changes, but they touch navigation params and `TradingService` tracking for trade/close events, so incorrect wiring could skew attribution or event schemas in production dashboards. > > **Overview** > Improves Perps analytics attribution by **standardizing `source` propagation** across navigation (market list/details/order/close flows), including passing `source` explicitly through reusable components (`PerpsMarketTypeSection`, `PerpsWatchlistMarkets`, tooltips, and close-position routing). > > Adds missing MetaMetrics properties across key screen views and transactions: `open_order`/`open_position` counts on more screens, `market_category` on market list, `order_value` for trade and close transactions, and more specific `action` values for TP/SL updates and flip-position direction; updates tests and the Perps MetaMetrics reference docs accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 36cf38e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent e8d5375 commit 43e2312

25 files changed

Lines changed: 198 additions & 41 deletions

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,10 @@ const PerpsClosePositionView: React.FC = () => {
8686
const navigation = useNavigation();
8787
const route =
8888
useRoute<RouteProp<PerpsNavigationParamList, 'PerpsClosePosition'>>();
89-
const { position } = route.params as { position: Position };
89+
const { position, source: routeSource } = route.params as {
90+
position: Position;
91+
source?: string;
92+
};
9093

9194
const inputMethodRef = useRef<InputMethod>('default');
9295
const isAmountInitializedRef = useRef(false);
@@ -392,6 +395,7 @@ const PerpsClosePositionView: React.FC = () => {
392395
metamaskFee: feeResults.metamaskFee,
393396
estimatedPoints: rewardsState.estimatedPoints,
394397
inputMethod: inputMethodRef.current,
398+
source: routeSource,
395399
},
396400
marketPrice: priceData[position.symbol]?.price,
397401
// Always pass slippage parameters for price context

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ jest.mock('@metamask/perps-controller', () => ({
230230
SOURCE: {
231231
MAIN_ACTION_BUTTON: 'main_action_button',
232232
HOMESCREEN_TAB: 'homescreen_tab',
233+
PERPS_HOME: 'perps_home',
233234
},
234235
BUTTON_LOCATION: {
235236
PERPS_HOME: 'perps_home',
@@ -558,7 +559,7 @@ describe('PerpsHomeView', () => {
558559

559560
expect(mockNavigateToMarketList).toHaveBeenCalledWith({
560561
defaultMarketTypeFilter: 'all',
561-
source: PERPS_EVENT_VALUE.SOURCE.HOMESCREEN_TAB,
562+
source: PERPS_EVENT_VALUE.SOURCE.PERPS_HOME,
562563
fromHome: true,
563564
button_clicked: 'magnifying_glass',
564565
button_location: 'perps_home',

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ const PerpsHomeView = () => {
210210
PERPS_EVENT_VALUE.SCREEN_TYPE.PERPS_HOME,
211211
[PERPS_EVENT_PROPERTY.SOURCE]: source,
212212
[PERPS_EVENT_PROPERTY.HAS_PERP_BALANCE]: hasPerpBalance,
213+
[PERPS_EVENT_PROPERTY.OPEN_POSITION]: livePositions.positions.length,
214+
[PERPS_EVENT_PROPERTY.OPEN_ORDER]: orders?.length || 0,
213215
...(buttonClicked && {
214216
[PERPS_EVENT_PROPERTY.BUTTON_CLICKED]: buttonClicked,
215217
}),
@@ -235,7 +237,7 @@ const PerpsHomeView = () => {
235237
);
236238
perpsNavigation.navigateToMarketList({
237239
defaultMarketTypeFilter: 'all',
238-
source: PERPS_EVENT_VALUE.SOURCE.HOMESCREEN_TAB,
240+
source: PERPS_EVENT_VALUE.SOURCE.PERPS_HOME,
239241
fromHome: true,
240242
button_clicked: PERPS_EVENT_VALUE.BUTTON_CLICKED.MAGNIFYING_GLASS,
241243
button_location: PERPS_EVENT_VALUE.BUTTON_LOCATION.PERPS_HOME,
@@ -257,7 +259,7 @@ const PerpsHomeView = () => {
257259
.build(),
258260
);
259261
navigation.navigate(Routes.PERPS.TUTORIAL, {
260-
source: PERPS_EVENT_VALUE.SOURCE.HOMESCREEN_TAB,
262+
source: PERPS_EVENT_VALUE.SOURCE.PERPS_HOME,
261263
});
262264
}, [navigation, trackEvent, createEventBuilder]);
263265

@@ -445,7 +447,7 @@ const PerpsHomeView = () => {
445447
<PerpsCard
446448
key={`${position.symbol}-${index}`}
447449
position={position}
448-
source={PERPS_EVENT_VALUE.SOURCE.HOMESCREEN_TAB}
450+
source={PERPS_EVENT_VALUE.SOURCE.PERPS_HOME}
449451
/>
450452
))}
451453
</View>
@@ -465,7 +467,7 @@ const PerpsHomeView = () => {
465467
<PerpsCard
466468
key={order.orderId}
467469
order={order}
468-
source={PERPS_EVENT_VALUE.SOURCE.HOMESCREEN_TAB}
470+
source={PERPS_EVENT_VALUE.SOURCE.PERPS_HOME}
469471
/>
470472
))}
471473
</View>
@@ -477,6 +479,7 @@ const PerpsHomeView = () => {
477479
isLoading={isLoading.markets}
478480
positions={positions}
479481
orders={orders}
482+
source={PERPS_EVENT_VALUE.SOURCE.PERPS_HOME}
480483
/>
481484

482485
{/* Crypto Markets List */}
@@ -487,6 +490,7 @@ const PerpsHomeView = () => {
487490
marketType="crypto"
488491
sortBy={sortBy}
489492
isLoading={isLoading.markets}
493+
source={PERPS_EVENT_VALUE.SOURCE.PERPS_HOME}
490494
/>
491495
</View>
492496

@@ -497,6 +501,7 @@ const PerpsHomeView = () => {
497501
marketType="commodities"
498502
sortBy={sortBy}
499503
isLoading={isLoading.markets}
504+
source={PERPS_EVENT_VALUE.SOURCE.PERPS_HOME}
500505
/>
501506

502507
{/* Stocks Markets List */}
@@ -507,6 +512,7 @@ const PerpsHomeView = () => {
507512
marketType="stocks"
508513
sortBy={sortBy}
509514
isLoading={isLoading.markets}
515+
source={PERPS_EVENT_VALUE.SOURCE.PERPS_HOME}
510516
/>
511517
</View>
512518

@@ -516,6 +522,7 @@ const PerpsHomeView = () => {
516522
markets={forexMarkets}
517523
marketType="forex"
518524
isLoading={isLoading.markets}
525+
source={PERPS_EVENT_VALUE.SOURCE.PERPS_HOME}
519526
/>
520527

521528
{/* Recent Activity List */}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,7 +1423,7 @@ describe('PerpsMarketDetailsView', () => {
14231423
expect(mockNavigateToOrder).toHaveBeenCalledWith({
14241424
direction: 'long',
14251425
asset: 'BTC',
1426-
source: 'trade_action',
1426+
source: 'perp_asset_screen',
14271427
});
14281428
});
14291429

@@ -1459,7 +1459,7 @@ describe('PerpsMarketDetailsView', () => {
14591459
expect(mockNavigateToOrder).toHaveBeenCalledWith({
14601460
direction: 'short',
14611461
asset: 'BTC',
1462-
source: 'trade_action',
1462+
source: 'perp_asset_screen',
14631463
});
14641464
});
14651465

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
557557
[PERPS_EVENT_PROPERTY.SOURCE]:
558558
source || PERPS_EVENT_VALUE.SOURCE.PERP_MARKETS,
559559
[PERPS_EVENT_PROPERTY.OPEN_POSITION]: existingPosition ? 1 : 0,
560+
[PERPS_EVENT_PROPERTY.OPEN_ORDER]: openOrders.length,
560561
// A/B Test context (TAT-1937) - for baseline exposure tracking
561562
...(isButtonColorTestEnabled && {
562563
[PERPS_EVENT_PROPERTY.AB_TEST_BUTTON_COLOR]: buttonColorVariant,
@@ -620,7 +621,7 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
620621
navigateBack();
621622
} else {
622623
// Fallback to markets list if no previous screen
623-
navigateToHome(source);
624+
navigateToHome(PERPS_EVENT_VALUE.SOURCE.PERP_ASSET_SCREEN);
624625
}
625626
};
626627

@@ -700,7 +701,7 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
700701
navigateToOrder({
701702
direction,
702703
asset: market.symbol,
703-
source: PERPS_EVENT_VALUE.SOURCE.TRADE_ACTION,
704+
source: PERPS_EVENT_VALUE.SOURCE.PERP_ASSET_SCREEN,
704705
});
705706
},
706707
[
@@ -865,7 +866,10 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
865866
return;
866867
}
867868

868-
navigateToClosePosition(existingPosition);
869+
navigateToClosePosition(
870+
existingPosition,
871+
PERPS_EVENT_VALUE.SOURCE.PERP_ASSET_SCREEN,
872+
);
869873
}, [existingPosition, navigateToClosePosition, isEligible, track]);
870874

871875
// Modify position handler - opens the modify action sheet
@@ -1526,6 +1530,7 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
15261530
onClose={handleTooltipClose}
15271531
contentKey={selectedTooltip}
15281532
testID={PerpsMarketDetailsViewSelectorsIDs.BOTTOM_SHEET_TOOLTIP}
1533+
buttonLocation={PERPS_EVENT_VALUE.BUTTON_LOCATION.PERP_MARKET_DETAILS}
15291534
/>
15301535
)}
15311536

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,13 @@ const PerpsMarketListView = ({
104104
if (onMarketSelect) {
105105
onMarketSelect(market);
106106
} else {
107-
perpsNavigation.navigateToMarketDetails(market, route.params?.source);
107+
perpsNavigation.navigateToMarketDetails(
108+
market,
109+
PERPS_EVENT_VALUE.SOURCE.PERP_MARKETS,
110+
);
108111
}
109112
},
110-
[onMarketSelect, perpsNavigation, route.params?.source],
113+
[onMarketSelect, perpsNavigation],
111114
);
112115

113116
// Compute available categories based on market counts (hide empty categories)
@@ -192,6 +195,7 @@ const PerpsMarketListView = ({
192195
PERPS_EVENT_VALUE.SCREEN_TYPE.MARKET_LIST,
193196
[PERPS_EVENT_PROPERTY.SOURCE]: source,
194197
[PERPS_EVENT_PROPERTY.HAS_PERP_BALANCE]: hasPerpBalance,
198+
[PERPS_EVENT_PROPERTY.MARKET_CATEGORY]: marketTypeFilter,
195199
...(buttonClicked && {
196200
[PERPS_EVENT_PROPERTY.BUTTON_CLICKED]: buttonClicked,
197201
}),

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,7 @@ describe('PerpsOrderBookView', () => {
775775

776776
expect(mockNavigateToClosePosition).toHaveBeenCalledWith(
777777
mockLongPosition,
778+
'order_book',
778779
);
779780
});
780781

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ const PerpsOrderBookView: React.FC<PerpsOrderBookViewProps> = ({
298298
PERPS_EVENT_VALUE.SCREEN_TYPE.ORDER_BOOK,
299299
[PERPS_EVENT_PROPERTY.ASSET]: symbol || '',
300300
[PERPS_EVENT_PROPERTY.SOURCE]: PERPS_EVENT_VALUE.SOURCE.PERP_ASSET_SCREEN,
301+
[PERPS_EVENT_PROPERTY.OPEN_POSITION]: existingPosition ? 1 : 0,
301302
},
302303
});
303304

@@ -465,7 +466,10 @@ const PerpsOrderBookView: React.FC<PerpsOrderBookViewProps> = ({
465466
return;
466467
}
467468

468-
navigateToClosePosition(existingPosition);
469+
navigateToClosePosition(
470+
existingPosition,
471+
PERPS_EVENT_VALUE.SOURCE.ORDER_BOOK,
472+
);
469473
}, [existingPosition, navigateToClosePosition, isEligible, track]);
470474

471475
// Handle Modify position button press
@@ -779,6 +783,7 @@ const PerpsOrderBookView: React.FC<PerpsOrderBookViewProps> = ({
779783
onClose={handleTooltipClose}
780784
contentKey={selectedTooltip}
781785
testID={PerpsOrderBookViewSelectorsIDs.BOTTOM_SHEET_TOOLTIP}
786+
buttonLocation={PERPS_EVENT_VALUE.BUTTON_LOCATION.ORDER_BOOK}
782787
/>
783788
</Modal>
784789
</View>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ const PerpsOrderViewContentBase: React.FC<PerpsOrderViewContentProps> = ({
381381
: PERPS_EVENT_VALUE.DIRECTION.SHORT,
382382
[PERPS_EVENT_PROPERTY.SOURCE]:
383383
source ?? PERPS_EVENT_VALUE.SOURCE.PERP_ASSET_SCREEN,
384+
[PERPS_EVENT_PROPERTY.OPEN_POSITION]: currentMarketPosition ? 1 : 0,
384385
...(routeAbTestTokenDetailsLayout && {
385386
ab_tests: {
386387
assetsASSETS2493AbtestTokenDetailsLayout: routeAbTestTokenDetailsLayout,
@@ -1788,6 +1789,7 @@ const PerpsOrderViewContentBase: React.FC<PerpsOrderViewContentProps> = ({
17881789
contentKey={selectedTooltip}
17891790
testID={PerpsOrderViewSelectorsIDs.BOTTOM_SHEET_TOOLTIP}
17901791
key={selectedTooltip}
1792+
buttonLocation={PERPS_EVENT_VALUE.BUTTON_LOCATION.PERPS_ASSET_SCREEN}
17911793
data={
17921794
selectedTooltip === 'fees'
17931795
? {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ describe('PerpsSelectModifyActionView', () => {
184184

185185
fireEvent.press(screen.getByTestId('reduce-position'));
186186

187-
expect(mockNavigateToClosePosition).toHaveBeenCalledWith(mockLongPosition);
187+
expect(mockNavigateToClosePosition).toHaveBeenCalledWith(
188+
mockLongPosition,
189+
'position_screen',
190+
);
188191
});
189192

190193
it('calls onReversePosition when flip_position is selected with callback', () => {

0 commit comments

Comments
 (0)