Skip to content

Commit

Permalink
Merge pull request Expensify#34640 from dukenv0307/fix/32618
Browse files Browse the repository at this point in the history
Implement get all ancestor of the thread
  • Loading branch information
marcaaron authored Feb 2, 2024
2 parents 6146600 + 9b9d8aa commit 3a610af
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 48 deletions.
27 changes: 27 additions & 0 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,32 @@ function hasRequestFromCurrentAccount(reportID: string, currentAccountID: number
return reportActions.some((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && action.actorAccountID === currentAccountID);
}

/**
* @private
*/
function isReportActionUnread(reportAction: OnyxEntry<ReportAction>, lastReadTime: string) {
if (!lastReadTime) {
return !isCreatedAction(reportAction);
}

return Boolean(reportAction && lastReadTime && reportAction.created && lastReadTime < reportAction.created);
}

/**
* Check whether the current report action of the report is unread or not
*
*/
function isCurrentActionUnread(report: Report | EmptyObject, reportAction: ReportAction): boolean {
const lastReadTime = report.lastReadTime ?? '';
const sortedReportActions = getSortedReportActions(Object.values(getAllReportActions(report.reportID)));
const currentActionIndex = sortedReportActions.findIndex((action) => action.reportActionID === reportAction.reportActionID);
if (currentActionIndex === -1) {
return false;
}
const prevReportAction = sortedReportActions[currentActionIndex - 1];
return isReportActionUnread(reportAction, lastReadTime) && (!prevReportAction || !isReportActionUnread(prevReportAction, lastReadTime));
}

export {
extractLinksFromMessageHtml,
getAllReportActions,
Expand Down Expand Up @@ -849,6 +875,7 @@ export {
getMemberChangeMessageFragment,
getMemberChangeMessagePlainText,
isReimbursementDeQueuedAction,
isCurrentActionUnread,
};

export type {LastVisibleMessage};
87 changes: 87 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,18 @@ type OnyxDataTaskAssigneeChat = {
optimisticChatCreatedReportAction?: OptimisticCreatedReportAction;
};

type Ancestor = {
report: Report;
reportAction: ReportAction;
shouldDisplayNewMarker: boolean;
shouldHideThreadDividerLine: boolean;
};

type AncestorIDs = {
reportIDs: string[];
reportActionsIDs: string[];
};

let currentUserEmail: string | undefined;
let currentUserAccountID: number | undefined;
let isAnonymousUser = false;
Expand Down Expand Up @@ -4661,6 +4673,78 @@ function shouldDisableThread(reportAction: OnyxEntry<ReportAction>, reportID: st
);
}

function getAllAncestorReportActions(report: Report | null | undefined, shouldHideThreadDividerLine: boolean): Ancestor[] {
if (!report) {
return [];
}
const allAncestors: Ancestor[] = [];
let parentReportID = report.parentReportID;
let parentReportActionID = report.parentReportActionID;

// Store the child of parent report
let currentReport = report;
let currentUnread = shouldHideThreadDividerLine;

while (parentReportID) {
const parentReport = getReport(parentReportID);
const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '0');

if (!parentReportAction || ReportActionsUtils.isTransactionThread(parentReportAction) || !parentReport) {
break;
}

const isParentReportActionUnread = ReportActionsUtils.isCurrentActionUnread(parentReport, parentReportAction);
allAncestors.push({
report: currentReport,
reportAction: parentReportAction,
shouldDisplayNewMarker: isParentReportActionUnread,
// We should hide the thread divider line if the previous ancestor action is unread
shouldHideThreadDividerLine: currentUnread,
});
parentReportID = parentReport?.parentReportID;
parentReportActionID = parentReport?.parentReportActionID;
if (!isEmptyObject(parentReport)) {
currentReport = parentReport;
currentUnread = isParentReportActionUnread;
}
}

return allAncestors.reverse();
}

function getAllAncestorReportActionIDs(report: Report | null | undefined): AncestorIDs {
if (!report) {
return {
reportIDs: [],
reportActionsIDs: [],
};
}

const allAncestorIDs: AncestorIDs = {
reportIDs: [],
reportActionsIDs: [],
};
let parentReportID = report.parentReportID;
let parentReportActionID = report.parentReportActionID;

while (parentReportID) {
const parentReport = getReport(parentReportID);
const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '0');

if (!parentReportAction || ReportActionsUtils.isTransactionThread(parentReportAction) || !parentReport) {
break;
}

allAncestorIDs.reportIDs.push(parentReportID ?? '');
allAncestorIDs.reportActionsIDs.push(parentReportActionID ?? '');

parentReportID = parentReport?.parentReportID;
parentReportActionID = parentReport?.parentReportActionID;
}

