From 8c94a8ad3edad8db821fc1e1c0c9c76a81e1db64 Mon Sep 17 00:00:00 2001 From: eliran goshen Date: Wed, 3 Dec 2025 16:53:57 +0100 Subject: [PATCH 1/6] track 404 page --- src/CONST/index.ts | 1 + src/pages/ErrorPage/NotFoundPage.tsx | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 723e357d4a12..69b30719acdc 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1687,6 +1687,7 @@ const CONST = { SPAN_OPEN_SEARCH_ROUTER: 'ManualOpenSearchRouter', SPAN_OPEN_CREATE_EXPENSE: 'ManualOpenCreateExpense', SPAN_SEND_MESSAGE: 'ManualSendMessage', + SPAN_NOT_FOUND_PAGE: 'ManualNotFoundPage', // Attribute names ATTRIBUTE_IOU_TYPE: 'iou_type', ATTRIBUTE_IOU_REQUEST_TYPE: 'iou_request_type', diff --git a/src/pages/ErrorPage/NotFoundPage.tsx b/src/pages/ErrorPage/NotFoundPage.tsx index 2917683da298..dffb8d957ca1 100644 --- a/src/pages/ErrorPage/NotFoundPage.tsx +++ b/src/pages/ErrorPage/NotFoundPage.tsx @@ -1,10 +1,12 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import type {FullPageNotFoundViewProps} from '@components/BlockingViews/FullPageNotFoundView'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import Navigation from '@libs/Navigation/Navigation'; +import {endSpan, startSpan} from '@libs/telemetry/activeSpans'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; type NotFoundPageProps = { @@ -21,6 +23,30 @@ function NotFoundPage({onBackButtonPress = () => Navigation.goBack(), isReportRe const topmostReportId = Navigation.getTopmostReportId(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${topmostReportId}`); + useEffect(() => { + startSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, { + name: CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, + op: CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, + }); + + // Handle page unload on web (closing tab/window, full page reload) + const handleBeforeUnload = () => { + endSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE); + }; + + if (typeof window !== 'undefined') { + window.addEventListener('beforeunload', handleBeforeUnload); + } + + return () => { + endSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE); + + if (typeof window !== 'undefined') { + window.removeEventListener('beforeunload', handleBeforeUnload); + } + }; + }, []); + return ( Date: Thu, 4 Dec 2025 17:12:58 +0100 Subject: [PATCH 2/6] trace current url and source --- .../BlockingViews/FullPageNotFoundView.tsx | 3 + src/hooks/useNotFoundSpan.ts | 70 +++++++++++++++++++ src/pages/ErrorPage/NotFoundPage.tsx | 30 ++------ 3 files changed, 77 insertions(+), 26 deletions(-) create mode 100644 src/hooks/useNotFoundSpan.ts diff --git a/src/components/BlockingViews/FullPageNotFoundView.tsx b/src/components/BlockingViews/FullPageNotFoundView.tsx index cf986fb1652a..250550c54f7d 100644 --- a/src/components/BlockingViews/FullPageNotFoundView.tsx +++ b/src/components/BlockingViews/FullPageNotFoundView.tsx @@ -12,6 +12,7 @@ import variables from '@styles/variables'; import type {TranslationPaths} from '@src/languages/types'; import BlockingView from './BlockingView'; import ForceFullScreenView from './ForceFullScreenView'; +import useNotFoundSpan from '@hooks/useNotFoundSpan'; type FullPageNotFoundViewProps = { /** TestID for test */ @@ -87,6 +88,8 @@ function FullPageNotFoundView({ const {translate} = useLocalize(); const illustrations = useMemoizedLazyIllustrations(['ToddBehindCloud'] as const); + useNotFoundSpan(); + if (shouldShow) { StatsCounter('FullPageNotFoundView'); return ( diff --git a/src/hooks/useNotFoundSpan.ts b/src/hooks/useNotFoundSpan.ts new file mode 100644 index 000000000000..8fee5fbe94fa --- /dev/null +++ b/src/hooks/useNotFoundSpan.ts @@ -0,0 +1,70 @@ +import {useContext, useEffect} from 'react'; +import {Platform} from 'react-native'; +import {InitialURLContext} from '@components/InitialURLContextProvider'; +import Navigation from '@libs/Navigation/Navigation'; +import {endSpan, startSpan} from '@libs/telemetry/activeSpans'; +import CONST from '@src/CONST'; + +/** + * Hook to track time spent on NotFoundPage with Sentry span. + * Ends the span after 1 second and adds attributes for URL and navigation source. + */ +export default function useNotFoundSpan() { + const {initialURL} = useContext(InitialURLContext); + + useEffect(() => { + let isDeeplink = false; + let currentUrl = ''; + // Get current URL and check if it's a deeplink + if (Platform.OS === 'web') { + currentUrl = window.location.href; + isDeeplink = currentUrl === initialURL; + } else { + isDeeplink = !!initialURL; + currentUrl = Navigation.getActiveRoute() || ''; + } + + const navigationSource = isDeeplink ? 'deeplink' : 'button'; + + // Start the span + startSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, { + name: CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, + op: CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, + attributes: { + url: currentUrl, + navigation_source: navigationSource, + }, + }); + + // End the span after 1 second + let spanEnded = false; + const endSpanSafely = () => { + if (!spanEnded) { + spanEnded = true; + endSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE); + } + }; + + const timeoutId = setTimeout(() => { + endSpanSafely(); + }, 5000); + + // Handle page unload on web (closing tab/window, full page reload) - fallback + const handleBeforeUnload = () => { + endSpanSafely(); + }; + + if (typeof window !== 'undefined') { + window.addEventListener('beforeunload', handleBeforeUnload); + } + + return () => { + clearTimeout(timeoutId); + endSpanSafely(); + + if (typeof window !== 'undefined') { + window.removeEventListener('beforeunload', handleBeforeUnload); + } + }; + }, []); +} diff --git a/src/pages/ErrorPage/NotFoundPage.tsx b/src/pages/ErrorPage/NotFoundPage.tsx index dffb8d957ca1..69f4aa36bc36 100644 --- a/src/pages/ErrorPage/NotFoundPage.tsx +++ b/src/pages/ErrorPage/NotFoundPage.tsx @@ -1,12 +1,11 @@ -import React, {useEffect} from 'react'; +import React from 'react'; import type {FullPageNotFoundViewProps} from '@components/BlockingViews/FullPageNotFoundView'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; +import useNotFoundSpan from '@hooks/useNotFoundSpan'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import Navigation from '@libs/Navigation/Navigation'; -import {endSpan, startSpan} from '@libs/telemetry/activeSpans'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; type NotFoundPageProps = { @@ -23,29 +22,8 @@ function NotFoundPage({onBackButtonPress = () => Navigation.goBack(), isReportRe const topmostReportId = Navigation.getTopmostReportId(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${topmostReportId}`); - useEffect(() => { - startSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, { - name: CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, - op: CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, - }); - - // Handle page unload on web (closing tab/window, full page reload) - const handleBeforeUnload = () => { - endSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE); - }; - - if (typeof window !== 'undefined') { - window.addEventListener('beforeunload', handleBeforeUnload); - } - - return () => { - endSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE); - - if (typeof window !== 'undefined') { - window.removeEventListener('beforeunload', handleBeforeUnload); - } - }; - }, []); + // Track time spent on NotFoundPage + useNotFoundSpan(); return ( Date: Fri, 5 Dec 2025 09:40:31 +0100 Subject: [PATCH 3/6] remove comments --- src/hooks/useNotFoundSpan.ts | 8 +------- src/pages/ErrorPage/NotFoundPage.tsx | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/hooks/useNotFoundSpan.ts b/src/hooks/useNotFoundSpan.ts index 8fee5fbe94fa..57e71159d293 100644 --- a/src/hooks/useNotFoundSpan.ts +++ b/src/hooks/useNotFoundSpan.ts @@ -5,17 +5,13 @@ import Navigation from '@libs/Navigation/Navigation'; import {endSpan, startSpan} from '@libs/telemetry/activeSpans'; import CONST from '@src/CONST'; -/** - * Hook to track time spent on NotFoundPage with Sentry span. - * Ends the span after 1 second and adds attributes for URL and navigation source. - */ export default function useNotFoundSpan() { const {initialURL} = useContext(InitialURLContext); useEffect(() => { let isDeeplink = false; let currentUrl = ''; - // Get current URL and check if it's a deeplink + if (Platform.OS === 'web') { currentUrl = window.location.href; isDeeplink = currentUrl === initialURL; @@ -26,7 +22,6 @@ export default function useNotFoundSpan() { const navigationSource = isDeeplink ? 'deeplink' : 'button'; - // Start the span startSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, { name: CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, op: CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, @@ -36,7 +31,6 @@ export default function useNotFoundSpan() { }, }); - // End the span after 1 second let spanEnded = false; const endSpanSafely = () => { if (!spanEnded) { diff --git a/src/pages/ErrorPage/NotFoundPage.tsx b/src/pages/ErrorPage/NotFoundPage.tsx index 69f4aa36bc36..42516d4cfc29 100644 --- a/src/pages/ErrorPage/NotFoundPage.tsx +++ b/src/pages/ErrorPage/NotFoundPage.tsx @@ -22,7 +22,6 @@ function NotFoundPage({onBackButtonPress = () => Navigation.goBack(), isReportRe const topmostReportId = Navigation.getTopmostReportId(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${topmostReportId}`); - // Track time spent on NotFoundPage useNotFoundSpan(); return ( From ff7354c07cf7972876874a415744fbc2f0dfa79c Mon Sep 17 00:00:00 2001 From: eliran goshen Date: Fri, 5 Dec 2025 14:02:02 +0100 Subject: [PATCH 4/6] pr fixes --- .../BlockingViews/FullPageNotFoundView.tsx | 3 -- .../telemetry}/useNotFoundSpan.ts | 34 ++----------------- src/pages/ErrorPage/NotFoundPage.tsx | 2 +- 3 files changed, 4 insertions(+), 35 deletions(-) rename src/{hooks => libs/telemetry}/useNotFoundSpan.ts (51%) diff --git a/src/components/BlockingViews/FullPageNotFoundView.tsx b/src/components/BlockingViews/FullPageNotFoundView.tsx index 250550c54f7d..cf986fb1652a 100644 --- a/src/components/BlockingViews/FullPageNotFoundView.tsx +++ b/src/components/BlockingViews/FullPageNotFoundView.tsx @@ -12,7 +12,6 @@ import variables from '@styles/variables'; import type {TranslationPaths} from '@src/languages/types'; import BlockingView from './BlockingView'; import ForceFullScreenView from './ForceFullScreenView'; -import useNotFoundSpan from '@hooks/useNotFoundSpan'; type FullPageNotFoundViewProps = { /** TestID for test */ @@ -88,8 +87,6 @@ function FullPageNotFoundView({ const {translate} = useLocalize(); const illustrations = useMemoizedLazyIllustrations(['ToddBehindCloud'] as const); - useNotFoundSpan(); - if (shouldShow) { StatsCounter('FullPageNotFoundView'); return ( diff --git a/src/hooks/useNotFoundSpan.ts b/src/libs/telemetry/useNotFoundSpan.ts similarity index 51% rename from src/hooks/useNotFoundSpan.ts rename to src/libs/telemetry/useNotFoundSpan.ts index 57e71159d293..a19bab75be75 100644 --- a/src/hooks/useNotFoundSpan.ts +++ b/src/libs/telemetry/useNotFoundSpan.ts @@ -20,45 +20,17 @@ export default function useNotFoundSpan() { currentUrl = Navigation.getActiveRoute() || ''; } - const navigationSource = isDeeplink ? 'deeplink' : 'button'; + const NAVIGATION_SOURCE = isDeeplink ? 'deeplink' : 'button'; startSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, { name: CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, op: CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, attributes: { url: currentUrl, - navigation_source: navigationSource, + navigation_source: NAVIGATION_SOURCE, }, }); - let spanEnded = false; - const endSpanSafely = () => { - if (!spanEnded) { - spanEnded = true; - endSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE); - } - }; - - const timeoutId = setTimeout(() => { - endSpanSafely(); - }, 5000); - - // Handle page unload on web (closing tab/window, full page reload) - fallback - const handleBeforeUnload = () => { - endSpanSafely(); - }; - - if (typeof window !== 'undefined') { - window.addEventListener('beforeunload', handleBeforeUnload); - } - - return () => { - clearTimeout(timeoutId); - endSpanSafely(); - - if (typeof window !== 'undefined') { - window.removeEventListener('beforeunload', handleBeforeUnload); - } - }; + endSpan(CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE); }, []); } diff --git a/src/pages/ErrorPage/NotFoundPage.tsx b/src/pages/ErrorPage/NotFoundPage.tsx index 42516d4cfc29..32482eeaf66f 100644 --- a/src/pages/ErrorPage/NotFoundPage.tsx +++ b/src/pages/ErrorPage/NotFoundPage.tsx @@ -2,10 +2,10 @@ import React from 'react'; import type {FullPageNotFoundViewProps} from '@components/BlockingViews/FullPageNotFoundView'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; -import useNotFoundSpan from '@hooks/useNotFoundSpan'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import Navigation from '@libs/Navigation/Navigation'; +import useNotFoundSpan from '@libs/telemetry/useNotFoundSpan'; import ONYXKEYS from '@src/ONYXKEYS'; type NotFoundPageProps = { From f41e410cefe5b3b1b2b4282dd966363baabfb2d4 Mon Sep 17 00:00:00 2001 From: eliran goshen Date: Fri, 5 Dec 2025 14:29:43 +0100 Subject: [PATCH 5/6] lint fixes --- .../telemetry/{useNotFoundSpan.ts => useAbsentPageSpan.ts} | 6 +++--- src/pages/ErrorPage/NotFoundPage.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) rename src/libs/telemetry/{useNotFoundSpan.ts => useAbsentPageSpan.ts} (86%) diff --git a/src/libs/telemetry/useNotFoundSpan.ts b/src/libs/telemetry/useAbsentPageSpan.ts similarity index 86% rename from src/libs/telemetry/useNotFoundSpan.ts rename to src/libs/telemetry/useAbsentPageSpan.ts index a19bab75be75..11922a3d82f8 100644 --- a/src/libs/telemetry/useNotFoundSpan.ts +++ b/src/libs/telemetry/useAbsentPageSpan.ts @@ -2,10 +2,10 @@ import {useContext, useEffect} from 'react'; import {Platform} from 'react-native'; import {InitialURLContext} from '@components/InitialURLContextProvider'; import Navigation from '@libs/Navigation/Navigation'; -import {endSpan, startSpan} from '@libs/telemetry/activeSpans'; import CONST from '@src/CONST'; +import {endSpan, startSpan} from './activeSpans'; -export default function useNotFoundSpan() { +export default function useAbsentPageSpan() { const {initialURL} = useContext(InitialURLContext); useEffect(() => { @@ -27,7 +27,7 @@ export default function useNotFoundSpan() { op: CONST.TELEMETRY.SPAN_NOT_FOUND_PAGE, attributes: { url: currentUrl, - navigation_source: NAVIGATION_SOURCE, + navigationSource: NAVIGATION_SOURCE, }, }); diff --git a/src/pages/ErrorPage/NotFoundPage.tsx b/src/pages/ErrorPage/NotFoundPage.tsx index 32482eeaf66f..6eb84806f33e 100644 --- a/src/pages/ErrorPage/NotFoundPage.tsx +++ b/src/pages/ErrorPage/NotFoundPage.tsx @@ -5,7 +5,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import Navigation from '@libs/Navigation/Navigation'; -import useNotFoundSpan from '@libs/telemetry/useNotFoundSpan'; +import useAbsentPageSpan from '@libs/telemetry/useAbsentPageSpan'; import ONYXKEYS from '@src/ONYXKEYS'; type NotFoundPageProps = { @@ -20,9 +20,9 @@ function NotFoundPage({onBackButtonPress = () => Navigation.goBack(), isReportRe // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); const topmostReportId = Navigation.getTopmostReportId(); - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${topmostReportId}`); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${topmostReportId}`, {canBeMissing: true}); - useNotFoundSpan(); + useAbsentPageSpan(); return ( Date: Wed, 10 Dec 2025 12:51:01 +0100 Subject: [PATCH 6/6] pr fixes --- src/components/BlockingViews/BlockingView.tsx | 3 +++ src/libs/telemetry/useAbsentPageSpan.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/BlockingViews/BlockingView.tsx b/src/components/BlockingViews/BlockingView.tsx index 70f03da986df..68d1ff21c87c 100644 --- a/src/components/BlockingViews/BlockingView.tsx +++ b/src/components/BlockingViews/BlockingView.tsx @@ -12,6 +12,7 @@ import Text from '@components/Text'; import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import useAbsentPageSpan from '@libs/telemetry/useAbsentPageSpan'; import variables from '@styles/variables'; import type {TranslationPaths} from '@src/languages/types'; import BlockingViewSubtitle from './BlockingViewSubtitle'; @@ -124,6 +125,8 @@ function BlockingView({ ); const containerStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding, addOfflineIndicatorBottomSafeAreaPadding, style: containerStyleProp}); + useAbsentPageSpan(); + return (