From 043acf3653660396f0ae6af96fb2e4648f1df387 Mon Sep 17 00:00:00 2001 From: Cristi Paval Date: Thu, 5 Mar 2026 17:56:00 +0200 Subject: [PATCH 1/5] Add a const for the scan shortcut span --- src/CONST/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index f9d3b3877584..fcab3ff96b4b 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1795,6 +1795,7 @@ const CONST = { SPAN_SEARCH_ROUTER_COMPUTE_OPTIONS: 'SearchRouter.ComputeOptions', SPAN_SEARCH_ROUTER_LIST_RENDER: 'SearchRouter.ListRender', SPAN_OPEN_CREATE_EXPENSE: 'ManualOpenCreateExpense', + SPAN_SCAN_SHORTCUT: 'ScanShortcut', SPAN_CAMERA_INIT: 'ManualCameraInit', SPAN_SHUTTER_TO_CONFIRMATION: 'ManualShutterToConfirmation', SPAN_RECEIPT_CAPTURE: 'ManualReceiptCapture', From e32c0dceed8cc19225fb948a4f30ce1fec2d1130 Mon Sep 17 00:00:00 2001 From: Cristi Paval Date: Thu, 5 Mar 2026 21:29:32 +0200 Subject: [PATCH 2/5] Add span for scan shortcut --- .../BaseFloatingCameraButton.tsx | 5 +++++ src/libs/actions/IOU/index.ts | 5 ++++- .../step/IOURequestStepScan/index.native.tsx | 14 +++++++++++++- .../iou/request/step/IOURequestStepScan/index.tsx | 14 +++++++++++++- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx b/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx index 61aec9e812fb..c582973cb727 100644 --- a/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx +++ b/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx @@ -9,6 +9,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {startMoneyRequest} from '@libs/actions/IOU'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; +import {startSpan} from '@libs/telemetry/activeSpans'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import {generateReportID, getWorkspaceChats} from '@libs/ReportUtils'; @@ -61,6 +62,10 @@ function BaseFloatingCameraButton({icon}: BaseFloatingCameraButtonProps) { const quickActionReportID = policyChatForActivePolicy?.reportID ?? reportID; Tab.setSelectedTab(CONST.TAB.IOU_REQUEST_TYPE, CONST.IOU.REQUEST_TYPE.SCAN); + startSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT, { + name: CONST.TELEMETRY.SPAN_SCAN_SHORTCUT, + op: CONST.TELEMETRY.SPAN_SCAN_SHORTCUT, + }); startMoneyRequest(CONST.IOU.TYPE.CREATE, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, !!policyChatForActivePolicy?.reportID, undefined, allTransactionDrafts, true); }); }; diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index ae71aade3154..ac979d93f6d7 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -1374,7 +1374,10 @@ function startMoneyRequest( }); clearMoneyRequest(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, skipConfirmation, draftTransactions); if (isFromFloatingActionButton) { - Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`, {isFromFloatingActionButton}); + Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`, { + isFromFloatingActionButton, + ...(requestType && {iouRequestType: requestType}), + }); } switch (requestType) { case CONST.IOU.REQUEST_TYPE.MANUAL: diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index c2044a833ed5..b4987dda4ac9 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -1,5 +1,5 @@ import {useFocusEffect} from '@react-navigation/core'; -import React, {useCallback, useEffect, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react'; import {Alert, AppState, StyleSheet, View} from 'react-native'; import ReactNativeBlobUtil from 'react-native-blob-util'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; @@ -95,6 +95,18 @@ function IOURequestStepScan({ const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report?.policyID}`); + // End scan shortcut span when Scan page finishes first render (only when opened from FAB with request type SCAN) + const isFromScanShortcut = + initialTransaction?.isFromFloatingActionButton === true && + initialTransaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; + useLayoutEffect(() => { + if (!isFromScanShortcut) { + return; + } + endSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT); + return () => cancelSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT); + }, [isFromScanShortcut]); + // Track camera init telemetry const cameraInitSpanStarted = useRef(false); const cameraInitialized = useRef(false); diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index db87e8183711..4e725c6a2e47 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -1,5 +1,5 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {useCallback, useEffect, useReducer, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useLayoutEffect, useReducer, useRef, useState} from 'react'; import type {LayoutRectangle} from 'react-native'; import {PanResponder, StyleSheet, View} from 'react-native'; import {RESULTS} from 'react-native-permissions'; @@ -86,6 +86,18 @@ function IOURequestStepScan({ endSpan(CONST.TELEMETRY.SPAN_OPEN_CREATE_EXPENSE); }, []); + // End scan shortcut span when Scan page finishes first render (only when opened from FAB with request type SCAN) + const isFromScanShortcut = + initialTransaction?.isFromFloatingActionButton === true && + initialTransaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; + useLayoutEffect(() => { + if (!isFromScanShortcut) { + return; + } + endSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT); + return () => cancelSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT); + }, [isFromScanShortcut]); + const navigateBack = useCallback(() => { Navigation.goBack(backTo); }, [backTo]); From 6d8505c636cd074a662bb3719e22911a791664df Mon Sep 17 00:00:00 2001 From: Cristi Paval Date: Thu, 5 Mar 2026 21:57:45 +0200 Subject: [PATCH 3/5] Start span in web as well --- src/pages/inbox/sidebar/FloatingActionButtonAndPopover.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pages/inbox/sidebar/FloatingActionButtonAndPopover.tsx b/src/pages/inbox/sidebar/FloatingActionButtonAndPopover.tsx index e91245da4ed1..d1b5257433ad 100644 --- a/src/pages/inbox/sidebar/FloatingActionButtonAndPopover.tsx +++ b/src/pages/inbox/sidebar/FloatingActionButtonAndPopover.tsx @@ -32,6 +32,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import {startDistanceRequest, startMoneyRequest} from '@libs/actions/IOU'; +import {startSpan} from '@libs/telemetry/activeSpans'; import {openOldDotLink} from '@libs/actions/Link'; import {navigateToQuickAction} from '@libs/actions/QuickActionNavigation'; import {createNewReport, startNewChat} from '@libs/actions/Report'; @@ -366,6 +367,11 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, ref const quickActionReportID = policyChatForActivePolicy?.reportID ?? reportID; Tab.setSelectedTab(CONST.TAB.IOU_REQUEST_TYPE, CONST.IOU.REQUEST_TYPE.SCAN); + // Scan shortcut span: green receipt button (web sidebar) and camera FAB (narrow/mobile) + startSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT, { + name: CONST.TELEMETRY.SPAN_SCAN_SHORTCUT, + op: CONST.TELEMETRY.SPAN_SCAN_SHORTCUT, + }); startMoneyRequest(CONST.IOU.TYPE.CREATE, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, !!policyChatForActivePolicy?.reportID, undefined, allTransactionDrafts, true); }); }, [policyChatForActivePolicy?.policyID, policyChatForActivePolicy?.reportID, reportID, allTransactionDrafts]); From 52c31e575c01a09c091a5d6d3965e4356b1791e7 Mon Sep 17 00:00:00 2001 From: Cristi Paval Date: Thu, 5 Mar 2026 21:58:16 +0200 Subject: [PATCH 4/5] Prettier --- .../FloatingCameraButton/BaseFloatingCameraButton.tsx | 2 +- src/pages/inbox/sidebar/FloatingActionButtonAndPopover.tsx | 2 +- .../iou/request/step/IOURequestStepScan/index.native.tsx | 4 +--- src/pages/iou/request/step/IOURequestStepScan/index.tsx | 4 +--- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx b/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx index c582973cb727..6ec88ed53a2f 100644 --- a/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx +++ b/src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx @@ -9,11 +9,11 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {startMoneyRequest} from '@libs/actions/IOU'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; -import {startSpan} from '@libs/telemetry/activeSpans'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import {generateReportID, getWorkspaceChats} from '@libs/ReportUtils'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; +import {startSpan} from '@libs/telemetry/activeSpans'; import variables from '@styles/variables'; import Tab from '@userActions/Tab'; import CONST from '@src/CONST'; diff --git a/src/pages/inbox/sidebar/FloatingActionButtonAndPopover.tsx b/src/pages/inbox/sidebar/FloatingActionButtonAndPopover.tsx index d1b5257433ad..590adc16d095 100644 --- a/src/pages/inbox/sidebar/FloatingActionButtonAndPopover.tsx +++ b/src/pages/inbox/sidebar/FloatingActionButtonAndPopover.tsx @@ -32,7 +32,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import {startDistanceRequest, startMoneyRequest} from '@libs/actions/IOU'; -import {startSpan} from '@libs/telemetry/activeSpans'; import {openOldDotLink} from '@libs/actions/Link'; import {navigateToQuickAction} from '@libs/actions/QuickActionNavigation'; import {createNewReport, startNewChat} from '@libs/actions/Report'; @@ -58,6 +57,7 @@ import { isPolicyExpenseChat, } from '@libs/ReportUtils'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; +import {startSpan} from '@libs/telemetry/activeSpans'; import isOnSearchMoneyRequestReportPage from '@navigation/helpers/isOnSearchMoneyRequestReportPage'; import variables from '@styles/variables'; import {closeReactNativeApp} from '@userActions/HybridApp'; diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index b4987dda4ac9..e9159635f5f4 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -96,9 +96,7 @@ function IOURequestStepScan({ const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report?.policyID}`); // End scan shortcut span when Scan page finishes first render (only when opened from FAB with request type SCAN) - const isFromScanShortcut = - initialTransaction?.isFromFloatingActionButton === true && - initialTransaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; + const isFromScanShortcut = initialTransaction?.isFromFloatingActionButton === true && initialTransaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; useLayoutEffect(() => { if (!isFromScanShortcut) { return; diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 4e725c6a2e47..074aec7d9cd2 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -87,9 +87,7 @@ function IOURequestStepScan({ }, []); // End scan shortcut span when Scan page finishes first render (only when opened from FAB with request type SCAN) - const isFromScanShortcut = - initialTransaction?.isFromFloatingActionButton === true && - initialTransaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; + const isFromScanShortcut = initialTransaction?.isFromFloatingActionButton === true && initialTransaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; useLayoutEffect(() => { if (!isFromScanShortcut) { return; From 334cfe57600a2b897488434ccc62dc19cc506a5c Mon Sep 17 00:00:00 2001 From: Cristi Paval Date: Thu, 5 Mar 2026 22:13:34 +0200 Subject: [PATCH 5/5] hook for ending span --- .../step/IOURequestStepScan/index.native.tsx | 13 +++-------- .../request/step/IOURequestStepScan/index.tsx | 13 +++-------- .../IOURequestStepScan/useScanShortcutSpan.ts | 22 +++++++++++++++++++ 3 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 src/pages/iou/request/step/IOURequestStepScan/useScanShortcutSpan.ts diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index e9159635f5f4..d50c2e53271c 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -1,5 +1,5 @@ import {useFocusEffect} from '@react-navigation/core'; -import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import {Alert, AppState, StyleSheet, View} from 'react-native'; import ReactNativeBlobUtil from 'react-native-blob-util'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; @@ -51,6 +51,7 @@ import NavigationAwareCamera from './NavigationAwareCamera/Camera'; import ReceiptPreviews from './ReceiptPreviews'; import type IOURequestStepScanProps from './types'; import useReceiptScan from './useReceiptScan'; +import useScanShortcutSpan from './useScanShortcutSpan'; function IOURequestStepScan({ report, @@ -95,15 +96,7 @@ function IOURequestStepScan({ const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report?.policyID}`); - // End scan shortcut span when Scan page finishes first render (only when opened from FAB with request type SCAN) - const isFromScanShortcut = initialTransaction?.isFromFloatingActionButton === true && initialTransaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; - useLayoutEffect(() => { - if (!isFromScanShortcut) { - return; - } - endSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT); - return () => cancelSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT); - }, [isFromScanShortcut]); + useScanShortcutSpan(initialTransaction); // Track camera init telemetry const cameraInitSpanStarted = useRef(false); diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 074aec7d9cd2..2c4bb84001d9 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -1,5 +1,5 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {useCallback, useEffect, useLayoutEffect, useReducer, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useReducer, useRef, useState} from 'react'; import type {LayoutRectangle} from 'react-native'; import {PanResponder, StyleSheet, View} from 'react-native'; import {RESULTS} from 'react-native-permissions'; @@ -49,6 +49,7 @@ import NavigationAwareCamera from './NavigationAwareCamera/WebCamera'; import ReceiptPreviews from './ReceiptPreviews'; import type IOURequestStepScanProps from './types'; import useReceiptScan from './useReceiptScan'; +import useScanShortcutSpan from './useScanShortcutSpan'; function IOURequestStepScan({ report, @@ -86,15 +87,7 @@ function IOURequestStepScan({ endSpan(CONST.TELEMETRY.SPAN_OPEN_CREATE_EXPENSE); }, []); - // End scan shortcut span when Scan page finishes first render (only when opened from FAB with request type SCAN) - const isFromScanShortcut = initialTransaction?.isFromFloatingActionButton === true && initialTransaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; - useLayoutEffect(() => { - if (!isFromScanShortcut) { - return; - } - endSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT); - return () => cancelSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT); - }, [isFromScanShortcut]); + useScanShortcutSpan(initialTransaction); const navigateBack = useCallback(() => { Navigation.goBack(backTo); diff --git a/src/pages/iou/request/step/IOURequestStepScan/useScanShortcutSpan.ts b/src/pages/iou/request/step/IOURequestStepScan/useScanShortcutSpan.ts new file mode 100644 index 000000000000..369c81bc4445 --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepScan/useScanShortcutSpan.ts @@ -0,0 +1,22 @@ +import {useLayoutEffect} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {cancelSpan, endSpan} from '@libs/telemetry/activeSpans'; +import CONST from '@src/CONST'; +import type Transaction from '@src/types/onyx/Transaction'; + +/** + * Ends the scan shortcut Sentry span when the Scan page finishes first render, + * only when the flow was started from the green receipt FAB (isFromFloatingActionButton + request type SCAN). + */ +function useScanShortcutSpan(initialTransaction: OnyxEntry) { + const isFromScanShortcut = initialTransaction?.isFromFloatingActionButton === true && initialTransaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; + useLayoutEffect(() => { + if (!isFromScanShortcut) { + return; + } + endSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT); + return () => cancelSpan(CONST.TELEMETRY.SPAN_SCAN_SHORTCUT); + }, [isFromScanShortcut]); +} + +export default useScanShortcutSpan;