Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions src/components/ReportActionItem/MoneyRequestReceiptView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ import type {ValueOf} from 'type-fest';

import {useRoute} from '@react-navigation/native';
import {hasSeenTourSelector} from '@selectors/Onboarding';
import {transactionThreadReportIDSelector} from '@selectors/ReportAction';
import {reportActionsErrorFieldsSelector, transactionThreadReportIDSelector} from '@selectors/ReportAction';
import {Str} from 'expensify-common';
import mapValues from 'lodash/mapValues';
import React, {useEffect, useMemo, useRef, useState} from 'react';
Expand Down Expand Up @@ -160,6 +160,7 @@ function MoneyRequestReceiptView({
const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(parentReportID)}`);
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(parentReport?.parentReportID)}`);
const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`);
const [reportActionsErrorFields] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {selector: reportActionsErrorFieldsSelector});
Comment thread
cretadn22 marked this conversation as resolved.
Resolved
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
Expand Down Expand Up @@ -583,15 +584,29 @@ function MoneyRequestReceiptView({
return;
}
clearError(linkedTransactionID);
clearAllRelatedReportActionErrors({reportID: report.reportID, reportAction: parentReportAction, originalReportID, isOffline, reports: reportsParentHierarchy});
clearAllRelatedReportActionErrors({
reportID: report.reportID,
reportAction: parentReportAction,
originalReportID,
isOffline,
reports: reportsParentHierarchy,
reportActions: reportActionsErrorFields,
});
return;
}
if (!isEmptyObject(transactionAndReportActionErrors)) {
revert(transaction, getLastModifiedExpense(report?.reportID));
}
if (!isEmptyObject(errorsWithoutReportCreation)) {
clearError(transaction.transactionID);
clearAllRelatedReportActionErrors({reportID: report.reportID, reportAction: parentReportAction, originalReportID, isOffline, reports: reportsParentHierarchy});
clearAllRelatedReportActionErrors({
reportID: report.reportID,
reportAction: parentReportAction,
originalReportID,
isOffline,
reports: reportsParentHierarchy,
reportActions: reportActionsErrorFields,
});
}
if (!isEmptyObject(reportCreationError)) {
if (isInNarrowPaneModal) {
Expand Down
19 changes: 11 additions & 8 deletions src/libs/actions/ClearReportActionErrors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {getLinkedTransactionID, getReportAction, getReportActionMessage, isCreatedTaskReportAction, isRejectedAction} from '@libs/ReportActionsUtils';
import {getLinkedTransactionID, getReportActionMessage, isCreatedTaskReportAction, isRejectedAction} from '@libs/ReportActionsUtils';
import {getOriginalReportID} from '@libs/ReportUtils';
import {buildOptimisticSnapshotData} from '@libs/SearchQueryUtils';

Expand Down Expand Up @@ -33,7 +33,7 @@ Onyx.connectWithoutView({
},
});

function clearReportActionErrors(reportAction: ReportAction, originalReportID: string | undefined, keys?: string[]) {
function clearReportActionErrors(reportAction: ReportAction, originalReportID: string | undefined, keys?: string[], reportActions?: OnyxCollection<OnyxTypes.ReportActions>) {
if (!reportAction?.reportActionID) {
return;
}
Expand All @@ -52,7 +52,7 @@ function clearReportActionErrors(reportAction: ReportAction, originalReportID: s
// Also clean up any orphaned optimistic reject actions (REJECTED/REJECTED_TO_SUBMITTER)
// from the same report. When a rejection fails, the comment action gets the error but the
// corresponding reject action remains with pendingAction ADD and no errors.
const siblingActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${originalReportID}`];
const siblingActions = (reportActions ?? allReportActions)?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${originalReportID}`];
if (siblingActions) {
for (const [actionID, siblingAction] of Object.entries(siblingActions)) {
if (
Expand Down Expand Up @@ -125,21 +125,22 @@ type ClearAllRelatedReportActionErrorsParams = {
ignore?: IgnoreDirection;
keys?: string[];
reports?: OnyxCollection<ReportHierarchyInfo>;
reportActions?: OnyxCollection<OnyxTypes.ReportActions>;
};

function clearAllRelatedReportActionErrors({reportID, reportAction, originalReportID, isOffline, ignore, keys, reports}: ClearAllRelatedReportActionErrorsParams) {
function clearAllRelatedReportActionErrors({reportID, reportAction, originalReportID, isOffline, ignore, keys, reports, reportActions}: ClearAllRelatedReportActionErrorsParams) {
const errorKeys = keys ?? Object.keys(reportAction?.errors ?? {});
if (!reportAction || errorKeys.length === 0 || !reportID) {
return;
}

clearReportActionErrors(reportAction, originalReportID, keys);
clearReportActionErrors(reportAction, originalReportID, keys, reportActions);

const report = (reports ?? allReports)?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
if (report?.parentReportID && report?.parentReportActionID && ignore !== 'parent') {
const parentReportAction = getReportAction(report.parentReportID, report.parentReportActionID);
const parentReportActions = (reportActions ?? allReportActions)?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`] ?? {};
const parentReportAction = parentReportActions[report.parentReportActionID];
const parentErrorKeys = Object.keys(parentReportAction?.errors ?? {}).filter((err) => errorKeys.includes(err));
const parentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`] ?? {};
const parentOriginalReportID = getOriginalReportID(report.parentReportID, parentReportAction, parentReportActions, isOffline);

clearAllRelatedReportActionErrors({
Expand All @@ -150,11 +151,12 @@ function clearAllRelatedReportActionErrors({reportID, reportAction, originalRepo
ignore: 'child',
keys: parentErrorKeys,
reports,
reportActions,
});
}

if (reportAction.childReportID && ignore !== 'child') {
const childActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportAction.childReportID}`] ?? {};
const childActions = (reportActions ?? allReportActions)?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportAction.childReportID}`] ?? {};
for (const action of Object.values(childActions)) {
const childErrorKeys = Object.keys(action.errors ?? {}).filter((err) => errorKeys.includes(err));
const childOriginalReportID = getOriginalReportID(reportAction.childReportID, action, childActions, isOffline);
Expand All @@ -166,6 +168,7 @@ function clearAllRelatedReportActionErrors({reportID, reportAction, originalRepo
ignore: 'parent',
keys: childErrorKeys,
reports,
reportActions,
});
}
}
Expand Down
43 changes: 43 additions & 0 deletions src/selectors/ReportAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {ReportAction, ReportActions} from '@src/types/onyx';
import type {VisibleReportActionsDerivedValue} from '@src/types/onyx/DerivedValues';
import mapOnyxCollectionItems from '@src/utils/mapOnyxCollectionItems';

import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';

Expand Down Expand Up @@ -152,6 +153,47 @@ function getReceiptScanFailedIOUActionDataSelector(
const transactionThreadReportIDSelector = (transactionID: string | undefined) => (reportActions: OnyxEntry<ReportActions>) =>
transactionID ? getIOUActionForTransactionID(Object.values(reportActions ?? {}), transactionID)?.childReportID : undefined;

/**
* Keeps only the fields `clearAllRelatedReportActionErrors` reads when walking related report actions.
* Nested fields (`errors`, `message`, `originalMessage`) are copied by reference so equality checks on the
* projected result short-circuit and unrelated action updates (reactions, read receipts, person data, ...)
* neither re-render the subscriber nor pay a deep comparison.
*/
function reportActionErrorFieldsSelector(reportAction: OnyxEntry<ReportAction>): OnyxEntry<ReportAction> {
Comment thread
cretadn22 marked this conversation as resolved.
Resolved
return (
reportAction && {
reportActionID: reportAction.reportActionID,
actionName: reportAction.actionName,
created: reportAction.created,
pendingAction: reportAction.pendingAction,
isOptimisticAction: reportAction.isOptimisticAction,
childReportID: reportAction.childReportID,
errors: reportAction.errors,
message: reportAction.message,
// Copying the deprecated originalMessage field is intentional. The projection must preserve the raw
// data so downstream helpers can resolve it through getOriginalMessage.
// eslint-disable-next-line @typescript-eslint/no-deprecated
originalMessage: reportAction.originalMessage,
}
);
}

function reportActionsErrorFieldsSelector(reportActions: OnyxCollection<ReportActions>): OnyxCollection<ReportActions> {
return mapOnyxCollectionItems(reportActions, (actions) => {
if (!actions) {
return actions;
}
const projectedActions: ReportActions = {};
for (const [reportActionID, reportAction] of Object.entries(actions)) {
const projectedAction = reportActionErrorFieldsSelector(reportAction);
if (projectedAction) {
projectedActions[reportActionID] = projectedAction;
}
}
return projectedActions;
});
}

/** Returns the ID of the author's newest comment in the report that can show the feedback prompt. */
const getLatestFeedbackActionIDByAuthorSelector = (authorAccountID: number | undefined) => (reportActions: OnyxEntry<ReportActions>) =>
authorAccountID ? getLatestConciergeFeedbackActionIDFromReportActions(reportActions, authorAccountID) : undefined;
Expand Down Expand Up @@ -195,6 +237,7 @@ export {
getReportActionByIDSelector,
getReceiptScanFailedIOUActionDataSelector,
reportVisibleActionsSelector,
reportActionsErrorFieldsSelector,
forwardedSinceLastSubmitReportIDsSelector,
transactionThreadReportIDSelector,
getParentReportActionWithoutThreadRepliesSelector,
Expand Down
Loading
Loading