diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index d38700efd53d..a93efb8cbff5 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -40,16 +40,16 @@ function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatO * Method used to get an error object with microsecond as the key. * @param error - error key or message to be saved */ -function getMicroSecondOnyxError(error: string | null, isTranslated = false): Errors { - return {[DateUtils.getMicroseconds()]: error && [error, {isTranslated}]}; +function getMicroSecondOnyxError(error: string | null, isTranslated = false, errorKey?: number): Errors { + return {[errorKey ?? DateUtils.getMicroseconds()]: error && [error, {isTranslated}]}; } /** * Method used to get an error object with microsecond as the key and an object as the value. * @param error - error key or message to be saved */ -function getMicroSecondOnyxErrorObject(error: Errors): ErrorFields { - return {[DateUtils.getMicroseconds()]: error}; +function getMicroSecondOnyxErrorObject(error: Errors, errorKey?: number): ErrorFields { + return {[errorKey ?? DateUtils.getMicroseconds()]: error}; } // We can assume that if error is a string, it has already been translated because it is server error diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5632268ef6ca..77954810f462 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -417,10 +417,10 @@ function resetMoneyRequestInfo(id = '') { } /** Helper function to get the receipt error for money requests, or the generic error if there's no receipt */ -function getReceiptError(receipt?: Receipt, filename?: string, isScanRequest = true): Errors | ErrorFields { +function getReceiptError(receipt?: Receipt, filename?: string, isScanRequest = true, errorKey?: number): Errors | ErrorFields { return isEmptyObject(receipt) || !isScanRequest - ? ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage') - : ErrorUtils.getMicroSecondOnyxErrorObject({error: CONST.IOU.RECEIPT_ERROR, source: receipt.source?.toString() ?? '', filename: filename ?? ''}); + ? ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage', false, errorKey) + : ErrorUtils.getMicroSecondOnyxErrorObject({error: CONST.IOU.RECEIPT_ERROR, source: receipt.source?.toString() ?? '', filename: filename ?? ''}, errorKey); } function needsToBeManuallySubmitted(iouReport: OnyxTypes.Report) { @@ -708,6 +708,8 @@ function buildOnyxDataForMoneyRequest( }, ); + const errorKey = DateUtils.getMicroseconds(); + const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -763,7 +765,7 @@ function buildOnyxDataForMoneyRequest( [iouCreatedAction.reportActionID]: { // Disabling this line since transaction.filename can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt?.filename, isScanRequest), + errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt?.filename, isScanRequest, errorKey), }, [iouAction.reportActionID]: { errors: ErrorUtils.getMicroSecondOnyxError(null), @@ -773,7 +775,7 @@ function buildOnyxDataForMoneyRequest( [iouAction.reportActionID]: { // Disabling this line since transaction.filename can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt?.filename, isScanRequest), + errors: getReceiptError(transaction.receipt, transaction.filename || transaction.receipt?.filename, isScanRequest, errorKey), }, }), }, @@ -783,7 +785,7 @@ function buildOnyxDataForMoneyRequest( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport.reportID}`, value: { [transactionThreadCreatedReportAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage', false, errorKey), }, }, }, @@ -3771,6 +3773,8 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor }); } + const errorKey = DateUtils.getMicroseconds(); + failureData.push( { onyxMethod: Onyx.METHOD.MERGE, @@ -3779,7 +3783,9 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor [reportAction.reportActionID]: { ...reportAction, pendingAction: null, - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericDeleteFailureMessage'), + errors: { + [errorKey]: ['iou.error.genericDeleteFailureMessage', {isTranslated: false}], + }, }, }, }, @@ -3801,7 +3807,9 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor [reportPreviewAction?.reportActionID ?? '']: { ...reportPreviewAction, pendingAction: null, - errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericDeleteFailureMessage'), + errors: { + [errorKey]: ['iou.error.genericDeleteFailureMessage', {isTranslated: false}], + }, }, }, }, diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 72d1fe9fe63d..66deb8aca065 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -1,5 +1,4 @@ import Onyx from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; @@ -7,7 +6,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type ReportAction from '@src/types/onyx/ReportAction'; import * as Report from './Report'; -function clearReportActionErrors(reportID: string, reportAction: OnyxEntry) { +type IgnoreDirection = 'parent' | 'child'; + +function clearReportActionErrors(reportID: string, reportAction: ReportAction, keys?: string[]) { const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); if (!reportAction?.reportActionID) { @@ -34,6 +35,20 @@ function clearReportActionErrors(reportID: string, reportAction: OnyxEntry = {}; + + keys.forEach((key) => { + errors[key] = null; + }); + + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${originalReportID}`, { + [reportAction.reportActionID]: { + errors, + }, + }); + return; + } Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${originalReportID}`, { [reportAction.reportActionID]: { errors: null, @@ -41,7 +56,37 @@ function clearReportActionErrors(reportID: string, reportAction: OnyxEntry errorKeys.includes(err)); + + clearAllRelatedReportActionErrors(report.parentReportID, parentReportAction, 'child', parentErrorKeys); + } + + if (reportAction.childReportID && ignore !== 'child') { + const childActions = ReportActionUtils.getAllReportActions(reportAction.childReportID); + Object.values(childActions).forEach((action) => { + const childErrorKeys = Object.keys(action.errors ?? {}).filter((err) => errorKeys.includes(err)); + clearAllRelatedReportActionErrors(reportAction.childReportID ?? '', action, 'parent', childErrorKeys); + }); + } +} + export { // eslint-disable-next-line import/prefer-default-export - clearReportActionErrors, + clearAllRelatedReportActionErrors, }; diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index cdec6945d302..84d5cf5108e9 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -829,7 +829,7 @@ function ReportActionItem({ /> ReportActions.clearReportActionErrors(report.reportID, action)} + onClose={() => ReportActions.clearAllRelatedReportActionErrors(report.reportID, action)} // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing pendingAction={ draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 3298cd51c9f1..33cdefb749ec 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -786,13 +786,12 @@ describe('actions/IOU', () => { .then( () => new Promise((resolve) => { - ReportActions.clearReportActionErrors(iouReportID ?? '', iouAction); - ReportActions.clearReportActionErrors(transactionThreadReport?.reportID ?? '', transactionThreadAction); + ReportActions.clearAllRelatedReportActionErrors(iouReportID ?? '', iouAction); resolve(); }), ) - // Then the reportAction should be removed from Onyx + // Then the reportAction from chat report should be removed from Onyx .then( () => new Promise((resolve) => { @@ -809,6 +808,39 @@ describe('actions/IOU', () => { }), ) + // Then the reportAction from iou report should be removed from Onyx + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, + waitForCollectionCallback: false, + callback: (reportActionsForReport) => { + Onyx.disconnect(connectionID); + iouAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null; + expect(iouAction).toBeFalsy(); + resolve(); + }, + }); + }), + ) + + // Then the reportAction from transaction report should be removed from Onyx + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport?.reportID}`, + waitForCollectionCallback: false, + callback: (reportActionsForReport) => { + Onyx.disconnect(connectionID); + expect(reportActionsForReport).toMatchObject({}); + resolve(); + }, + }); + }), + ) + // Along with the associated transaction .then( () =>