Skip to content

chore: track explore conversions in predict cp-7.80.0#30722

Merged
juanmigdr merged 2 commits into
mainfrom
chore/track-explore-conversions-in-predict
May 30, 2026
Merged

chore: track explore conversions in predict cp-7.80.0#30722
juanmigdr merged 2 commits into
mainfrom
chore/track-explore-conversions-in-predict

Conversation

@juanmigdr

@juanmigdr juanmigdr commented May 28, 2026

Copy link
Copy Markdown
Member

Description

Track explore conversions in predict

Changelog

CHANGELOG entry: track explore conversions in predict

Related issues

Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-3271

Manual testing steps

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

Before

After

Pre-merge author checklist

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 to import wallets with many accounts and tokens
  • I've instrumented key operations with Sentry traces for production performance metrics

For performance guidelines and tooling, see the Performance Guide.

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.

Note

Low Risk
Analytics attribution and navigation params only; no changes to trading, auth, or payment logic.

Overview
This PR wires Predict analytics entryPoint through navigation and the feed so Explore-driven journeys can be measured as conversions.

PredictFeed now accepts an optional entryPoint prop (overriding route params), uses that value for session attribution and performance traces, and passes a resolved listEntryPoint into market cards—defaulting to predict_feed when nothing is set. Search results still use the search entry point.

Explore → Predict list: navigateToPredictionsList always sends entryPoint in route params (default explore); a new navigateToExplorePredictionsList helper is used from Explore tabs (Now, Crypto, Sports, Macro, RWAs). Homepage embedded feed explicitly passes predict_feed.

Tests assert explore entry points on buy preview order events, market details, feed market cards, and homepage discovery tabs.

Reviewed by Cursor Bugbot for commit 8c59bfc. Bugbot is set up for automated code reviews on this repo. Configure here.

@juanmigdr juanmigdr requested a review from a team as a code owner May 28, 2026 08:08
@sonarqubecloud

Copy link
Copy Markdown

@juanmigdr juanmigdr enabled auto-merge May 28, 2026 08:36
bergarces
bergarces previously approved these changes May 28, 2026
@MarioAslau

Copy link
Copy Markdown
Contributor

High severity

H1 — PredictTabContent’s useRoute<RouteProp<…, 'PredictMarketList'>>() lies about the route in the Homepage/Discovery tab and silently mis-attributes entry points

PredictFeed is mounted in two places:

  • The Predict navigator at Routes.PREDICT.MARKET_LIST (where entryPoint is now passed).
  • HomepageDiscoveryTabs (app/components/Views/Homepage/components/HomepageDiscoveryTabs/HomepageDiscoveryTabs.tsx), as the “Predictions” tab inside the Homepage screen.

The new code in PredictTabContent does:

  const route =
    useRoute<RouteProp<PredictNavigationParamList, 'PredictMarketList'>>();
  const listEntryPoint =
    route.params?.entryPoint ?? PredictEventValues.ENTRY_POINT.PREDICT_FEED;

When PredictFeed is rendered inside the Homepage tab, useRoute() returns the Homepage route, not PredictMarketList. The generic type assertion is a TypeScript lie — at runtime route.params?.entryPoint reads whatever happens to live on the host screen’s params. Today that is most likely undefined, so it silently falls back to PREDICT_FEED, but:

  1. If a future caller ever navigates to a screen that hosts PredictFeed with an unrelated entryPoint param (e.g. a Wallet/Homepage route that ever gained an entryPoint for any reason), every list-item event from Predict would be attributed to that foreign value with zero compile-time protection — exactly the kind of silent analytics bug this PR is trying to fix.
  2. The Homepage Predictions tab is reachable from things like “New prediction” / “Featured carousel” / “Home section” entry points that are tracked elsewhere. By defaulting to PREDICT_FEED instead of receiving the real entry point, conversions originating from the Homepage discovery tab will be permanently mis-attributed to predict_feed, which is the same bucket already used for in-list re-entries. There is no way to distinguish them after this PR.

Recommendation: stop reading the route in PredictTabContent. Pass entryPoint down as a prop from PredictFeed (which already reads route.params?.entryPoint), and let PredictFeed’s callers (the Predict navigator screen and HomepageDiscoveryTabs) supply the correct value (e.g. expose entryPoint?: PredictEntryPoint on PredictFeedProps and have HomepageDiscoveryTabs pass HOME_SECTION or a new dedicated value).


H2 — startSession default of PREDICT_FEED overwrites a meaningful “unknown” signal and breaks the existing usePredictMeasurement debug context

Before this PR:

sessionManager.startSession(route.params?.entryPoint, initialTabKey);

