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
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ import {useConciergeDraft} from '@pages/inbox/ConciergeDraftContext';
import FloatingMessageCounter from '@pages/inbox/report/FloatingMessageCounter';
import ReportActionIndexContext from '@pages/inbox/report/ReportActionIndexContext';
import ReportActionsListItemRenderer from '@pages/inbox/report/ReportActionsListItemRenderer';
import ReportActionsListTailIndicator from '@pages/inbox/report/ReportActionsListTailIndicator';
import {getUnreadMarkerReportAction} from '@pages/inbox/report/shouldDisplayNewMarkerOnReportAction';
import useReportUnreadMessageScrollTracking from '@pages/inbox/report/useReportUnreadMessageScrollTracking';

Expand Down Expand Up @@ -139,7 +138,7 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)

const {reportActions: unfilteredReportActions, hasNewerActions, hasOlderActions} = usePaginatedReportActions(reportID, route?.params?.reportActionID);
const reportActions = useMemo(() => getFilteredReportActionsForReportView(unfilteredReportActions), [unfilteredReportActions]);
const {draftReportAction, isDraftPendingCompletion} = useConciergeDraft();
const {draftReportAction} = useConciergeDraft();
const draftReportActionID = draftReportAction?.reportActionID;

const allReportTransactions = useReportTransactionsCollection(reportIDFromRoute);
Expand Down Expand Up @@ -224,7 +223,6 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
const listRef = useActionListRef();

const scrollingVerticalBottomOffset = useRef(0);
const tailIndicatorHeightRef = useRef(0);
const readActionSkipped = useRef(false);
const stickToBottomRef = useRef(false);
const stickToBottomTimeoutRef = useRef<NodeJS.Timeout | null>(null);
Expand Down Expand Up @@ -508,10 +506,6 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
resetKey: report?.reportID ?? reportIDFromRoute ?? '',
});

useEffect(() => {
tailIndicatorHeightRef.current = 0;
}, [report?.reportID]);

/**
* Subscribe to read/unread events and update our unreadMarkerTime
*/
Expand Down Expand Up @@ -700,24 +694,6 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
}
};

const handleTailIndicatorLayout = useCallback(
(event: LayoutChangeEvent) => {
const previousHeight = tailIndicatorHeightRef.current;
const nextHeight = event.nativeEvent.layout.height;
tailIndicatorHeightRef.current = nextHeight;

if (previousHeight > 0 || nextHeight <= 0 || linkedReportActionID || !hasNewestReportAction || scrollOffsetRef.current >= CONST.REPORT.ACTIONS.AUTOSCROLL_TO_TOP_THRESHOLD) {
return;
}

setIsFloatingMessageCounterVisible(false);
requestAnimationFrame(() => {
scrollToBottom();
});
},
[hasNewestReportAction, linkedReportActionID, scrollToBottom, scrollOffsetRef, setIsFloatingMessageCounterVisible],
);

/**
* Runs when the FlatList finishes laying out
*/
Expand All @@ -738,18 +714,6 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
return null;
}

const listFooterComponent = (
<View
testID="money-request-report-tail-indicator"
onLayout={handleTailIndicatorLayout}
>
<ReportActionsListTailIndicator
reportID={report.reportID}
isDraftPendingCompletion={isDraftPendingCompletion}
/>
</View>
);

const shouldUseMarkAsDoneCopy = shouldShowMarkAsDone({
policy,
report,
Expand Down Expand Up @@ -814,7 +778,6 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
contentContainerStyle={shouldUseNarrowLayout ? styles.pt4 : styles.pt3}
isLoadingInitialActions={!!showReportActionsLoadingState}
skeletonReasonAttributes={skeletonReasonAttributes}
listFooterComponent={listFooterComponent}
/>
)}
</View>
Expand Down
44 changes: 19 additions & 25 deletions src/components/MoneyRequestReportView/MoneyRequestReportView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan

import Navigation from '@navigation/Navigation';

