Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/libs/Navigation/OnyxTabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ type OnyxTabNavigatorProps<TTabName extends string = SelectedTabRequest> = Child

/** Whether tabs should have equal width */
equalWidth?: boolean;

/** Fires when a tab becomes active, including the initial mount. Deduplicated so it fires at most
* once per distinct active tab. Intended for side-effects like telemetry where consumers want to
* observe focus events without duplicating React Navigation state-listener noise. */
onTabFocused?: (tabName: TTabName) => void;
};

const TopTab = createMaterialTopTabNavigator<ParamListBase, string>();
Expand Down Expand Up @@ -105,10 +110,12 @@ function OnyxTabNavigator<TTabName extends string = SelectedTabRequest>({
lazyLoadEnabled = false,
onTabSelect,
equalWidth = false,
onTabFocused,
...rest
}: OnyxTabNavigatorProps<TTabName>) {
const styles = useThemeStyles();
const isFirstMountRef = useRef(true);
const lastFocusedTabRef = useRef<string | null>(null);
// Mapping of tab name to focus trap container element
const [focusTrapContainerElementMapping, setFocusTrapContainerElementMapping] = useState<Record<string, HTMLElement>>({});
const [selectedTab, selectedTabResult] = useOnyx(`${ONYXKEYS.COLLECTION.SELECTED_TAB}${id}`);
Expand Down Expand Up @@ -193,6 +200,10 @@ function OnyxTabNavigator<TTabName extends string = SelectedTabRequest>({
isFirstMountRef.current = false;
}
const newSelectedTab = routeNames.at(index);
if (newSelectedTab && newSelectedTab !== lastFocusedTabRef.current) {
lastFocusedTabRef.current = newSelectedTab;
onTabFocused?.(newSelectedTab as TTabName);
}
if (selectedTab === newSelectedTab) {
return;
}
Expand Down
3 changes: 3 additions & 0 deletions src/libs/actions/IOU/Split.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
} from '@libs/ReportUtils';
import type {OptimisticChatReport} from '@libs/ReportUtils';
import playSound, {SOUNDS} from '@libs/Sound';
import emitExpenseCreated from '@libs/telemetry/emitExpenseCreated';
import {addOptimization, setPendingSubmitFollowUpAction} from '@libs/telemetry/submitFollowUpAction';
import {
buildOptimisticTransaction,
Expand Down Expand Up @@ -1995,6 +1996,8 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest
onyxData = moneyRequestOnyxData;
distanceIouReport = iouReport;

emitExpenseCreated(transaction.iouRequestType);

const isGPSDistanceRequest = transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_GPS;

if (isDistanceExpenseType(transaction.iouRequestType)) {
Expand Down
18 changes: 18 additions & 0 deletions src/libs/telemetry/emitExpenseCreated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as Sentry from '@sentry/react-native';
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';

type IOURequestType = ValueOf<typeof CONST.IOU.REQUEST_TYPE>;

function emitExpenseCreated(iouRequestType: IOURequestType | undefined) {
if (!iouRequestType) {
return;
}
Sentry.startInactiveSpan({
name: `ExpenseCreated.${iouRequestType}`,
op: 'expense.created',

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.

❌ CONSISTENCY-2 (docs)

The strings 'expense.created' (op) and the 'ExpenseCreated.' prefix (name) are magic strings. The existing telemetry codebase consistently defines span identifiers as named constants in CONST.TELEMETRY (e.g., CONST.TELEMETRY.SPAN_SUBMIT_EXPENSE, CONST.TELEMETRY.SPAN_APP_STARTUP). Using inline strings here diverges from that established pattern and makes it harder to find all telemetry span definitions in one place.

Define these as constants in CONST.TELEMETRY and reference them here:

// In CONST.ts under TELEMETRY
SPAN_EXPENSE_CREATED: 'ExpenseCreated',
OP_EXPENSE_CREATED: 'expense.created',

Reviewed at: 1de2933 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

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.

@Julesssss this makes sense IMO

forceTransaction: true,
})?.end();
}

export default emitExpenseCreated;
14 changes: 12 additions & 2 deletions src/pages/iou/request/DistanceRequestStartPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {useEffect, useMemo, useState} from 'react';
import * as Sentry from '@sentry/react-native';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
import FocusTrapContainerElement from '@components/FocusTrap/FocusTrapContainerElement';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
Expand All @@ -18,7 +19,7 @@ import {endSpan} from '@libs/telemetry/activeSpans';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import SCREENS from '@src/SCREENS';
import type {SelectedTabRequest} from '@src/types/onyx';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import IOURequestStepDistanceGPS from './step/IOURequestStepDistanceGPS';
Expand Down Expand Up @@ -84,6 +85,14 @@ function DistanceRequestStartPage({
endSpan(CONST.TELEMETRY.SPAN_OPEN_CREATE_EXPENSE);
}, []);

const handleTabFocused = useCallback((tabName: SelectedTabRequest) => {

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.

❌ CLEAN-REACT-PATTERNS-0 (docs)

React Compiler is enabled and this file compiles successfully. The useCallback wrapping handleTabFocused is redundant because the compiler automatically memoizes closures based on their captured variables. Manual memoization adds maintenance overhead (dependency arrays) with no benefit.

Remove useCallback and define handleTabFocused as a plain function:

const handleTabFocused = (tabName: SelectedTabRequest) => {
    Sentry.startInactiveSpan({
        name: `${SCREENS.MONEY_REQUEST.DISTANCE_CREATE}.tab.${tabName}`,
        op: 'ui.tab.focus',
        forceTransaction: true,
    })?.end();
};

Reviewed at: 1de2933 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

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.

I also think this should be added

Sentry.startInactiveSpan({
name: `${SCREENS.MONEY_REQUEST.DISTANCE_CREATE}.tab.${tabName}`,
op: 'ui.tab.focus',

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.

❌ CONSISTENCY-2 (docs)

The string 'ui.tab.focus' is a magic string used as a Sentry span operation name. The rest of the telemetry codebase defines span identifiers as named constants in CONST.TELEMETRY. Using an inline string here makes it harder to discover and maintain telemetry instrumentation points.

Define this as a constant in CONST.TELEMETRY and reference it here:

// In CONST.ts under TELEMETRY
OP_TAB_FOCUS: 'ui.tab.focus',

Reviewed at: 1de2933 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

forceTransaction: true,
})?.end();
}, []);

const navigateBack = () => {
Navigation.closeRHPFlow();
};
Expand Down Expand Up @@ -126,6 +135,7 @@ function DistanceRequestStartPage({
tabBar={TabSelector}
onTabBarFocusTrapContainerElementChanged={setTabBarContainerElement}
onActiveTabFocusTrapContainerElementChanged={setActiveTabContainerElement}
onTabFocused={handleTabFocused}
lazyLoadEnabled
>
<TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MAP}>
Expand Down
Loading