Skip to content

Commit 3a610af

Browse files
authored
Merge pull request #34640 from dukenv0307/fix/32618
Implement get all ancestor of the thread
2 parents 6146600 + 9b9d8aa commit 3a610af

8 files changed

+251
-48
lines changed

src/libs/ReportActionsUtils.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,32 @@ function hasRequestFromCurrentAccount(reportID: string, currentAccountID: number
800800
return reportActions.some((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && action.actorAccountID === currentAccountID);
801801
}
802802

803+
/**
804+
* @private
805+
*/
806+
function isReportActionUnread(reportAction: OnyxEntry<ReportAction>, lastReadTime: string) {
807+
if (!lastReadTime) {
808+
return !isCreatedAction(reportAction);
809+
}
810+
811+
return Boolean(reportAction && lastReadTime && reportAction.created && lastReadTime < reportAction.created);
812+
}
813+
814+
/**
815+
* Check whether the current report action of the report is unread or not
816+
*
817+
*/
818+
function isCurrentActionUnread(report: Report | EmptyObject, reportAction: ReportAction): boolean {
819+
const lastReadTime = report.lastReadTime ?? '';
820+
const sortedReportActions = getSortedReportActions(Object.values(getAllReportActions(report.reportID)));
821+
const currentActionIndex = sortedReportActions.findIndex((action) => action.reportActionID === reportAction.reportActionID);
822+
if (currentActionIndex === -1) {
823+
return false;
824+
}
825+
const prevReportAction = sortedReportActions[currentActionIndex - 1];
826+
return isReportActionUnread(reportAction, lastReadTime) && (!prevReportAction || !isReportActionUnread(prevReportAction, lastReadTime));
827+
}
828+
803829
export {
804830
extractLinksFromMessageHtml,
805831
getAllReportActions,
@@ -849,6 +875,7 @@ export {
849875
getMemberChangeMessageFragment,
850876
getMemberChangeMessagePlainText,
851877
isReimbursementDeQueuedAction,
878+
isCurrentActionUnread,
852879
};
853880

854881
export type {LastVisibleMessage};

src/libs/ReportUtils.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,18 @@ type OnyxDataTaskAssigneeChat = {
402402
optimisticChatCreatedReportAction?: OptimisticCreatedReportAction;
403403
};
404404

405+
type Ancestor = {
406+
report: Report;
407+
reportAction: ReportAction;
408+
shouldDisplayNewMarker: boolean;
409+
shouldHideThreadDividerLine: boolean;
410+
};
411+
412+
type AncestorIDs = {
413+
reportIDs: string[];
414+
reportActionsIDs: string[];
415+
};
416+
405417
let currentUserEmail: string | undefined;
406418
let currentUserAccountID: number | undefined;
407419
let isAnonymousUser = false;
@@ -4661,6 +4673,78 @@ function shouldDisableThread(reportAction: OnyxEntry<ReportAction>, reportID: st
46614673
);
46624674
}
46634675

4676+
function getAllAncestorReportActions(report: Report | null | undefined, shouldHideThreadDividerLine: boolean): Ancestor[] {
4677+
if (!report) {
4678+
return [];
4679+
}
4680+
const allAncestors: Ancestor[] = [];
4681+
let parentReportID = report.parentReportID;
4682+
let parentReportActionID = report.parentReportActionID;
4683+
4684+
// Store the child of parent report
4685+
let currentReport = report;
4686+
let currentUnread = shouldHideThreadDividerLine;
4687+
4688+
while (parentReportID) {
4689+
const parentReport = getReport(parentReportID);
4690+
const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '0');
4691+
4692+
if (!parentReportAction || ReportActionsUtils.isTransactionThread(parentReportAction) || !parentReport) {
4693+
break;
4694+
}
4695+
4696+
const isParentReportActionUnread = ReportActionsUtils.isCurrentActionUnread(parentReport, parentReportAction);
4697+
allAncestors.push({
4698+
report: currentReport,
4699+
reportAction: parentReportAction,
4700+
shouldDisplayNewMarker: isParentReportActionUnread,
4701+
// We should hide the thread divider line if the previous ancestor action is unread
4702+
shouldHideThreadDividerLine: currentUnread,
4703+
});
4704+
parentReportID = parentReport?.parentReportID;
4705+
parentReportActionID = parentReport?.parentReportActionID;
4706+
if (!isEmptyObject(parentReport)) {
4707+
currentReport = parentReport;
4708+
currentUnread = isParentReportActionUnread;
4709+
}
4710+
}
4711+
4712+
return allAncestors.reverse();
4713+
}
4714+
4715+
function getAllAncestorReportActionIDs(report: Report | null | undefined): AncestorIDs {
4716+
if (!report) {
4717+
return {
4718+
reportIDs: [],
4719+
reportActionsIDs: [],
4720+
};
4721+
}
4722+
4723+
const allAncestorIDs: AncestorIDs = {
4724+
reportIDs: [],
4725+
reportActionsIDs: [],
4726+
};
4727+
let parentReportID = report.parentReportID;
4728+
let parentReportActionID = report.parentReportActionID;
4729+
4730+
while (parentReportID) {
4731+
const parentReport = getReport(parentReportID);
4732+
const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '0');
4733+
4734+
if (!parentReportAction || ReportActionsUtils.isTransactionThread(parentReportAction) || !parentReport) {
4735+
break;
4736+
}
4737+
4738+
allAncestorIDs.reportIDs.push(parentReportID ?? '');
4739+
allAncestorIDs.reportActionsIDs.push(parentReportActionID ?? '');
4740+
4741+
parentReportID = parentReport?.parentReportID;
4742+
parentReportActionID = parentReport?.parentReportActionID;
4743+
}
4744+
4745+
return allAncestorIDs;
4746+
}
4747+
46644748
function canBeAutoReimbursed(report: OnyxEntry<Report>, policy: OnyxEntry<Policy> = null): boolean {
46654749
if (!policy) {
46664750
return false;
@@ -4858,11 +4942,13 @@ export {
48584942
shouldDisableThread,
48594943
doesReportBelongToWorkspace,
48604944
getChildReportNotificationPreference,
4945+
getAllAncestorReportActions,
48614946
isReportParticipant,
48624947
isValidReport,
48634948
isReportFieldOfTypeTitle,
48644949
isReportFieldDisabled,
48654950
getAvailableReportFields,
4951+
getAllAncestorReportActionIDs,
48664952
};
48674953

48684954
export type {
@@ -4874,4 +4960,5 @@ export type {
48744960
OptimisticAddCommentReportAction,
48754961
OptimisticCreatedReportAction,
48764962
OptimisticClosedReportAction,
4963+
Ancestor,
48774964
};

src/libs/onyxSubscribe.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {OnyxCollectionKey, OnyxKey} from '@src/ONYXKEYS';
88
* @param mapping Same as for Onyx.connect()
99
* @return Unsubscribe callback
1010
*/
11-
function onyxSubscribe<TKey extends OnyxKey | OnyxCollectionKey>(mapping: ConnectOptions<TKey>) {
11+
function onyxSubscribe<TKey extends OnyxKey | OnyxCollectionKey | `${OnyxCollectionKey}${string}`>(mapping: ConnectOptions<TKey>) {
1212
const connectionId = Onyx.connect(mapping);
1313
return () => Onyx.disconnect(connectionId);
1414
}

src/pages/home/report/ContextMenu/ContextMenuActions.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ const ContextMenuActions: ContextMenuAction[] = [
248248
shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport, reportID, isPinnedChat, isUnreadChat) =>
249249
type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION || (type === CONST.CONTEXT_MENU_TYPES.REPORT && !isUnreadChat),
250250
onPress: (closePopover, {reportAction, reportID}) => {
251-
Report.markCommentAsUnread(reportID, reportAction?.created);
251+
const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction) ?? '';
252+
Report.markCommentAsUnread(originalReportID, reportAction?.created);
252253
if (closePopover) {
253254
hideContextMenu(true, ReportActionComposeFocusManager.focus);
254255
}

src/pages/home/report/ReportActionItem.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ const propTypes = {
121121

122122
/** All the report actions belonging to the report's parent */
123123
parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),
124+
125+
/** Callback to be called on onPress */
126+
onPress: PropTypes.func,
124127
};
125128

126129
const defaultProps = {
@@ -132,6 +135,7 @@ const defaultProps = {
132135
shouldHideThreadDividerLine: false,
133136
userWallet: {},
134137
parentReportActions: {},
138+
onPress: undefined,
135139
};
136140

137141
function ReportActionItem(props) {
@@ -704,6 +708,7 @@ function ReportActionItem(props) {
704708
return (
705709
<PressableWithSecondaryInteraction
706710
ref={popoverAnchorRef}
711+
onPress={props.onPress}
707712
style={[props.action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? styles.pointerEventsNone : styles.pointerEventsAuto]}
708713
onPressIn={() => props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
709714
onPressOut={() => ControlSelection.unblock()}
Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,103 @@
1-
import React from 'react';
1+
import React, {useEffect, useRef, useState} from 'react';
22
import {View} from 'react-native';
33
import {withOnyx} from 'react-native-onyx';
44
import type {OnyxEntry} from 'react-native-onyx';
55
import OfflineWithFeedback from '@components/OfflineWithFeedback';
66
import useStyleUtils from '@hooks/useStyleUtils';
77
import useThemeStyles from '@hooks/useThemeStyles';
88
import useWindowDimensions from '@hooks/useWindowDimensions';
9-
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
9+
import Navigation from '@libs/Navigation/Navigation';
10+
import onyxSubscribe from '@libs/onyxSubscribe';
11+
import * as ReportUtils from '@libs/ReportUtils';
1012
import * as Report from '@userActions/Report';
1113
import ONYXKEYS from '@src/ONYXKEYS';
14+
import ROUTES from '@src/ROUTES';
1215
import type * as OnyxTypes from '@src/types/onyx';
1316
import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground';
1417
import ReportActionItem from './ReportActionItem';
1518

1619
type ReportActionItemParentActionOnyxProps = {
17-
/** The report currently being looked at */
20+
/** The current report is displayed */
1821
report: OnyxEntry<OnyxTypes.Report>;
19-
20-
/** The actions from the parent report */
21-
parentReportActions: OnyxEntry<OnyxTypes.ReportActions>;
2222
};
2323

2424
type ReportActionItemParentActionProps = ReportActionItemParentActionOnyxProps & {
2525
/** Flag to show, hide the thread divider line */
2626
shouldHideThreadDividerLine?: boolean;
2727

28-
/** Flag to display the new marker on top of the comment */
29-
shouldDisplayNewMarker: boolean;
30-
3128
/** Position index of the report parent action in the overall report FlatList view */
3229
index: number;
3330

3431
/** The id of the report */
3532
// eslint-disable-next-line react/no-unused-prop-types
3633
reportID: string;
37-
38-
/** The id of the parent report */
39-
// eslint-disable-next-line react/no-unused-prop-types
40-
parentReportID: string;
4134
};
4235

43-
function ReportActionItemParentAction({report, parentReportActions = {}, index = 0, shouldHideThreadDividerLine = false, shouldDisplayNewMarker}: ReportActionItemParentActionProps) {
36+
function ReportActionItemParentAction({report, index = 0, shouldHideThreadDividerLine = false}: ReportActionItemParentActionProps) {
4437
const styles = useThemeStyles();
4538
const StyleUtils = useStyleUtils();
4639
const {isSmallScreenWidth} = useWindowDimensions();
47-
const parentReportAction = parentReportActions?.[`${report?.parentReportActionID ?? ''}`] ?? null;
40+
const ancestorIDs = useRef(ReportUtils.getAllAncestorReportActionIDs(report));
41+
const [allAncestors, setAllAncestors] = useState<ReportUtils.Ancestor[]>([]);
42+
43+
useEffect(() => {
44+
const unsubscribeReports: Array<() => void> = [];
45+
const unsubscribeReportActions: Array<() => void> = [];
46+
ancestorIDs.current.reportIDs.forEach((ancestorReportID) => {
47+
unsubscribeReports.push(
48+
onyxSubscribe({
49+
key: `${ONYXKEYS.COLLECTION.REPORT}${ancestorReportID}`,
50+
callback: () => {
51+
setAllAncestors(ReportUtils.getAllAncestorReportActions(report, shouldHideThreadDividerLine));
52+
},
53+
}),
54+
);
55+
unsubscribeReportActions.push(
56+
onyxSubscribe({
57+
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${ancestorReportID}`,
58+
callback: () => {
59+
setAllAncestors(ReportUtils.getAllAncestorReportActions(report, shouldHideThreadDividerLine));
60+
},
61+
}),
62+
);
63+
});
64+
65+
return () => {
66+
unsubscribeReports.forEach((unsubscribeReport) => unsubscribeReport());
67+
unsubscribeReportActions.forEach((unsubscribeReportAction) => unsubscribeReportAction());
68+
};
69+
// eslint-disable-next-line react-hooks/exhaustive-deps
70+
}, []);
4871

49-
// In case of transaction threads, we do not want to render the parent report action.
50-
if (ReportActionsUtils.isTransactionThread(parentReportAction)) {
51-
return null;
52-
}
5372
return (
54-
<OfflineWithFeedback
55-
shouldDisableOpacity={Boolean(parentReportAction?.pendingAction ?? false)}
56-
pendingAction={report?.pendingFields?.addWorkspaceRoom ?? report?.pendingFields?.createChat}
57-
errors={report?.errorFields?.addWorkspaceRoom ?? report?.errorFields?.createChat}
58-
errorRowStyles={[styles.ml10, styles.mr2]}
59-
onClose={() => Report.navigateToConciergeChatAndDeleteReport(report?.reportID ?? '0')}
60-
>
61-
<View style={StyleUtils.getReportWelcomeContainerStyle(isSmallScreenWidth)}>
73+
<>
74+
<View style={[StyleUtils.getReportWelcomeContainerStyle(isSmallScreenWidth), styles.justifyContentEnd]}>
6275
<AnimatedEmptyStateBackground />
6376
<View style={[styles.p5, StyleUtils.getReportWelcomeTopMarginStyle(isSmallScreenWidth)]} />
64-
{parentReportAction && (
65-
<ReportActionItem
66-
// @ts-expect-error TODO: Remove the comment after ReportActionItem is migrated to TypeScript.
67-
report={report}
68-
action={parentReportAction}
69-
displayAsGroup={false}
70-
isMostRecentIOUReportAction={false}
71-
shouldDisplayNewMarker={shouldDisplayNewMarker}
72-
index={index}
73-
/>
74-
)}
77+
{allAncestors.map((ancestor) => (
78+
<OfflineWithFeedback
79+
key={ancestor.reportAction.reportActionID}
80+
shouldDisableOpacity={Boolean(ancestor.reportAction?.pendingAction)}
81+
pendingAction={ancestor.report?.pendingFields?.addWorkspaceRoom ?? ancestor.report?.pendingFields?.createChat}
82+
errors={ancestor.report?.errorFields?.addWorkspaceRoom ?? ancestor.report?.errorFields?.createChat}
83+
errorRowStyles={[styles.ml10, styles.mr2]}
84+
onClose={() => Report.navigateToConciergeChatAndDeleteReport(ancestor.report.reportID)}
85+
>
86+
<ReportActionItem
87+
// @ts-expect-error TODO: Remove this once ReportActionItem (https://github.com/Expensify/App/issues/31982) is migrated to TypeScript.
88+
onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID))}
89+
report={ancestor.report}
90+
action={ancestor.reportAction}
91+
displayAsGroup={false}
92+
isMostRecentIOUReportAction={false}
93+
shouldDisplayNewMarker={ancestor.shouldDisplayNewMarker}
94+
index={index}
95+
/>
96+
{!ancestor.shouldHideThreadDividerLine && <View style={[styles.threadDividerLine]} />}
97+
</OfflineWithFeedback>
98+
))}
7599
</View>
76-
{!shouldHideThreadDividerLine && <View style={[styles.threadDividerLine]} />}
77-
</OfflineWithFeedback>
100+
</>
78101
);
79102
}
80103

@@ -84,8 +107,4 @@ export default withOnyx<ReportActionItemParentActionProps, ReportActionItemParen
84107
report: {
85108
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
86109
},
87-
parentReportActions: {
88-
key: ({parentReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`,
89-
canEvict: false,
90-
},
91110
})(ReportActionItemParentAction);

src/pages/home/report/ReportActionsListItemRenderer.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,6 @@ function ReportActionsListItemRenderer({
124124
<ReportActionItemParentAction
125125
shouldHideThreadDividerLine={shouldDisplayParentAction && shouldHideThreadDividerLine}
126126
reportID={report.reportID}
127-
parentReportID={`${report.parentReportID}`}
128-
shouldDisplayNewMarker={shouldDisplayNewMarker}
129127
index={index}
130128
/>
131129
) : (

0 commit comments

Comments
 (0)