diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 05701c3e321f..1df59508257a 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -269,7 +269,9 @@ function getContinuousReportActionChain(sortedReportActions: ReportAction[], id? } if (index === -1) { - return []; + // if no non-pending action is found, that means all actions on the report are optimistic + // in this case, we'll assume the whole chain of reportActions is continuous and return it in its entirety + return id ? [] : sortedReportActions; } let startIndex = index; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 6adde9e69d20..aa03714394b1 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -234,7 +234,7 @@ function ReportScreen({ const prevReport = usePrevious(report); const prevUserLeavingStatus = usePrevious(userLeavingStatus); - const [isLinkingToMessage, setLinkingToMessage] = useState(!!reportActionIDFromRoute); + const [isLinkingToMessage, setIsLinkingToMessage] = useState(!!reportActionIDFromRoute); const reportActions = useMemo(() => { if (!sortedAllReportActions.length) { return []; @@ -243,9 +243,11 @@ function ReportScreen({ return currentRangeOfReportActions; }, [reportActionIDFromRoute, sortedAllReportActions]); - // Define here because reportActions are recalculated before mount, allowing data to display faster than useEffect can trigger. If we have cached reportActions, they will be shown immediately. We aim to display a loader first, then fetch relevant reportActions, and finally show them. + // Define here because reportActions are recalculated before mount, allowing data to display faster than useEffect can trigger. + // If we have cached reportActions, they will be shown immediately. + // We aim to display a loader first, then fetch relevant reportActions, and finally show them. useLayoutEffect(() => { - setLinkingToMessage(!!reportActionIDFromRoute); + setIsLinkingToMessage(!!reportActionIDFromRoute); }, [route, reportActionIDFromRoute]); const [isBannerVisible, setIsBannerVisible] = useState(true); @@ -261,8 +263,6 @@ function ReportScreen({ const {reportPendingAction, reportErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report); const screenWrapperStyle: ViewStyle[] = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}]; const isEmptyChat = useMemo((): boolean => reportActions.length === 0, [reportActions]); - // There are no reportActions at all to display and we are still in the process of loading the next set of actions. - const isLoadingInitialReportActions = reportActions.length === 0 && !!reportMetadata?.isLoadingInitialReportActions; const isOptimisticDelete = report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; // If there's a non-404 error for the report we should show it instead of blocking the screen @@ -325,16 +325,20 @@ function ReportScreen({ /** * When false the ReportActionsView will completely unmount and we will show a loader until it returns true. */ - const isReportReadyForDisplay = useMemo((): boolean => { + const isCurrentReportLoadedFromOnyx = useMemo((): boolean => { // This is necessary so that when we are retrieving the next report data from Onyx the ReportActionsView will remount completely const isTransitioning = report && report.reportID !== reportIDFromRoute; return reportIDFromRoute !== '' && !!report.reportID && !isTransitioning; }, [report, reportIDFromRoute]); const shouldShowSkeleton = - isLinkingToMessage || !isReportReadyForDisplay || isLoadingInitialReportActions || isLoading || (!!reportActionIDFromRoute && reportMetadata?.isLoadingInitialReportActions); + isLinkingToMessage || + !isCurrentReportLoadedFromOnyx || + (reportActions.length === 0 && !!reportMetadata?.isLoadingInitialReportActions) || + isLoading || + (!!reportActionIDFromRoute && reportMetadata?.isLoadingInitialReportActions); - const shouldShowReportActionList = isReportReadyForDisplay && !isLoading; + const shouldShowReportActionList = isCurrentReportLoadedFromOnyx && !isLoading; const fetchReport = useCallback(() => { Report.openReport(reportIDFromRoute, reportActionIDFromRoute); @@ -544,14 +548,14 @@ function ReportScreen({ // This helps in tracking from the moment 'route' triggers useMemo until isLoadingInitialReportActions becomes true. It prevents blinking when loading reportActions from cache. useEffect(() => { InteractionManager.runAfterInteractions(() => { - setLinkingToMessage(false); + setIsLinkingToMessage(false); }); }, [reportMetadata?.isLoadingInitialReportActions]); - const onLinkPress = () => { + const navigateToEndOfReport = useCallback(() => { Navigation.setParams({reportActionID: ''}); fetchReport(); - }; + }, [fetchReport]); const isLinkedReportActionDeleted = useMemo(() => { if (!reportActionIDFromRoute || !sortedAllReportActions) { @@ -570,7 +574,7 @@ function ReportScreen({ title={translate('notFound.notHere')} shouldShowLink linkKey="notFound.noAccess" - onLinkPress={onLinkPress} + onLinkPress={navigateToEndOfReport} /> ); } @@ -618,7 +622,7 @@ function ReportScreen({ shouldShowCloseButton /> )} - + } - {isReportReadyForDisplay ? ( + {isCurrentReportLoadedFromOnyx ? ( { input.pop(); expect(result).toStrictEqual(expectedResult.reverse()); }); + + it('given an empty input ID and the report only contains pending actions, it will return all actions', () => { + const input: ReportAction[] = [ + // Given these sortedReportActions + { + reportActionID: '1', + previousReportActionID: undefined, + created: '2022-11-13 22:27:01.825', + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + originalMessage: { + html: 'Hello world', + whisperedTo: [], + }, + message: [ + { + html: 'Hello world', + type: 'Action type', + text: 'Action text', + }, + ], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + { + reportActionID: '2', + previousReportActionID: '1', + created: '2022-11-13 22:27:01.825', + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + originalMessage: { + html: 'Hello world', + whisperedTo: [], + }, + message: [ + { + html: 'Hello world', + type: 'Action type', + text: 'Action text', + }, + ], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + { + reportActionID: '3', + previousReportActionID: '2', + created: '2022-11-13 22:27:01.825', + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + originalMessage: { + html: 'Hello world', + whisperedTo: [], + }, + message: [ + { + html: 'Hello world', + type: 'Action type', + text: 'Action text', + }, + ], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + { + reportActionID: '4', + previousReportActionID: '3', + created: '2022-11-13 22:27:01.825', + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + originalMessage: { + html: 'Hello world', + whisperedTo: [], + }, + message: [ + { + html: 'Hello world', + type: 'Action type', + text: 'Action text', + }, + ], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + { + reportActionID: '5', + previousReportActionID: '4', + created: '2022-11-13 22:27:01.825', + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + originalMessage: { + html: 'Hello world', + whisperedTo: [], + }, + message: [ + { + html: 'Hello world', + type: 'Action type', + text: 'Action text', + }, + ], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + { + reportActionID: '6', + previousReportActionID: '5', + created: '2022-11-13 22:27:01.825', + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + originalMessage: { + html: 'Hello world', + whisperedTo: [], + }, + message: [ + { + html: 'Hello world', + type: 'Action type', + text: 'Action text', + }, + ], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + { + reportActionID: '7', + previousReportActionID: '6', + created: '2022-11-13 22:27:01.825', + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + originalMessage: { + html: 'Hello world', + whisperedTo: [], + }, + message: [ + { + html: 'Hello world', + type: 'Action type', + text: 'Action text', + }, + ], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + ]; + + const expectedResult = input; + // Reversing the input array to simulate descending order sorting as per our data structure + const result = ReportActionsUtils.getContinuousReportActionChain(input.reverse(), ''); + expect(result).toStrictEqual(expectedResult.reverse()); + }); }); describe('getLastVisibleAction', () => {