import {AgentZeroStatusProvider} from '@pages/inbox/AgentZeroStatusContext';
import {ConciergeDraftProvider} from '@pages/inbox/ConciergeDraftContext';
import ReportActionsList from '@pages/inbox/report/ReportActionsList';
import ReportFooter from '@pages/inbox/report/ReportFooter';
import UserTypingEventListener from '@pages/inbox/report/UserTypingEventListener';
Expand Down Expand Up @@ -278,29 +276,25 @@ function MoneyRequestReportView({report, reportLoadingState, shouldDisplayReport
</ScrollView>
</Animated.View>
)}
<AgentZeroStatusProvider reportID={report.reportID}>
<ConciergeDraftProvider reportID={report.reportID}>
<View style={[styles.overflowHidden, styles.justifyContentEnd, styles.flex1]}>
{shouldDisplayMoneyRequestActionsList ? (
<MoneyRequestReportActionsList onLayout={onLayout} />
) : (
<>
<ReportActionsList
reportID={report.reportID}
onLayout={onLayout}
/>
<UserTypingEventListener report={report} />
</>
)}
{shouldDisplayReportFooter ? (
<>
<ReportFooter />
<PortalHost name="suggestions" />
</>
) : null}
</View>
</ConciergeDraftProvider>
</AgentZeroStatusProvider>
<View style={[styles.overflowHidden, styles.justifyContentEnd, styles.flex1]}>
{shouldDisplayMoneyRequestActionsList ? (
<MoneyRequestReportActionsList onLayout={onLayout} />
) : (
<>
<ReportActionsList
reportID={report.reportID}
onLayout={onLayout}
/>
<UserTypingEventListener report={report} />
</>
)}
{shouldDisplayReportFooter ? (
<>
<ReportFooter />
<PortalHost name="suggestions" />
</>
) : null}
</View>
</View>
</OfflineWithFeedback>
</View>
Expand Down
9 changes: 3 additions & 6 deletions src/pages/inbox/AgentZeroStatusContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,8 @@ const AgentZeroStatusStateContext = createContext<AgentZeroStatusState>(defaultS
const AgentZeroStatusActionsContext = createContext<AgentZeroStatusActions>(defaultActions);

/**
* Cheap outer guard — subscribes to scalar report metadata plus this report's processing-indicator
* NVP. For non-AgentZero reports with no server-driven indicator (the common case), returns
* children directly.
* Cheap outer guard — only subscribes to the scalar CONCIERGE_REPORT_ID and the report's chat
* metadata. For non-AgentZero reports (the common case), returns children directly.
*
* AgentZero chats include Concierge DMs, policy #admins rooms, and custom-agent chats (any
* report with a participant whose personalDetails carries `isCustomAgent: true`, stamped
Expand All @@ -73,15 +72,13 @@ function AgentZeroStatusProvider({reportID, children}: React.PropsWithChildren<{
const [agentParticipantAccountID] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: getCustomAgentParticipantAccountID(participantAccountIDs)});
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const [currentUserAccountID] = useOnyx(ONYXKEYS.SESSION, {selector: accountIDSelector});
const [serverAgentIDs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`, {selector: agentZeroProcessingAgentIDsSelector});

const isConciergeChat = reportID === conciergeReportID;
const isAdmin = chatType === CONST.REPORT.CHAT_TYPE.POLICY_ADMINS;
const isCustomAgentChat = agentParticipantAccountID !== undefined;
const hasServerDrivenStatus = (serverAgentIDs?.length ?? 0) > 0;
const otherParticipantCount = currentUserAccountID === undefined ? 0 : (participantAccountIDs ?? []).filter((accountID) => accountID !== currentUserAccountID).length;
const customAgentDMAccountID = isCustomAgentChat && isDMReport && otherParticipantCount === 1 ? agentParticipantAccountID : undefined;
const isAgentZeroChat = isConciergeChat || isAdmin || isCustomAgentChat || hasServerDrivenStatus;
const isAgentZeroChat = isConciergeChat || isAdmin || isCustomAgentChat;

if (!reportID || !isAgentZeroChat) {
return children;
Expand Down
4 changes: 2 additions & 2 deletions src/pages/inbox/report/ReportActionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ import React, {useEffect, useRef, useState} from 'react';
import FloatingMessageCounter from './FloatingMessageCounter';
import ReportActionIndexContext from './ReportActionIndexContext';
import {useReportActionsListActions, useReportActionsListState} from './ReportActionsListContext';
import ReportActionsListHeader from './ReportActionsListHeader';
import ReportActionsListItemRenderer from './ReportActionsListItemRenderer';
import ReportActionsListPaddingView from './ReportActionsListPaddingView';
import ReportActionsListTailIndicator from './ReportActionsListTailIndicator';
import ReportActionsSkeletonGuard from './ReportActionsSkeletonGuard';
import ShowPreviousMessagesButton from './ShowPreviousMessagesButton';
import useFollowActionBadgeTarget from './useFollowActionBadgeTarget';
Expand Down Expand Up @@ -396,7 +396,7 @@ function ReportActionsListContent({reportID, onLayout}: ReportActionsListProps)
const extraData = [shouldUseNarrowLayout ? unreadMarkerReportActionID : undefined, isArchivedNonExpenseReport(report, isReportArchived), draftReportActionID, draftMessageHTML];

const listHeaderComponent = (
<ReportActionsListTailIndicator
<ReportActionsListHeader
reportID={reportID}
isDraftPendingCompletion={isDraftPendingCompletion}
/>
Expand Down
21 changes: 21 additions & 0 deletions src/pages/inbox/report/ReportActionsListHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ConciergeThinkingMessage from '@pages/home/report/ConciergeThinkingMessage';

import React from 'react';

type ReportActionsListHeaderProps = {
/** The ID of the report being displayed */
reportID: string;

/** Whether a Concierge draft is still streaming in — hides the thinking indicator only while the reply is actively revealing, not after it completes */
isDraftPendingCompletion?: boolean;
};