return allAncestorIDs;
}

function canBeAutoReimbursed(report: OnyxEntry<Report>, policy: OnyxEntry<Policy> = null): boolean {
if (!policy) {
return false;
Expand Down Expand Up @@ -4858,11 +4942,13 @@ export {
shouldDisableThread,
doesReportBelongToWorkspace,
getChildReportNotificationPreference,
getAllAncestorReportActions,
isReportParticipant,
isValidReport,
isReportFieldOfTypeTitle,
isReportFieldDisabled,
getAvailableReportFields,
getAllAncestorReportActionIDs,
};

export type {
Expand All @@ -4874,4 +4960,5 @@ export type {
OptimisticAddCommentReportAction,
OptimisticCreatedReportAction,
OptimisticClosedReportAction,
Ancestor,
};
2 changes: 1 addition & 1 deletion src/libs/onyxSubscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {OnyxCollectionKey, OnyxKey} from '@src/ONYXKEYS';
* @param mapping Same as for Onyx.connect()
* @return Unsubscribe callback
*/
function onyxSubscribe<TKey extends OnyxKey | OnyxCollectionKey>(mapping: ConnectOptions<TKey>) {
function onyxSubscribe<TKey extends OnyxKey | OnyxCollectionKey | `${OnyxCollectionKey}${string}`>(mapping: ConnectOptions<TKey>) {
const connectionId = Onyx.connect(mapping);
return () => Onyx.disconnect(connectionId);
}
Expand Down
3 changes: 2 additions & 1 deletion src/pages/home/report/ContextMenu/ContextMenuActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ const ContextMenuActions: ContextMenuAction[] = [
shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport, reportID, isPinnedChat, isUnreadChat) =>
type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION || (type === CONST.CONTEXT_MENU_TYPES.REPORT && !isUnreadChat),
onPress: (closePopover, {reportAction, reportID}) => {
Report.markCommentAsUnread(reportID, reportAction?.created);
const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction) ?? '';
Report.markCommentAsUnread(originalReportID, reportAction?.created);
if (closePopover) {
hideContextMenu(true, ReportActionComposeFocusManager.focus);
}
Expand Down
5 changes: 5 additions & 0 deletions src/pages/home/report/ReportActionItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ const propTypes = {

/** All the report actions belonging to the report's parent */
parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),

/** Callback to be called on onPress */
onPress: PropTypes.func,
};

const defaultProps = {
Expand All @@ -132,6 +135,7 @@ const defaultProps = {
shouldHideThreadDividerLine: false,
userWallet: {},
parentReportActions: {},
onPress: undefined,
};

function ReportActionItem(props) {
Expand Down Expand Up @@ -704,6 +708,7 @@ function ReportActionItem(props) {
return (
<PressableWithSecondaryInteraction
ref={popoverAnchorRef}
onPress={props.onPress}
style={[props.action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? styles.pointerEventsNone : styles.pointerEventsAuto]}
onPressIn={() => props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
Expand Down
107 changes: 63 additions & 44 deletions src/pages/home/report/ReportActionItemParentAction.tsx
Original file line number Diff line number Diff line change
@@ -1,80 +1,103 @@
import React from 'react';
import React, {useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import Navigation from '@libs/Navigation/Navigation';
import onyxSubscribe from '@libs/onyxSubscribe';
import * as ReportUtils from '@libs/ReportUtils';
import * as Report from '@userActions/Report';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground';
import ReportActionItem from './ReportActionItem';

type ReportActionItemParentActionOnyxProps = {
/** The report currently being looked at */
/** The current report is displayed */
report: OnyxEntry<OnyxTypes.Report>;

/** The actions from the parent report */
parentReportActions: OnyxEntry<OnyxTypes.ReportActions>;
};

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

/** Flag to display the new marker on top of the comment */
shouldDisplayNewMarker: boolean;

/** Position index of the report parent action in the overall report FlatList view */
index: number;

/** The id of the report */
// eslint-disable-next-line react/no-unused-prop-types
reportID: string;

/** The id of the parent report */
// eslint-disable-next-line react/no-unused-prop-types
parentReportID: string;
};

function ReportActionItemParentAction({report, parentReportActions = {}, index = 0, shouldHideThreadDividerLine = false, shouldDisplayNewMarker}: ReportActionItemParentActionProps) {
function ReportActionItemParentAction({report, index = 0, shouldHideThreadDividerLine = false}: ReportActionItemParentActionProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {isSmallScreenWidth} = useWindowDimensions();
const parentReportAction = parentReportActions?.[`${report?.parentReportActionID ?? ''}`] ?? null;
const ancestorIDs = useRef(ReportUtils.getAllAncestorReportActionIDs(report));
const [allAncestors, setAllAncestors] = useState<ReportUtils.Ancestor[]>([]);

useEffect(() => {
const unsubscribeReports: Array<() => void> = [];
const unsubscribeReportActions: Array<() => void> = [];
ancestorIDs.current.reportIDs.forEach((ancestorReportID) => {
unsubscribeReports.push(
onyxSubscribe({
key: `${ONYXKEYS.COLLECTION.REPORT}${ancestorReportID}`,
callback: () => {
setAllAncestors(ReportUtils.getAllAncestorReportActions(report, shouldHideThreadDividerLine));
},
}),
);
unsubscribeReportActions.push(
onyxSubscribe({
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${ancestorReportID}`,
callback: () => {
setAllAncestors(ReportUtils.getAllAncestorReportActions(report, shouldHideThreadDividerLine));
},
}),
);
});

return () => {
unsubscribeReports.forEach((unsubscribeReport) => unsubscribeReport());
unsubscribeReportActions.forEach((unsubscribeReportAction) => unsubscribeReportAction());
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// In case of transaction threads, we do not want to render the parent report action.
if (ReportActionsUtils.isTransactionThread(parentReportAction)) {
return null;
}
return (
<OfflineWithFeedback
shouldDisableOpacity={Boolean(parentReportAction?.pendingAction ?? false)}
pendingAction={report?.pendingFields?.addWorkspaceRoom ?? report?.pendingFields?.createChat}
errors={report?.errorFields?.addWorkspaceRoom ?? report?.errorFields?.createChat}
errorRowStyles={[styles.ml10, styles.mr2]}
onClose={() => Report.navigateToConciergeChatAndDeleteReport(report?.reportID ?? '0')}
>
<View style={StyleUtils.getReportWelcomeContainerStyle(isSmallScreenWidth)}>
<>
<View style={[StyleUtils.getReportWelcomeContainerStyle(isSmallScreenWidth), styles.justifyContentEnd]}>
<AnimatedEmptyStateBackground />
<View style={[styles.p5, StyleUtils.getReportWelcomeTopMarginStyle(isSmallScreenWidth)]} />
{parentReportAction && (
<ReportActionItem
// @ts-expect-error TODO: Remove the comment after ReportActionItem is migrated to TypeScript.
report={report}
action={parentReportAction}
displayAsGroup={false}
isMostRecentIOUReportAction={false}
shouldDisplayNewMarker={shouldDisplayNewMarker}
index={index}
/>
)}
{allAncestors.map((ancestor) => (
<OfflineWithFeedback
key={ancestor.reportAction.reportActionID}
shouldDisableOpacity={Boolean(ancestor.reportAction?.pendingAction)}
pendingAction={ancestor.report?.pendingFields?.addWorkspaceRoom ?? ancestor.report?.pendingFields?.createChat}
errors={ancestor.report?.errorFields?.addWorkspaceRoom ?? ancestor.report?.errorFields?.createChat}
errorRowStyles={[styles.ml10, styles.mr2]}
onClose={() => Report.navigateToConciergeChatAndDeleteReport(ancestor.report.reportID)}
>
<ReportActionItem
// @ts-expect-error TODO: Remove this once ReportActionItem (https://github.com/Expensify/App/issues/31982) is migrated to TypeScript.
onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID))}
report={ancestor.report}
action={ancestor.reportAction}
displayAsGroup={false}
isMostRecentIOUReportAction={false}
shouldDisplayNewMarker={ancestor.shouldDisplayNewMarker}
index={index}
/>
{!ancestor.shouldHideThreadDividerLine && <View style={[styles.threadDividerLine]} />}
</OfflineWithFeedback>
))}
</View>
{!shouldHideThreadDividerLine && <View style={[styles.threadDividerLine]} />}
</OfflineWithFeedback>
</>
);
}

Expand All @@ -84,8 +107,4 @@ export default withOnyx<ReportActionItemParentActionProps, ReportActionItemParen
report: {
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
},
parentReportActions: {
key: ({parentReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`,
canEvict: false,
},
})(ReportActionItemParentAction);
2 changes: 0 additions & 2 deletions src/pages/home/report/ReportActionsListItemRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@ function ReportActionsListItemRenderer({
<ReportActionItemParentAction
shouldHideThreadDividerLine={shouldDisplayParentAction && shouldHideThreadDividerLine}
reportID={report.reportID}
parentReportID={`${report.parentReportID}`}
shouldDisplayNewMarker={shouldDisplayNewMarker}
index={index}
/>
) : (
Expand Down
Loading

0 comments on commit 3a610af

Please sign in to comment.