diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 164869e7127ab..42c84d7fa20d9 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1600,13 +1600,6 @@ function isWorkspaceThread(report: OnyxEntry): boolean { return isThread(report) && isChatReport(report) && CONST.WORKSPACE_ROOM_TYPES.some((type) => chatType === type); } -/** - * Returns true if reportAction is the first chat preview of a Thread - */ -function isThreadFirstChat(reportAction: OnyxInputOrEntry, reportID: string): boolean { - return reportAction?.childReportID?.toString() === reportID; -} - /** * Checks if a report is a child report. */ @@ -1852,10 +1845,9 @@ function canDeleteReportAction(reportAction: OnyxInputOrEntry, rep return false; } - const linkedReport = isThreadFirstChat(reportAction, reportID) ? getReportOrDraftReport(report?.parentReportID) : report; if (isActionOwner) { - if (!isEmptyObject(linkedReport) && (isMoneyRequestReport(linkedReport) || isInvoiceReport(linkedReport))) { - return canDeleteTransaction(linkedReport); + if (!isEmptyObject(report) && (isMoneyRequestReport(report) || isInvoiceReport(report))) { + return canDeleteTransaction(report); } return true; } @@ -7276,10 +7268,9 @@ function getOriginalReportID(reportID: string, reportAction: OnyxInputOrEntry, reportID: string): boolean { +function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry, isThreadReportParentAction: boolean): boolean { const hasReplies = (reportAction?.childVisibleActionCount ?? 0) > 0; - return hasReplies && !!reportAction?.childCommenterCount && !isThreadFirstChat(reportAction, reportID); + return hasReplies && !!reportAction?.childCommenterCount && !isThreadReportParentAction; } /** @@ -7807,7 +7798,7 @@ function getNonHeldAndFullAmount(iouReport: OnyxEntry, policy: OnyxEntry * - The action is a whisper action and it's neither a report preview nor IOU action * - The action is the thread's first chat */ -function shouldDisableThread(reportAction: OnyxInputOrEntry, reportID: string): boolean { +function shouldDisableThread(reportAction: OnyxInputOrEntry, reportID: string, isThreadReportParentAction: boolean): boolean { const isSplitBillAction = ReportActionsUtils.isSplitBillAction(reportAction); const isDeletedAction = ReportActionsUtils.isDeletedAction(reportAction); const isReportPreviewAction = ReportActionsUtils.isReportPreviewAction(reportAction); @@ -7822,7 +7813,7 @@ function shouldDisableThread(reportAction: OnyxInputOrEntry, repor (isDeletedAction && !reportAction?.childVisibleActionCount) || (isArchivedReport && !reportAction?.childVisibleActionCount) || (isWhisperAction && !isReportPreviewAction && !isIOUAction) || - isThreadFirstChat(reportAction, reportID) + isThreadReportParentAction ); } @@ -8678,7 +8669,6 @@ export { isSystemChat, isTaskReport, isThread, - isThreadFirstChat, isTrackExpenseReport, isUnread, isUnreadWithMention, diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx index 2e92669aa8c56..1d7deea43a046 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -76,6 +76,12 @@ type BaseReportActionContextMenuProps = { /** Flag to check if the chat is unread in the LHN. Used for the Mark as Read/Unread action */ isUnreadChat?: boolean; + /** + * Is the action a thread's parent reportAction viewed from within the thread report? + * It will be false if we're viewing the same parent report action from the report it belongs to rather than the thread. + */ + isThreadReportParentAction?: boolean; + /** Content Ref */ contentRef?: RefObject; @@ -101,6 +107,7 @@ function BaseReportActionContextMenu({ isVisible = false, isPinnedChat = false, isUnreadChat = false, + isThreadReportParentAction = false, selection = '', draftMessage = '', reportActionID, @@ -194,6 +201,7 @@ function BaseReportActionContextMenu({ reportID, isPinnedChat, isUnreadChat, + isThreadReportParentAction, isOffline: !!isOffline, isMini, isProduction, @@ -284,6 +292,7 @@ function BaseReportActionContextMenu({ true, () => {}, true, + isThreadReportParentAction, ); }; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index a3dc9cbba25f5..705b85d4c3fc0 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -64,6 +64,7 @@ type ShouldShow = (args: { reportID: string; isPinnedChat: boolean; isUnreadChat: boolean; + isThreadReportParentAction: boolean; isOffline: boolean; isMini: boolean; isProduction: boolean; @@ -178,11 +179,11 @@ const ContextMenuActions: ContextMenuAction[] = [ isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.replyInThread', icon: Expensicons.ChatBubbleReply, - shouldShow: ({type, reportAction, reportID}) => { + shouldShow: ({type, reportAction, reportID, isThreadReportParentAction}) => { if (type !== CONST.CONTEXT_MENU_TYPES.REPORT_ACTION) { return false; } - return !ReportUtils.shouldDisableThread(reportAction, reportID); + return !ReportUtils.shouldDisableThread(reportAction, reportID, isThreadReportParentAction); }, onPress: (closePopover, {reportAction, reportID}) => { const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); @@ -301,16 +302,22 @@ const ContextMenuActions: ContextMenuAction[] = [ isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.joinThread', icon: Expensicons.Bell, - shouldShow: ({reportAction, isArchivedRoom, reportID}) => { + shouldShow: ({reportAction, isArchivedRoom, isThreadReportParentAction}) => { const childReportNotificationPreference = ReportUtils.getChildReportNotificationPreference(reportAction); const isDeletedAction = ReportActionsUtils.isDeletedAction(reportAction); - const shouldDisplayThreadReplies = ReportUtils.shouldDisplayThreadReplies(reportAction, reportID); + const shouldDisplayThreadReplies = ReportUtils.shouldDisplayThreadReplies(reportAction, isThreadReportParentAction); const subscribed = childReportNotificationPreference !== 'hidden'; - const isThreadFirstChat = ReportUtils.isThreadFirstChat(reportAction, reportID); const isWhisperAction = ReportActionsUtils.isWhisperAction(reportAction) || ReportActionsUtils.isActionableTrackExpense(reportAction); const isExpenseReportAction = ReportActionsUtils.isMoneyRequestAction(reportAction) || ReportActionsUtils.isReportPreviewAction(reportAction); const isTaskAction = ReportActionsUtils.isCreatedTaskReportAction(reportAction); - return !subscribed && !isWhisperAction && !isTaskAction && !isExpenseReportAction && !isThreadFirstChat && (shouldDisplayThreadReplies || (!isDeletedAction && !isArchivedRoom)); + return ( + !subscribed && + !isWhisperAction && + !isTaskAction && + !isExpenseReportAction && + !isThreadReportParentAction && + (shouldDisplayThreadReplies || (!isDeletedAction && !isArchivedRoom)) + ); }, onPress: (closePopover, {reportAction, reportID}) => { const childReportNotificationPreference = ReportUtils.getChildReportNotificationPreference(reportAction); diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index f387f09d38803..1d7311ef526fd 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -62,6 +62,7 @@ function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef([]); const [shoudSwitchPositionIfOverflow, setShoudSwitchPositionIfOverflow] = useState(false); @@ -172,6 +173,7 @@ function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef {}, isOverflowMenu = false, + isThreadReportParentActionParam = false, ) => { const {pageX = 0, pageY = 0} = extractPointerEvent(event); contextMenuAnchorRef.current = contextMenuAnchor; @@ -220,6 +222,7 @@ function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef void, isOverflowMenu?: boolean, + isThreadReportParentAction?: boolean, ) => void; type ReportActionContextMenu = { @@ -118,6 +119,7 @@ function showContextMenu( shouldCloseOnTarget = false, setIsEmojiPickerActive = () => {}, isOverflowMenu = false, + isThreadReportParentAction = false, ) { if (!contextMenuRef.current) { return; @@ -142,6 +144,7 @@ function showContextMenu( shouldCloseOnTarget, setIsEmojiPickerActive, isOverflowMenu, + isThreadReportParentAction, ); }; diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 559d635f73fe7..399550069c0a4 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -131,11 +131,15 @@ type ReportActionItemProps = { /** If this is the first visible report action */ isFirstVisibleReportAction: boolean; + /** + * Is the action a thread's parent reportAction viewed from within the thread report? + * It will be false if we're viewing the same parent report action from the report it belongs to rather than the thread. + */ + isThreadReportParentAction?: boolean; + /** IF the thread divider line will be used */ shouldUseThreadDividerLine?: boolean; - hideThreadReplies?: boolean; - /** Whether context menu should be displayed */ shouldDisplayContextMenu?: boolean; }; @@ -154,8 +158,8 @@ function ReportActionItem({ shouldShowSubscriptAvatar = false, onPress = undefined, isFirstVisibleReportAction = false, + isThreadReportParentAction = false, shouldUseThreadDividerLine = false, - hideThreadReplies = false, shouldDisplayContextMenu = true, parentReportActionForTransactionThread, }: ReportActionItemProps) { @@ -356,9 +360,22 @@ function ReportActionItem({ disabledActions, false, setIsEmojiPickerActive as () => void, + undefined, + isThreadReportParentAction, ); }, - [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport], + [ + draftMessage, + action, + reportID, + toggleContextMenuFromActiveReportAction, + originalReportID, + shouldDisplayContextMenu, + disabledActions, + isArchivedRoom, + isChronosReport, + isThreadReportParentAction, + ], ); // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. @@ -784,7 +801,7 @@ function ReportActionItem({ } const numberOfThreadReplies = action.childVisibleActionCount ?? 0; - const shouldDisplayThreadReplies = !hideThreadReplies && ReportUtils.shouldDisplayThreadReplies(action, reportID); + const shouldDisplayThreadReplies = ReportUtils.shouldDisplayThreadReplies(action, isThreadReportParentAction); const oldestFourAccountIDs = action.childOldestFourAccountIDs ?.split(',') @@ -970,6 +987,7 @@ function ReportActionItem({ displayAsGroup={displayAsGroup} disabledActions={disabledActions} isVisible={hovered && draftMessage === undefined && !hasErrors} + isThreadReportParentAction={isThreadReportParentAction} draftMessage={draftMessage} isChronosReport={isChronosReport} checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx index d66b91436b795..3f4ae35717dee 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -159,7 +159,7 @@ function ReportActionItemParentAction({ index={index} isFirstVisibleReportAction={isFirstVisibleReportAction} shouldUseThreadDividerLine={shouldUseThreadDividerLine} - hideThreadReplies + isThreadReportParentAction /> )} diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 4144a63341500..dc752ae73b1c4 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -793,7 +793,7 @@ describe('ReportUtils', () => { it('should disable on thread-disabled actions', () => { const reportAction = ReportUtils.buildOptimisticCreatedReportAction('email1@test.com'); - expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy(); + expect(ReportUtils.shouldDisableThread(reportAction, reportID, false)).toBeTruthy(); }); it('should disable thread on split expense actions', () => { @@ -805,7 +805,7 @@ describe('ReportUtils', () => { [{login: 'email1@test.com'}, {login: 'email2@test.com'}], NumberUtils.rand64(), ) as ReportAction; - expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy(); + expect(ReportUtils.shouldDisableThread(reportAction, reportID, false)).toBeTruthy(); }); it('should disable on deleted and not-thread actions', () => { @@ -821,10 +821,10 @@ describe('ReportUtils', () => { ], childVisibleActionCount: 1, } as ReportAction; - expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeFalsy(); + expect(ReportUtils.shouldDisableThread(reportAction, reportID, false)).toBeFalsy(); reportAction.childVisibleActionCount = 0; - expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy(); + expect(ReportUtils.shouldDisableThread(reportAction, reportID, false)).toBeTruthy(); }); it('should disable on archived reports and not-thread actions', () => { @@ -837,10 +837,10 @@ describe('ReportUtils', () => { const reportAction = { childVisibleActionCount: 1, } as ReportAction; - expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeFalsy(); + expect(ReportUtils.shouldDisableThread(reportAction, reportID, false)).toBeFalsy(); reportAction.childVisibleActionCount = 0; - expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy(); + expect(ReportUtils.shouldDisableThread(reportAction, reportID, false)).toBeTruthy(); }); }); @@ -851,14 +851,14 @@ describe('ReportUtils', () => { whisperedTo: [123456], }, } as ReportAction; - expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy(); + expect(ReportUtils.shouldDisableThread(reportAction, reportID, false)).toBeTruthy(); }); it('should disable on thread first chat', () => { const reportAction = { childReportID: reportID, } as ReportAction; - expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy(); + expect(ReportUtils.shouldDisableThread(reportAction, reportID, true)).toBeTruthy(); }); });