Skip to content

Commit a152afa

Browse files
authored
Merge pull request Expensify#81280 from callstack-internal/feat/derived-value-v3
Report Actions Derived Value
2 parents 12565a6 + 11d8cc0 commit a152afa

38 files changed

+951
-409
lines changed

src/ONYXKEYS.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,7 @@ const ONYXKEYS = {
10121012
REPORT_ATTRIBUTES: 'reportAttributes',
10131013
REPORT_TRANSACTIONS_AND_VIOLATIONS: 'reportTransactionsAndViolations',
10141014
OUTSTANDING_REPORTS_BY_POLICY_ID: 'outstandingReportsByPolicyID',
1015+
VISIBLE_REPORT_ACTIONS: 'visibleReportActions',
10151016
NON_PERSONAL_AND_WORKSPACE_CARD_LIST: 'nonPersonalAndWorkspaceCardList',
10161017
PERSONAL_AND_WORKSPACE_CARD_LIST: 'personalAndWorkspaceCardList',
10171018
CARD_FEED_ERRORS: 'cardFeedErrors',
@@ -1434,6 +1435,7 @@ type OnyxDerivedValuesMapping = {
14341435
[ONYXKEYS.DERIVED.REPORT_ATTRIBUTES]: OnyxTypes.ReportAttributesDerivedValue;
14351436
[ONYXKEYS.DERIVED.REPORT_TRANSACTIONS_AND_VIOLATIONS]: OnyxTypes.ReportTransactionsAndViolationsDerivedValue;
14361437
[ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID]: OnyxTypes.OutstandingReportsByPolicyIDDerivedValue;
1438+
[ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS]: OnyxTypes.VisibleReportActionsDerivedValue;
14371439
[ONYXKEYS.DERIVED.NON_PERSONAL_AND_WORKSPACE_CARD_LIST]: OnyxTypes.NonPersonalAndWorkspaceCardListDerivedValue;
14381440
[ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST]: OnyxTypes.PersonalAndWorkspaceCardListDerivedValue;
14391441
[ONYXKEYS.DERIVED.CARD_FEED_ERRORS]: OnyxTypes.CardFeedErrorsDerivedValue;

src/components/Attachments/AttachmentCarousel/extractAttachments.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import type {OnyxEntry} from 'react-native-onyx';
33
import type {ValueOf} from 'type-fest';
44
import type {Attachment} from '@components/Attachments/types';
55
import {getFileName, splitExtensionFromFileName} from '@libs/fileDownload/FileUtils';
6-
import {getHtmlWithAttachmentID, getReportActionHtml, getReportActionMessage, getSortedReportActions, isMoneyRequestAction, shouldReportActionBeVisible} from '@libs/ReportActionsUtils';
6+
import {getHtmlWithAttachmentID, getReportActionHtml, getReportActionMessage, getSortedReportActions, isMoneyRequestAction, isReportActionVisible} from '@libs/ReportActionsUtils';
77
import {canUserPerformWriteAction} from '@libs/ReportUtils';
88
import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
99
import CONST from '@src/CONST';
10-
import type {Report, ReportAction, ReportActions} from '@src/types/onyx';
10+
import type {Report, ReportAction, ReportActions, VisibleReportActionsDerivedValue} from '@src/types/onyx';
1111
import type {Note} from '@src/types/onyx/Report';
1212

1313
/**
@@ -22,13 +22,15 @@ function extractAttachments(
2222
reportActions,
2323
report,
2424
isReportArchived,
25+
visibleReportActionsData,
2526
}: {
2627
privateNotes?: Record<number, Note>;
2728
accountID?: number;
2829
parentReportAction?: OnyxEntry<ReportAction>;
2930
reportActions?: OnyxEntry<ReportActions>;
3031
report: OnyxEntry<Report>;
3132
isReportArchived: boolean | undefined;
33+
visibleReportActionsData?: VisibleReportActionsDerivedValue;
3234
},
3335
) {
3436
const targetNote = privateNotes?.[Number(accountID)]?.note ?? '';
@@ -115,9 +117,13 @@ function extractAttachments(
115117
return attachments.reverse();
116118
}
117119

120+
const reportID = report?.reportID;
121+
if (!reportID) {
122+
return attachments.reverse();
123+
}
118124
const actions = [...(parentReportAction ? [parentReportAction] : []), ...getSortedReportActions(Object.values(reportActions ?? {}))];
119-
for (const [key, action] of actions.entries()) {
120-
if (!shouldReportActionBeVisible(action, key, canUserPerformAction) || isMoneyRequestAction(action)) {
125+
for (const action of actions) {
126+
if (!isReportActionVisible(action, reportID, canUserPerformAction, visibleReportActionsData) || isMoneyRequestAction(action)) {
121127
continue;
122128
}
123129

src/components/LHNOptionsList/LHNOptionsList.tsx

Lines changed: 26 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
1616
import useLocalize from '@hooks/useLocalize';
1717
import useNetwork from '@hooks/useNetwork';
1818
import useOnyx from '@hooks/useOnyx';
19-
import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses';
2019
import usePrevious from '@hooks/usePrevious';
2120
import useReportAttributes from '@hooks/useReportAttributes';
2221
import useRootNavigationState from '@hooks/useRootNavigationState';
@@ -25,15 +24,14 @@ import useTheme from '@hooks/useTheme';
2524
import useThemeStyles from '@hooks/useThemeStyles';
2625
import getPlatform from '@libs/getPlatform';
2726
import Log from '@libs/Log';
28-
import {getMovedReportID} from '@libs/ModifiedExpenseMessage';
29-
import {getIOUReportIDOfLastAction, getLastMessageTextForReport} from '@libs/OptionsListUtils';
30-
import {findLastReportActions, getOriginalMessage, isInviteOrRemovedAction, isMoneyRequestAction} from '@libs/ReportActionsUtils';
27+
import {getIOUReportIDOfLastAction} from '@libs/OptionsListUtils';
28+
import {getLastVisibleActionIncludingTransactionThread, getOriginalMessage, isActionableTrackExpense, isInviteOrRemovedAction, isMoneyRequestAction} from '@libs/ReportActionsUtils';
3129
import {canUserPerformWriteAction as canUserPerformWriteActionUtil} from '@libs/ReportUtils';
3230
import variables from '@styles/variables';
3331
import CONST from '@src/CONST';
3432
import NAVIGATORS from '@src/NAVIGATORS';
3533
import ONYXKEYS from '@src/ONYXKEYS';
36-
import type {PersonalDetails, Report} from '@src/types/onyx';
34+
import type {Report} from '@src/types/onyx';
3735
import {isEmptyObject} from '@src/types/utils/EmptyObject';
3836
import OptionRowLHNData from './OptionRowLHNData';
3937
import OptionRowRendererComponent from './OptionRowRendererComponent';
@@ -53,7 +51,6 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
5351
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
5452
const reportAttributes = useReportAttributes();
5553
const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS);
56-
const [reportMetadataCollection] = useOnyx(ONYXKEYS.COLLECTION.REPORT_METADATA);
5754
const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
5855
const [policy] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
5956
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
@@ -64,8 +61,8 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
6461
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
6562
const [onboarding] = useOnyx(ONYXKEYS.NVP_ONBOARDING);
6663
const [isFullscreenVisible] = useOnyx(ONYXKEYS.FULLSCREEN_VISIBILITY);
64+
const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS);
6765
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
68-
const {policyForMovingExpensesID} = usePolicyForMovingExpenses();
6966

7067
const theme = useTheme();
7168
const styles = useThemeStyles();
@@ -184,9 +181,6 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
184181
}
185182
const itemInvoiceReceiverPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`];
186183

187-
const iouReportIDOfLastAction = getIOUReportIDOfLastAction(item);
188-
const itemIouReportReportActions = iouReportIDOfLastAction ? reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportIDOfLastAction}`] : undefined;
189-
190184
const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${item?.policyID}`];
191185
const transactionID = isMoneyRequestAction(itemParentReportAction)
192186
? (getOriginalMessage(itemParentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID)
@@ -198,42 +192,29 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
198192

199193
const isReportArchived = !!itemReportNameValuePairs?.private_isArchived;
200194
const canUserPerformWrite = canUserPerformWriteActionUtil(item, isReportArchived);
201-
const {lastVisibleAction: lastReportAction, lastActionForDisplay: lastAction} = findLastReportActions(itemReportActions, canUserPerformWrite);
202195

203-
// Get the transaction for the last report action
204-
const lastReportActionTransactionID = isMoneyRequestAction(lastReportAction)
205-
? (getOriginalMessage(lastReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID)
206-
: CONST.DEFAULT_NUMBER_ID;
196+
const lastAction = getLastVisibleActionIncludingTransactionThread(
197+
reportID,
198+
canUserPerformWrite,
199+
reportActions,
200+
visibleReportActionsData,
201+
itemOneTransactionThreadReport?.reportID,
202+
);
203+
204+
const iouReportIDOfLastAction = getIOUReportIDOfLastAction(item, visibleReportActionsData, lastAction);
205+
const itemIouReportReportActions = iouReportIDOfLastAction ? reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportIDOfLastAction}`] : undefined;
206+
207+
const lastReportActionTransactionID = isMoneyRequestAction(lastAction) ? (getOriginalMessage(lastAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID) : CONST.DEFAULT_NUMBER_ID;
207208
const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`];
208209

209-
// SidebarUtils.getOptionData in OptionRowLHNData does not get re-evaluated when the linked task report changes, so we have the lastMessageTextFromReport evaluation logic here
210-
let lastActorDetails: Partial<PersonalDetails> | null = item?.lastActorAccountID && personalDetails?.[item.lastActorAccountID] ? personalDetails[item.lastActorAccountID] : null;
211-
if (!lastActorDetails && lastReportAction) {
212-
const lastActorDisplayName = lastReportAction?.person?.[0]?.text;
213-
lastActorDetails = lastActorDisplayName
214-
? {
215-
displayName: lastActorDisplayName,
216-
accountID: item?.lastActorAccountID,
217-
}
218-
: null;
210+
// Only override lastMessageTextFromReport when a track expense whisper's transaction has been deleted, to prevent showing stale text.
211+
let lastMessageTextFromReport: string | undefined;
212+
if (isActionableTrackExpense(lastAction)) {
213+
const whisperTransactionID = getOriginalMessage(lastAction)?.transactionID;
214+
if (whisperTransactionID && !transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${whisperTransactionID}`]) {
215+
lastMessageTextFromReport = '';
216+
}
219217
}
220-
const movedFromReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${getMovedReportID(lastReportAction, CONST.REPORT.MOVE_TYPE.FROM)}`];
221-
const movedToReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${getMovedReportID(lastReportAction, CONST.REPORT.MOVE_TYPE.TO)}`];
222-
const chatReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${item?.chatReportID}`];
223-
const itemReportMetadata = reportMetadataCollection?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportID}`];
224-
const lastMessageTextFromReport = getLastMessageTextForReport({
225-
translate,
226-
report: item,
227-
lastActorDetails,
228-
movedFromReport,
229-
movedToReport,
230-
policy: itemPolicy,
231-
isReportArchived: !!itemReportNameValuePairs?.private_isArchived,
232-
policyForMovingExpensesID,
233-
reportMetadata: itemReportMetadata,
234-
chatReport,
235-
reportAttributesDerived: reportAttributes,
236-
});
237218

238219
const shouldShowRBRorGBRTooltip = firstReportIDWithGBRorRBR === reportID;
239220

@@ -289,14 +270,12 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
289270
reports,
290271
reportNameValuePairs,
291272
reportActions,
292-
reportMetadataCollection,
293273
isOffline,
294274
reportAttributes,
295275
policy,
296276
transactions,
297277
draftComments,
298278
personalDetails,
299-
policyForMovingExpensesID,
300279
firstReportIDWithGBRorRBR,
301280
optionMode,
302281
shouldDisableFocusOptions,
@@ -312,6 +291,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
312291
isScreenFocused,
313292
localeCompare,
314293
translate,
294+
visibleReportActionsData,
315295
currentUserAccountID,
316296
],
317297
);
@@ -333,6 +313,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
333313
isOffline,
334314
isScreenFocused,
335315
isReportsSplitNavigatorLast,
316+
visibleReportActionsData,
336317
],
337318
[
338319
reportActions,
@@ -350,6 +331,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
350331
isOffline,
351332
isScreenFocused,
352333
isReportsSplitNavigatorLast,
334+
visibleReportActionsData,
353335
],
354336
);
355337

src/components/LHNOptionsList/OptionRowLHNData.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ function OptionRowLHNData({
5252

5353
const [movedFromReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getMovedReportID(lastAction, CONST.REPORT.MOVE_TYPE.FROM)}`);
5454
const [movedToReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getMovedReportID(lastAction, CONST.REPORT.MOVE_TYPE.TO)}`);
55-
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${fullReport?.chatReportID}`);
5655
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
5756
// Check the report errors equality to avoid re-rendering when there are no changes
5857
const prevReportErrors = usePrevious(reportAttributes?.reportErrors);
@@ -82,7 +81,6 @@ function OptionRowLHNData({
8281
movedFromReport,
8382
movedToReport,
8483
currentUserAccountID,
85-
chatReport,
8684
reportAttributesDerived,
8785
});
8886
if (deepEqual(item, optionItemRef.current)) {
@@ -94,6 +92,7 @@ function OptionRowLHNData({
9492
return item;
9593
// Listen parentReportAction to update title of thread report when parentReportAction changed
9694
// Listen to transaction to update title of transaction report when transaction changed
95+
// Listen to lastAction to update when action is deleted or gets pendingAction
9796
// eslint-disable-next-line react-hooks/exhaustive-deps
9897
}, [
9998
fullReport,
@@ -115,13 +114,14 @@ function OptionRowLHNData({
115114
invoiceReceiverPolicy,
116115
lastMessageTextFromReport,
117116
card,
117+
lastAction,
118+
lastActionReport,
118119
translate,
119120
localeCompare,
120121
isReportArchived,
121122
movedFromReport,
122123
movedToReport,
123124
currentUserAccountID,
124-
chatReport,
125125
reportAttributesDerived,
126126
]);
127127

src/components/LHNOptionsList/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ type OptionRowLHNDataProps = {
123123
viewMode?: OptionMode;
124124

125125
/** The last message text from the report */
126-
lastMessageTextFromReport: string;
126+
lastMessageTextFromReport?: string;
127127

128128
/** A function that is called when an option is selected. Selected option is passed as a param */
129129
onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject<View | null>) => void;

src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ import {
5656
isCurrentActionUnread,
5757
isDeletedParentAction,
5858
isIOUActionMatchingTransactionList,
59-
shouldReportActionBeVisible,
59+
isReportActionVisible,
6060
wasMessageReceivedWhileOffline,
6161
} from '@libs/ReportActionsUtils';
6262
import {canUserPerformWriteAction, chatIncludesChronosWithID, getOriginalReportID, getReportLastVisibleActionCreated, isHarvestCreatedExpenseReport, isUnread} from '@libs/ReportUtils';
@@ -186,6 +186,7 @@ function MoneyRequestReportActionsList({
186186

187187
const isReportArchived = useReportIsArchived(reportID);
188188
const canPerformWriteAction = canUserPerformWriteAction(report, isReportArchived);
189+
const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS);
189190

190191
const {shouldUseNarrowLayout} = useResponsiveLayoutOnWideRHP();
191192

@@ -316,17 +317,29 @@ function MoneyRequestReportActionsList({
316317
const visibleReportActions = useMemo(() => {
317318
const filteredActions = reportActions.filter((reportAction) => {
318319
const isActionVisibleOnMoneyReport = isActionVisibleOnMoneyRequestReport(reportAction, shouldShowHarvestCreatedAction);
320+
if (!isActionVisibleOnMoneyReport) {
321+
return false;
322+
}
319323

320-
return (
321-
isActionVisibleOnMoneyReport &&
322-
(isOffline || isDeletedParentAction(reportAction) || reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || reportAction.errors) &&
323-
shouldReportActionBeVisible(reportAction, reportAction.reportActionID, canPerformWriteAction) &&
324-
isIOUActionMatchingTransactionList(reportAction, reportTransactionIDs)
325-
);
324+
const passesOfflineCheck = isOffline || isDeletedParentAction(reportAction) || reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || reportAction.errors;
325+
if (!passesOfflineCheck) {
326+
return false;
327+
}
328+
329+
const actionReportID = reportAction.reportID ?? reportID;
330+
if (!isReportActionVisible(reportAction, actionReportID, canPerformWriteAction, visibleReportActionsData)) {
331+
return false;
332+
}
333+
334+
if (!isIOUActionMatchingTransactionList(reportAction, reportTransactionIDs)) {
335+
return false;
336+
}
337+
338+
return true;
326339
});
327340

328341
return filteredActions.toReversed();
329-
}, [reportActions, isOffline, canPerformWriteAction, reportTransactionIDs, shouldShowHarvestCreatedAction]);
342+
}, [reportActions, isOffline, canPerformWriteAction, reportTransactionIDs, shouldShowHarvestCreatedAction, visibleReportActionsData, reportID]);
330343

331344
const shouldShowOpenReportLoadingSkeleton = !isOffline && !!showReportActionsLoadingState && visibleReportActions.length === 0;
332345
useEffect(() => {

src/components/ParentNavigationSubtitle.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
1313
import {isFullScreenName} from '@libs/Navigation/helpers/isNavigatorName';
1414
import Navigation from '@libs/Navigation/Navigation';
1515
import type {RightModalNavigatorParamList} from '@libs/Navigation/types';
16-
import {getReportAction, shouldReportActionBeVisible} from '@libs/ReportActionsUtils';
16+
import {getReportAction, isReportActionVisible} from '@libs/ReportActionsUtils';
1717
import {canUserPerformWriteAction as canUserPerformWriteActionReportUtils, isMoneyRequestReport} from '@libs/ReportUtils';
1818
import CONST from '@src/CONST';
1919
import type {ParentNavigationSummaryParams} from '@src/languages/params';
@@ -88,6 +88,7 @@ function ParentNavigationSubtitle({
8888
const {translate} = useLocalize();
8989
const [currentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
9090
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`);
91+
const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS);
9192
const isReportArchived = useReportIsArchived(report?.reportID);
9293
const canUserPerformWriteAction = canUserPerformWriteActionReportUtils(report, isReportArchived);
9394
const isReportInRHP = currentRoute.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT;
@@ -116,7 +117,7 @@ function ParentNavigationSubtitle({
116117

117118
const onPress = () => {
118119
const parentAction = getReportAction(parentReportID, parentReportActionID);
119-
const isVisibleAction = shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? CONST.DEFAULT_NUMBER_ID, canUserPerformWriteAction);
120+
const isVisibleAction = isReportActionVisible(parentAction, parentReportID, canUserPerformWriteAction, visibleReportActionsData);
120121

121122
const focusedNavigatorState = currentFocusedNavigator?.state;
122123
const currentReportIndex = focusedNavigatorState?.index;

0 commit comments

Comments
 (0)