function ReportActionsListHeader({reportID, isDraftPendingCompletion}: ReportActionsListHeaderProps) {
if (isDraftPendingCompletion) {
return null;
}

return <ConciergeThinkingMessage reportID={reportID} />;
}

export default ReportActionsListHeader;
21 changes: 0 additions & 21 deletions src/pages/inbox/report/ReportActionsListTailIndicator.tsx

This file was deleted.

27 changes: 0 additions & 27 deletions tests/ui/MoneyRequestReportActionsListRejectModalTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,6 @@ jest.mock('@components/MoneyRequestReportView/MoneyRequestReportTransactionList'
return ({listFooterComponent}: {listFooterComponent?: React.ReactElement}) => <View testID="MockMoneyRequestReportTransactionList">{listFooterComponent}</View>;
});

jest.mock('@pages/home/report/ConciergeThinkingMessage', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const {View} = require('react-native');
return {
__esModule: true,
default: () => <View testID="ConciergeThinkingMessage" />,
};
});

jest.mock('@components/HoldOrRejectEducationalModal', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const {View} = require('react-native');
Expand Down Expand Up @@ -240,24 +231,6 @@ describe('MoneyRequestReportActionsList - Reject Educational Modal', () => {
});
});

it('renders Concierge thinking indicator in the money request report table path', async () => {
await act(async () => {
await Onyx.multiSet({
[`${ONYXKEYS.COLLECTION.REPORT}${FAKE_REPORT_ID}` as const]: mockReport,
[`${ONYXKEYS.COLLECTION.POLICY}${FAKE_POLICY_ID}` as const]: mockPolicy,
[`${ONYXKEYS.COLLECTION.TRANSACTION}${FAKE_TRANSACTION_ID}` as const]: mockTransaction,
[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${FAKE_REPORT_ID}` as const]: {[mockReportAction.reportActionID]: mockReportAction},
[`${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${FAKE_REPORT_ID}` as const]: {isLoadingInitialReportActions: false, hasOnceLoadedReportActions: true},
[ONYXKEYS.SESSION]: {accountID: FAKE_ACCOUNT_ID, email: FAKE_EMAIL} as Session,
});
});

renderComponent();
await waitForBatchedUpdatesWithAct();

expect(screen.getByTestId('ConciergeThinkingMessage')).toBeTruthy();
});

it('should show reject educational modal when reject option is selected and explanation has NOT been dismissed', async () => {
await act(async () => {
await Onyx.multiSet({
Expand Down
27 changes: 0 additions & 27 deletions tests/ui/MoneyRequestReportViewTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import * as MoneyRequestReportUtils from '@libs/MoneyRequestReportUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';

import {AgentZeroStatusProvider} from '@pages/inbox/AgentZeroStatusContext';
import {ConciergeDraftProvider} from '@pages/inbox/ConciergeDraftContext';
import ReportActionsList from '@pages/inbox/report/ReportActionsList';
import UserTypingEventListener from '@pages/inbox/report/UserTypingEventListener';

Expand Down Expand Up @@ -57,22 +55,6 @@ jest.mock('@components/MoneyRequestHeader', () => jest.fn(() => null));
jest.mock('@components/CollapsibleHeaderOnKeyboard', () => jest.fn(() => null));
jest.mock('@components/ReportActionItem/MoneyRequestReceiptView', () => jest.fn(() => null));
jest.mock('@pages/inbox/report/ReportFooter', () => jest.fn(() => null));
jest.mock('@pages/inbox/AgentZeroStatusContext', () => {
const reactModule = jest.requireActual<typeof React>('react');
return {
AgentZeroStatusProvider: jest.fn(({children}: {children: React.ReactNode}) => reactModule.createElement(reactModule.Fragment, null, children)),
useAgentZeroStatus: jest.fn(() => ({candidateAgentIDs: []})),
useAgentZeroStatusActions: jest.fn(() => ({kickoffWaitingIndicator: jest.fn()})),
};
});
jest.mock('@pages/inbox/ConciergeDraftContext', () => {
const reactModule = jest.requireActual<typeof React>('react');
return {
ConciergeDraftProvider: jest.fn(({children}: {children: React.ReactNode}) => reactModule.createElement(reactModule.Fragment, null, children)),
useConciergeDraft: jest.fn(() => ({draftReportAction: null, hasActiveDraft: false, isDraftPendingCompletion: false})),
useConciergeDraftActions: jest.fn(() => ({clearDraft: jest.fn(), dispatchLocalDraftEvent: jest.fn(), revealDraftFromReportAction: jest.fn()})),
};
});
jest.mock('@components/OfflineWithFeedback', () => {
const reactModule = jest.requireActual<typeof React>('react');
return jest.fn(({children}: {children: React.ReactNode}) => reactModule.createElement(reactModule.Fragment, null, children));
Expand All @@ -86,8 +68,6 @@ const mockUseReportTransactionsCollection = useReportTransactionsCollection as j
const mockMoneyRequestReportActionsList = MoneyRequestReportActionsList as jest.MockedFunction<typeof MoneyRequestReportActionsList>;
const mockReportActionsListBody = ReportActionsList as jest.MockedFunction<typeof ReportActionsList>;
const mockUserTypingEventListener = UserTypingEventListener as jest.MockedFunction<typeof UserTypingEventListener>;
const mockAgentZeroStatusProvider = AgentZeroStatusProvider as jest.MockedFunction<typeof AgentZeroStatusProvider>;
const mockConciergeDraftProvider = ConciergeDraftProvider as jest.MockedFunction<typeof ConciergeDraftProvider>;

const defaultPaginatedReportActionsResult: ReturnType<typeof usePaginatedReportActions> = {
reportActions: [],
Expand Down Expand Up @@ -212,11 +192,4 @@ describe('MoneyRequestReportView', () => {
expect(mockReportActionsListBody).not.toHaveBeenCalled();
expect(mockUserTypingEventListener).not.toHaveBeenCalled();
});

it('wraps the action list and footer in the AgentZero providers for the report', () => {
renderMoneyRequestReportView(jest.fn());

expect(mockAgentZeroStatusProvider.mock.calls.at(-1)?.at(0)).toEqual(expect.objectContaining({reportID: REPORT_ID}));
expect(mockConciergeDraftProvider.mock.calls.at(-1)?.at(0)).toEqual(expect.objectContaining({reportID: REPORT_ID}));
});
});
Loading
Loading