After:

  useEffect(() => {
    sessionManager.enableAppStateListener();
    sessionManager.startSession(
      route.params?.entryPoint ?? PredictEventValues.ENTRY_POINT.PREDICT_FEED,
      initialTabKey,
    );

Two concrete problems:

  1. Semantic regression. PREDICT_FEED is the entry point already used by PredictFeedSessionManager.handleAppStateChange for in-feed re-entries (alongside BACKGROUND). Conflating “user landed in the feed without an entry point” with “user is already in the feed” destroys the analytics team’s ability to identify untagged surfaces. The previous undefined value was, paradoxically, the useful signal — it surfaced bugs like this PR is trying to fix. After this change, those bugs become invisible.

  2. Inconsistent debug context. A few lines above, the trace’s debugContext.entryPoint still uses the raw route.params?.entryPoint:

      usePredictMeasurement({
        traceName: TraceName.PredictFeedView,
        conditions: [!isSearchVisible],
        debugContext: {
          entryPoint: route.params?.entryPoint,
          isSearchVisible,
        },
      });

    So usePredictMeasurement will log undefined while the session reports predict_feed. Telemetry consumers correlating these two signals will see contradictory data for the same render.

Recommendation:

  • Keep startSession(route.params?.entryPoint, ...) as-is (let the analytics layer track undefined/null as “unattributed”), or introduce a dedicated UNATTRIBUTED / UNKNOWN enum value instead of overloading PREDICT_FEED.
  • If you really do want to default, apply the same default to the trace debugContext so the two signals match.
  • Either path should be coordinated with the analytics team — this is a backwards-incompatible change to the dashboards already keyed off entry_point.

Medium severity

M1 — entryPoint: PredictEntryPoint = EXPLORE default in navigateToPredictionsList makes future mis-attribution easy and silent

export const navigateToPredictionsList = (
  navigation: AppNavigationProp,
  variant: PredictionsVariant,
  entryPoint: PredictEntryPoint = PredictEventValues.ENTRY_POINT.EXPLORE,
): void => {
  const tab = VARIANT_TO_TAB[variant];
  navigation.navigate(Routes.PREDICT.ROOT, {
    screen: Routes.PREDICT.MARKET_LIST,
    params: {
      entryPoint,
      ...(tab && { tab }),
    },
  });
};

Every current call site is in TrendingView (Explore), so defaulting to EXPLORE is correct today:

  • Views/TrendingView/tabs/{SportsTab,RwasTab,NowTab,MacroTab,CryptoTab}.tsx
  • Views/TrendingView/ExplorePageV1.tsx
  • Views/TrendingView/components/QuickActions.tsx

But this helper is a generic “go to the predictions list” utility — nothing in its name, location, or signature prevents a future caller (e.g. a Home banner, a deep link handler, a new entry surface) from calling it without specifying the entry point and silently tagging itself as EXPLORE. That is exactly the same class of bug this PR fixes, just shifted in time.

Recommendation: make entryPoint required (entryPoint: PredictEntryPoint) and update the seven existing call sites explicitly. This is a 2-minute change and protects the metric you are paying real engineering time to instrument.

M2 — Duplicate useRoute call adds a re-render coupling and is harder to test

PredictFeed already calls useRoute<RouteProp<PredictNavigationParamList, 'PredictMarketList'>>() at line 663. The new code re-invokes the same hook inside PredictTabContent, which is rendered up to 5+ times (once per tab in PredictFeedTabs). Each instance now subscribes independently to the route subscription, and any consumer that wants to test PredictTabContent in isolation has to mock useRoute even when the test isn’t about routing. Pass listEntryPoint (or the whole route params object) as a prop instead — also resolves H1.

M3 — useCallback(renderItem, [...]) now depends on a route-derived value, which silently triggers a full FlashList re-mount on entry-point change

  const renderItem = useCallback(
    (info: { item: PredictMarketType; index: number }) => (
      <PredictMarketListItem
        market={info.item}
        entryPoint={listEntryPoint}
        ...
      />
    ),
    [category, listEntryPoint, transactionActiveAbTests],
  );

If a parent screen ever mutates route.params.entryPoint without unmounting PredictFeed, the renderItem identity will change and FlashList will re-render every visible item. With the current navigator flow this is unlikely, but combined with H1 (route ambiguity) it is a foot-gun. Worth memoising on a stable string value or pulling the entry point onto a useRef if it really is intended to be immutable per mount.

M4 — Tests bind to PredictMarket.mock.calls[0][0], which is index-fragile

      expect(mockPredictMarket.mock.calls[0][0].entryPoint).toBe(
        PredictEventValues.ENTRY_POINT.EXPLORE,
      );

PredictFeedTabs maps over tabs and mounts a PredictTabContent per tab. Each one calls renderItem for each market in its data set. The “first” call (calls[0]) is whichever tab’s first item renders first under the mocked FlashList — order is determined by tab ordering, feature flags (upDownEnabled, worldCup…), and the mock data shape. Adding a new tab, reordering tabs, or changing the default of any feature flag in beforeEach can break this assertion in a way that has nothing to do with the entry point logic.

Recommendation: assert on mockPredictMarket.mock.calls.every(([props]) => props.entryPoint === EXPECTED), or have the mock collect props into a Set and assert membership.

M5 — accessibilityLabel={entryPoint} is dead/incorrect mock instrumentation

jest.mock('../../components/PredictMarket', () => {
  const { View, Text } = jest.requireActual('react-native');
  return {
    __esModule: true,
    default: jest.fn(({ testID, entryPoint }) => (
      <View testID={testID} accessibilityLabel={entryPoint}>
        <Text>Market Card</Text>
      </View>
    )),
  };
});

The new tests don’t query by accessibilityLabel; they read mock.calls directly. Stamping entryPoint onto accessibilityLabel therefore (a) is dead, and (b) pollutes accessibility-tree queries used by any future test (getByLabelText('explore') would unexpectedly match a market card). Either query through it (getByLabelText(...)) or drop it.

@juanmigdr juanmigdr dismissed stale reviews from Prithpal-Sooriya and bergarces via 8c59bfc May 28, 2026 14:34
@juanmigdr juanmigdr requested a review from a team as a code owner May 28, 2026 14:34
@github-actions github-actions Bot added size-M and removed size-S labels May 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePredictions, SmokeWalletPlatform, SmokeConfirmations
  • Selected Performance tags: @PerformancePredict
  • Risk Level: medium
  • AI Confidence: 88%
click to see 🤖 AI reasoning details

E2E Test Selection:
The PR makes targeted changes to the Predictions feature and Trending view tabs:

  1. PredictFeed.tsx: Adds entryPoint prop support so the component can receive entry point from parent (not just route params). This enables correct analytics tracking when PredictFeed is embedded in HomepageDiscoveryTabs. Navigation behavior is unchanged.

  2. HomepageDiscoveryTabs.tsx: Passes PREDICT_FEED entry point to the embedded PredictFeed — analytics fix.

  3. predictionsNavigation.ts: Adds navigateToExplorePredictionsList helper with EXPLORE entry point, and makes navigateToPredictionsList accept an optional entry point param. The navigation destination (routes/screens) is unchanged.

  4. CryptoTab, SportsTab, MacroTab, NowTab, RwasTab: All switch from navigateToPredictionsList to navigateToExplorePredictionsList for "View All" navigation. This changes the entry point passed in navigation params from undefined/default to EXPLORE — primarily an analytics change, but the navigation call itself is slightly different.

SmokePredictions: Directly impacted — PredictFeed component changes, navigation from Trending tabs to predictions list. The "View All" flow from Trending tabs now uses a new function. Need to verify the navigation still works correctly.

SmokeWalletPlatform: Trending tab components (CryptoTab, MacroTab, NowTab, RwasTab, SportsTab) are modified. Per tag description, Trending is the connecting point for all subsections and changes to Predictions views embedded in Trending affect this tag.

SmokeConfirmations: Per SmokePredictions tag description, opening/closing positions are on-chain transactions — should be included when selecting SmokePredictions.

The changes are low-risk (analytics/entry point tracking), but the navigation path changes warrant validation of the Predictions and Trending flows.

Performance Test Selection:
PredictFeed.tsx changes affect the market list rendering component (PredictTabContent, PredictFeedTabs) with new prop threading. While the changes are primarily analytics-related, the PredictFeed is a list-rendering component and the entry point prop is now threaded through multiple levels. @PerformancePredict covers prediction market list loading and balance display, which is directly affected by PredictFeed changes.

View GitHub Actions results

@juanmigdr juanmigdr added the skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. label May 28, 2026
@juanmigdr juanmigdr changed the title chore: track explore conversions in predict chore: track explore conversions in predict cp-7.80.0 May 29, 2026

@wachunei wachunei left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@MarioAslau MarioAslau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM !

@juanmigdr juanmigdr added this pull request to the merge queue May 29, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 29, 2026
@juanmigdr juanmigdr added this pull request to the merge queue May 30, 2026
Merged via the queue into main with commit b65bcbd May 30, 2026
306 of 330 checks passed
@juanmigdr juanmigdr deleted the chore/track-explore-conversions-in-predict branch May 30, 2026 05:09
@github-actions github-actions Bot locked and limited conversation to collaborators May 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

size-M skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. team-assets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants