Skip to content
Merged
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
15 changes: 15 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1805,6 +1805,7 @@ const CONST = {
SPAN_CONFIRMATION_RECEIPT_LOAD: 'ManualConfirmationReceiptLoad',
SPAN_SUBMIT_EXPENSE: 'ManualCreateExpenseSubmit',
SPAN_NAVIGATE_AFTER_EXPENSE_CREATE: 'ManualCreateExpenseNavigation',
SPAN_SUBMIT_TO_DESTINATION_VISIBLE: 'ManualSubmitToDestinationVisible',
SPAN_EXPENSE_SERVER_RESPONSE: 'ManualCreateExpenseServerResponse',
SPAN_SEND_MESSAGE: 'ManualSendMessage',
SPAN_NOT_FOUND_PAGE: 'ManualNotFoundPage',
Expand Down Expand Up @@ -1850,12 +1851,25 @@ const CONST = {
ATTRIBUTE_SCENARIO: 'scenario',
ATTRIBUTE_HAS_RECEIPT: 'has_receipt',
ATTRIBUTE_IS_FROM_GLOBAL_CREATE: 'is_from_global_create',
/** Sentry span attribute: follow-up action taken after submit (e.g. dismiss_modal_and_open_report, navigate_to_search). */
ATTRIBUTE_SUBMIT_FOLLOW_UP_ACTION: 'submit_follow_up_action',
ATTRIBUTE_COMMAND: 'command',
ATTRIBUTE_JSON_CODE: 'json_code',
ATTRIBUTE_COLD_START: 'cold_start',
ATTRIBUTE_TRIGGER: 'trigger',
ATTRIBUTE_PLATFORM: 'platform',
ATTRIBUTE_IS_MULTI_SCAN: 'is_multi_scan',
/** Follow-up action after expense submit (action-based; used as submit_follow_up_action in span). */
SUBMIT_FOLLOW_UP_ACTION: {
DISMISS_MODAL_AND_OPEN_REPORT: 'dismiss_modal_and_open_report',
NAVIGATE_TO_SEARCH: 'navigate_to_search',
DISMISS_MODAL_ONLY: 'dismiss_modal_only',
},
/** Trigger for useSubmitToDestinationVisible: end span on focus vs on layout. */
SUBMIT_TO_DESTINATION_VISIBLE_TRIGGER: {
FOCUS: 'focus',
LAYOUT: 'layout',
},
SUBMIT_EXPENSE_SCENARIO: {
REQUEST_MONEY_MANUAL: 'request_money_manual',
REQUEST_MONEY_SCAN: 'request_money_scan',
Expand All @@ -1866,6 +1880,7 @@ const CONST = {
SPLIT_GLOBAL: 'split_global',
INVOICE: 'invoice',
PER_DIEM: 'per_diem',
SEND_MONEY: 'send_money',
},
// Event names
EVENT_SKELETON_ATTRIBUTES_UPDATE: 'skeleton_attributes_updated',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import {tierNameSelector} from '@selectors/UserWallet';
import isEmpty from 'lodash/isEmpty';
import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
import type {ListRenderItemInfo, NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
import type {LayoutChangeEvent, ListRenderItemInfo, NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
import {DeviceEventEmitter, InteractionManager, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
Expand Down Expand Up @@ -121,6 +121,9 @@

/** The type of action that's pending */
reportPendingAction?: PendingAction | null;

/** Callback executed on layout */
onLayout?: (event: LayoutChangeEvent) => void;
};

function MoneyRequestReportActionsList({
Expand All @@ -134,6 +137,7 @@
hasPendingDeletionTransaction,
showReportActionsLoadingState,
reportPendingAction,
onLayout,
}: MoneyRequestReportListProps) {
const styles = useThemeStyles();
const {translate, getLocalDateFromDatetime} = useLocalize();
Expand Down Expand Up @@ -734,7 +738,7 @@
reportScrollManager.scrollToEnd();
readActionSkipped.current = false;
readNewestAction(report.reportID, !!reportMetadata?.hasOnceLoadedReportActions);
}, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, reportMetadata?.hasOnceLoadedReportActions]);

Check warning on line 741 in src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

React Hook useCallback has a missing dependency: 'introSelected'. Either include it or remove the dependency array

Check warning on line 741 in src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

React Hook useCallback has a missing dependency: 'introSelected'. Either include it or remove the dependency array

const scrollToNewTransaction = useCallback(
(pageY: number) => {
Expand Down Expand Up @@ -843,6 +847,7 @@
/>
<SearchMoneyRequestReportEmptyState
report={report}
onLayout={onLayout}
policy={policy}
/>
</ScrollView>
Expand All @@ -869,6 +874,7 @@
/>
<MoneyRequestReportTransactionList
report={report}
onLayout={onLayout}
transactions={transactions}
newTransactions={newTransactions}
hasPendingDeletionTransaction={hasPendingDeletionTransaction}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React, {memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, u
import {View} from 'react-native';
// ScrollView type is needed for the horizontal scroll ref; the project ScrollView component is used for rendering.
// eslint-disable-next-line no-restricted-imports
import type {NativeScrollEvent, NativeSyntheticEvent, ScrollView as RNScrollView} from 'react-native';
import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, ScrollView as RNScrollView} from 'react-native';
import type {TupleToUnion} from 'type-fest';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
import Checkbox from '@components/Checkbox';
Expand Down Expand Up @@ -101,6 +101,9 @@ type MoneyRequestReportTransactionListProps = {

/** Whether the report actions are being loaded, used to show 'Comments' during loading state */
isLoadingInitialReportActions?: boolean;

/** Callback executed on layout */
onLayout?: (event: LayoutChangeEvent) => void;
};

type TransactionWithOptionalHighlight = OnyxTypes.Transaction & {
Expand Down Expand Up @@ -156,6 +159,7 @@ function MoneyRequestReportTransactionList({
scrollToNewTransaction,
policy,
hasComments,
onLayout,
isLoadingInitialReportActions = false,
}: MoneyRequestReportTransactionListProps) {
useCopySelectionHelper();
Expand Down Expand Up @@ -522,7 +526,10 @@ function MoneyRequestReportTransactionList({
);

const transactionListContent = (
<View style={[listHorizontalPadding, styles.gap2, styles.pb4, styles.mb2]}>
<View
style={[listHorizontalPadding, styles.gap2, styles.pb4, styles.mb2]}
onLayout={onLayout}
>
{shouldShowGroupedTransactions
? groupedTransactions.map((group) => {
const selectionState = groupSelectionState.get(group.groupKey) ?? {
Expand Down Expand Up @@ -641,6 +648,7 @@ function MoneyRequestReportTransactionList({
return (
<>
<SearchMoneyRequestReportEmptyState
onLayout={onLayout}
report={report}
policy={policy}
/>
Expand Down Expand Up @@ -677,6 +685,7 @@ function MoneyRequestReportTransactionList({
contentContainerStyle={{width: minTableWidth}}
onScroll={handleHorizontalScroll}
scrollEventThrottle={16}
onLayout={onLayout}
>
<View style={[styles.flex1]}>
{tableHeaderContent}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, {useCallback, useEffect, useMemo} from 'react';
// to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated.
// eslint-disable-next-line no-restricted-imports
import {Animated, InteractionManager, ScrollView, View} from 'react-native';
import type {LayoutChangeEvent} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import MoneyReportHeader from '@components/MoneyReportHeader';
import MoneyRequestHeader from '@components/MoneyRequestHeader';
Expand Down Expand Up @@ -56,6 +57,9 @@ type MoneyRequestReportViewProps = {

/** The `backTo` route that should be used when clicking back button */
backToRoute: Route | undefined;

/** Callback executed on layout */
onLayout?: (event: LayoutChangeEvent) => void;
};

function goBackFromSearchMoneyRequest() {
Expand Down Expand Up @@ -85,9 +89,12 @@ function goBackFromSearchMoneyRequest() {
Navigation.goBack(ROUTES.SEARCH_ROOT.getRoute({query: buildCannedSearchQuery()}));
}

function InitialLoadingSkeleton({styles}: {styles: ThemeStyles}) {
function InitialLoadingSkeleton({styles, onLayout}: {styles: ThemeStyles; onLayout?: (event: LayoutChangeEvent) => void}) {
return (
<View style={[styles.flex1]}>
<View
style={[styles.flex1]}
onLayout={onLayout}
>
<View style={[styles.appContentHeader, styles.borderBottom]}>
<ReportHeaderSkeletonView onBackButtonPress={() => {}} />
</View>
Expand All @@ -96,7 +103,7 @@ function InitialLoadingSkeleton({styles}: {styles: ThemeStyles}) {
);
}

function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayReportFooter, backToRoute}: MoneyRequestReportViewProps) {
function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayReportFooter, backToRoute, onLayout}: MoneyRequestReportViewProps) {
const styles = useThemeStyles();
const {isOffline} = useNetwork();

Expand Down Expand Up @@ -282,6 +289,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe
newTransactions={newTransactions}
reportActions={reportActions}
hasOlderActions={hasOlderActions}
onLayout={onLayout}
hasNewerActions={hasNewerActions}
showReportActionsLoadingState={isLoadingInitialReportActions && !reportMetadata?.hasOnceLoadedReportActions}
reportPendingAction={reportPendingAction}
Expand All @@ -294,6 +302,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe
hasNewerActions={hasNewerActions}
hasOlderActions={hasOlderActions}
parentReportAction={parentReportAction}
onLayout={onLayout}
transactionThreadReportID={transactionThreadReportID}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {useEffect} from 'react';
import {View} from 'react-native';
import type {LayoutChangeEvent} from 'react-native';
import EmptyStateComponent from '@components/EmptyStateComponent';
import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
Expand All @@ -18,7 +19,7 @@ import type * as OnyxTypes from '@src/types/onyx';

const minModalHeight = 380;

function SearchMoneyRequestReportEmptyState({report, policy}: {report: OnyxTypes.Report; policy?: OnyxTypes.Policy}) {
function SearchMoneyRequestReportEmptyState({report, policy, onLayout}: {report: OnyxTypes.Report; policy?: OnyxTypes.Policy; onLayout?: (event: LayoutChangeEvent) => void}) {
const [userBillingGraceEndPeriodCollection] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`);
const {translate} = useLocalize();
Expand Down Expand Up @@ -80,7 +81,10 @@ function SearchMoneyRequestReportEmptyState({report, policy}: {report: OnyxTypes
}, [report.reportID]);

return (
<View style={styles.flex1}>
<View
style={styles.flex1}
onLayout={onLayout}
>
<EmptyStateComponent
cardStyles={[styles.appBG]}
cardContentStyles={[styles.pb0]}
Expand Down
15 changes: 13 additions & 2 deletions src/components/ReportActionsSkeletonView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import {Dimensions, View} from 'react-native';
import type {LayoutChangeEvent} from 'react-native';
import CONST from '@src/CONST';
import SkeletonViewLines from './SkeletonViewLines';

Expand All @@ -9,9 +10,12 @@ type ReportActionsSkeletonViewProps = {

/** Number of possible visible content items */
possibleVisibleContentItems?: number;

/** Callback executed on layout */
onLayout?: (event: LayoutChangeEvent) => void;
};

function ReportActionsSkeletonView({shouldAnimate = true, possibleVisibleContentItems = 0}: ReportActionsSkeletonViewProps) {
function ReportActionsSkeletonView({shouldAnimate = true, possibleVisibleContentItems = 0, onLayout}: ReportActionsSkeletonViewProps) {
const contentItems = possibleVisibleContentItems || Math.ceil(Dimensions.get('screen').height / CONST.CHAT_SKELETON_VIEW.AVERAGE_ROW_HEIGHT);
const skeletonViewLines: React.ReactNode[] = [];
for (let index = 0; index < contentItems; index++) {
Expand Down Expand Up @@ -45,7 +49,14 @@ function ReportActionsSkeletonView({shouldAnimate = true, possibleVisibleContent
);
}
}
return <View testID="ReportActionsSkeletonView">{skeletonViewLines}</View>;
return (
<View
onLayout={onLayout}
testID="ReportActionsSkeletonView"
>
{skeletonViewLines}
</View>
);
}

export default ReportActionsSkeletonView;
16 changes: 16 additions & 0 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {
} from '@libs/SearchUIUtils';
import {cancelSpan, endSpanWithAttributes, getSpan, startSpan} from '@libs/telemetry/activeSpans';
import markNavigateAfterExpenseCreateEnd from '@libs/telemetry/markNavigateAfterExpenseCreateEnd';
import {cancelSubmitFollowUpActionSpan, endSubmitFollowUpActionSpan, getPendingSubmitFollowUpAction} from '@libs/telemetry/submitFollowUpAction';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import {getOriginalTransactionWithSplitInfo, hasValidModifiedAmount, isOnHold, isTransactionPendingDelete} from '@libs/TransactionUtils';
import Navigation, {navigationRef} from '@navigation/Navigation';
Expand Down Expand Up @@ -1239,6 +1240,10 @@ function Search({
hasHadFirstLayout.current = true;
endSpanWithAttributes(CONST.TELEMETRY.SPAN_NAVIGATE_TO_REPORTS, {[CONST.TELEMETRY.ATTRIBUTE_IS_WARM]: true});
markNavigateAfterExpenseCreateEnd();
const pending = getPendingSubmitFollowUpAction();
if (pending && pending.followUpAction !== CONST.TELEMETRY.SUBMIT_FOLLOW_UP_ACTION.DISMISS_MODAL_AND_OPEN_REPORT) {
endSubmitFollowUpActionSpan(pending.followUpAction);
}
// Reset the ref after the span is ended so future render-time cancelSpan calls are no longer guarded.
spanExistedOnMount.current = false;
handleSelectionListScroll(sortedData, searchListRef.current);
Expand All @@ -1258,6 +1263,10 @@ function Search({
const cancelNavigationSpans = useCallback(() => {
cancelSpan(CONST.TELEMETRY.SPAN_NAVIGATE_TO_REPORTS);
cancelSpan(CONST.TELEMETRY.SPAN_NAVIGATE_AFTER_EXPENSE_CREATE);
// Only cancel submit follow-up action span when Search is the pending action (e.g. we're bailing to error/empty). Otherwise we'd cancel a span intended for dismiss_modal_only or another action.
if (getPendingSubmitFollowUpAction()?.followUpAction === CONST.TELEMETRY.SUBMIT_FOLLOW_UP_ACTION.NAVIGATE_TO_SEARCH) {
cancelSubmitFollowUpActionSpan();
}
spanExistedOnMount.current = false;
}, []);

Expand All @@ -1278,6 +1287,10 @@ function Search({
if (!hasHadFirstLayout.current) {
return;
}
const pending = getPendingSubmitFollowUpAction();
if (pending && pending.followUpAction !== CONST.TELEMETRY.SUBMIT_FOLLOW_UP_ACTION.DISMISS_MODAL_AND_OPEN_REPORT) {
endSubmitFollowUpActionSpan(pending.followUpAction);
}
endSpanWithAttributes(CONST.TELEMETRY.SPAN_NAVIGATE_TO_REPORTS, {
[CONST.TELEMETRY.ATTRIBUTE_IS_WARM]: !shouldShowLoadingState,
});
Expand Down Expand Up @@ -1366,6 +1379,9 @@ function Search({

if (shouldShowChartView && isGroupedItemArray(sortedData)) {
cancelSpan(CONST.TELEMETRY.SPAN_NAVIGATE_AFTER_EXPENSE_CREATE);
if (getPendingSubmitFollowUpAction()?.followUpAction === CONST.TELEMETRY.SUBMIT_FOLLOW_UP_ACTION.NAVIGATE_TO_SEARCH) {
cancelSubmitFollowUpActionSpan();
}
let chartTitle = translate(`search.chartTitles.${validGroupBy}`);
if (savedSearch) {
if (savedSearch.name !== savedSearch.query) {
Expand Down
61 changes: 61 additions & 0 deletions src/hooks/useSubmitToDestinationVisible.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {useFocusEffect} from '@react-navigation/native';
import {useCallback, useRef} from 'react';
import type {LayoutChangeEvent} from 'react-native';
import type {ValueOf} from 'type-fest';
import {endSubmitFollowUpActionSpan, getPendingSubmitFollowUpAction} from '@libs/telemetry/submitFollowUpAction';
import type {SubmitFollowUpAction} from '@libs/telemetry/submitFollowUpAction';
import CONST from '@src/CONST';

type Trigger = ValueOf<typeof CONST.TELEMETRY.SUBMIT_TO_DESTINATION_VISIBLE_TRIGGER>;

/**
* End the submit-to-destination-visible span when the destination becomes visible.
* - trigger FOCUS: ends the span when the screen gains focus (e.g. ReportScreen for dismiss-and-open-report / dismiss-modal-only).
* - trigger LAYOUT: returns a callback to attach to onLayout; ends the span when layout runs (e.g. SearchMoneyRequestReportPage).
* Resets the "already ended" guard on blur so the next visit can end the span again.
*/
function useSubmitToDestinationVisible(
followUpActions: readonly SubmitFollowUpAction[],
reportID: string | undefined,
trigger: typeof CONST.TELEMETRY.SUBMIT_TO_DESTINATION_VISIBLE_TRIGGER.FOCUS,
): void;
function useSubmitToDestinationVisible(
followUpActions: readonly SubmitFollowUpAction[],
reportID: string | undefined,
trigger: typeof CONST.TELEMETRY.SUBMIT_TO_DESTINATION_VISIBLE_TRIGGER.LAYOUT,
): (event?: LayoutChangeEvent) => void;
function useSubmitToDestinationVisible(followUpActions: readonly SubmitFollowUpAction[], reportID: string | undefined, trigger: Trigger): void | ((event?: LayoutChangeEvent) => void) {
const hasEndedRef = useRef(false);

const tryEnd = useCallback(() => {
if (hasEndedRef.current) {
return;
}
const pending = getPendingSubmitFollowUpAction();
if (!pending || !followUpActions.includes(pending.followUpAction)) {
return;
}
if (pending.reportID !== undefined && pending.reportID !== reportID) {
return;
}
hasEndedRef.current = true;
endSubmitFollowUpActionSpan(pending.followUpAction, reportID);
}, [followUpActions, reportID]);

const reset = useCallback(() => {
hasEndedRef.current = false;
}, []);

useFocusEffect(
useCallback(() => {
if (trigger === CONST.TELEMETRY.SUBMIT_TO_DESTINATION_VISIBLE_TRIGGER.FOCUS && reportID) {
tryEnd();
}
return reset;
}, [trigger, reportID, tryEnd, reset]),
);

return trigger === CONST.TELEMETRY.SUBMIT_TO_DESTINATION_VISIBLE_TRIGGER.LAYOUT ? tryEnd : undefined;
}

export default useSubmitToDestinationVisible;
Loading
Loading