From ac1a94f2897fc23a414022d70a41f8ad2864ec48 Mon Sep 17 00:00:00 2001 From: Uzaifm127 Date: Sat, 21 Feb 2026 13:47:54 +0530 Subject: [PATCH 1/4] fix message and task created offline are not shown in reports page --- src/libs/ReportUtils.ts | 2 + src/libs/actions/Report/index.ts | 24 +++++++++++- src/libs/actions/Task.ts | 66 +++++++++++++++++++++++++++----- 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 56be27480e5b2..7c17491ba2ded 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -726,6 +726,7 @@ type OptimisticTaskReport = SetRequired< Pick< Report, | 'reportID' + | 'created' | 'reportName' | 'description' | 'ownerAccountID' @@ -8545,6 +8546,7 @@ function buildOptimisticTaskReport( return { reportID: generateReportID(), + created: DateUtils.getDBTime(), reportName: getParsedComment(title ?? '', undefined, undefined, [...CONST.TASK_TITLE_DISABLED_RULES]), description: getParsedComment(description ?? '', {}), ownerAccountID, diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index d57d5bb495419..68b40e155555f 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -159,7 +159,7 @@ import { isValidReportIDFromPath, prepareOnboardingOnyxData, } from '@libs/ReportUtils'; -import {getCurrentSearchQueryJSON} from '@libs/SearchQueryUtils'; +import {getCurrentSearchQueryJSON, buildCannedSearchQuery, buildSearchQueryJSON} from '@libs/SearchQueryUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import {getAmount, getCurrency, hasValidModifiedAmount, isOnHold, shouldClearConvertedAmount} from '@libs/TransactionUtils'; import addTrailingForwardSlash from '@libs/UrlUtils'; @@ -723,7 +723,9 @@ function addActions({ parameters.pregeneratedResponse = pregeneratedResponseParams.pregeneratedResponse; } - const optimisticData: Array> = [ + const optimisticData: Array< + OnyxUpdate + > = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, @@ -736,6 +738,24 @@ function addActions({ }, ]; + // We are pushing the optimistic report and report actions into the chat snapshot so that the newly sent message appears immediately in "Reports > Chats" while offline. + const chatSearchQuery = buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.CHAT}); + const chatSearchQueryJSON = buildSearchQueryJSON(chatSearchQuery); + + if (chatSearchQueryJSON) { + // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${chatSearchQueryJSON.hash}`, + value: { + data: { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: optimisticReport, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: optimisticReportActions, + }, + }, + }); + } + optimisticData.push(...getOptimisticDataForAncestors(ancestors, currentTime, CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD)); const successReportActions: OnyxCollection> = {}; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index e00ba5ea1f241..e7aca7dadfeb8 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -26,6 +26,7 @@ import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import type {OnyxData} from '@src/types/onyx/Request'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import {buildCannedSearchQuery, buildSearchQueryJSON} from '@libs/SearchQueryUtils'; import {getMostRecentReportID, navigateToConciergeChatAndDeleteReport, notifyNewAction, optimisticReportLastData} from './Report'; import {setSelfTourViewed} from './Welcome'; @@ -125,26 +126,32 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { hasOutstandingChildTask: assigneeAccountID === currentUserAccountID ? true : parentReport?.hasOutstandingChildTask, }; + const completeOptimisticTaskReport = { + ...optimisticTaskReport, + pendingFields: { + createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + description: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + managerID: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }; + // We're only setting onyx data for the task report here because it's possible for the parent report to not exist yet (if you're assigning a task to someone you haven't chatted with before) // So we don't want to set the parent report data until we've successfully created that chat report // FOR TASK REPORT const optimisticData: Array< OnyxUpdate< - typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.REPORT_METADATA | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE + | typeof ONYXKEYS.COLLECTION.SNAPSHOT > > = [ { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTaskReport.reportID}`, - value: { - ...optimisticTaskReport, - pendingFields: { - createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - description: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - managerID: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - }, - }, + value: completeOptimisticTaskReport, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -160,6 +167,13 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { }, ]; + // We need the personal details of the task creator and assignee so that the "From" and "Assignee" columns wil be rendered in "Reports > Task" while offline. + const personalDetailsList = PersonalDetailsUtils.createPersonalDetailsLookupByAccountID( + PersonalDetailsUtils.getPersonalDetailsByIDs({ + accountIDs: [currentUserAccountID, assigneeAccountID], + }), + ); + // FOR TASK REPORT const successData: Array< OnyxUpdate @@ -239,6 +253,38 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { }, ); + const searchDataTypes = [CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.DATA_TYPES.TASK]; + + // We push the optimistic task data into chat and task snapshot hashes so it appears immediately in "Reports > Chats" and "Reports > Task" while offline. + for (const type of searchDataTypes) { + const searchQuery = buildCannedSearchQuery({type}); + const searchQueryJSON = buildSearchQueryJSON(searchQuery); + + if (searchQueryJSON) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${searchQueryJSON.hash}`, + value: { + // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 + data: { + [`${ONYXKEYS.COLLECTION.REPORT}${optimisticTaskReport.reportID}`]: { + ...completeOptimisticTaskReport, + accountID: currentUserAccountID, + }, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTaskReport.reportID}`]: { + [optimisticTaskCreatedAction.reportActionID]: optimisticTaskCreatedAction as OnyxTypes.ReportAction, + }, + [`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`]: optimisticParentReport, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`]: { + [optimisticAddCommentReport.reportAction.reportActionID]: optimisticAddCommentReport.reportAction as OnyxTypes.ReportAction, + }, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetailsList, + }, + }, + }); + } + } + const shouldUpdateNotificationPreference = !isEmptyObject(parentReport) && ReportUtils.isHiddenForCurrentUser(parentReport); if (shouldUpdateNotificationPreference) { optimisticData.push({ From f53b8e05e8546b602d797ad3b4ef17e732212c34 Mon Sep 17 00:00:00 2001 From: Uzaifm127 Date: Sun, 22 Feb 2026 19:01:26 +0530 Subject: [PATCH 2/4] Fixed the failing jest test and move the snapshot data storing logic in a shared utility --- src/libs/SearchQueryUtils.ts | 23 +++++++++++++++- src/libs/actions/Report/index.ts | 25 +++++++----------- src/libs/actions/Task.ts | 45 ++++++++++++++------------------ tests/actions/ReportTest.ts | 23 +++++++++------- 4 files changed, 64 insertions(+), 52 deletions(-) diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 9e143fbef6aae..7b6c2db483111 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -1,5 +1,6 @@ import cloneDeep from 'lodash/cloneDeep'; -import type {OnyxCollection} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import type {OnyxCollection, OnyxUpdate} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {LocaleContextProps, LocalizedTranslate} from '@components/LocaleContextProvider'; import type { @@ -1628,6 +1629,25 @@ function shouldSkipSuggestedSearchNavigation(queryJSON?: SearchQueryJSON) { return !!queryJSON.rawFilterList || hasKeywordFilter || hasContextFilter || hasInlineKeywordFilter || hasInlineContextFilter || isChatSearch; } +/** + * Builds an optimistic Snapshot update to ensure offline data for Tasks and Chat messages appears in Search. + */ +function buildOptimisticSnapshotData(type: SearchDataTypes, data: Record): OnyxUpdate | undefined { + const searchQuery = buildCannedSearchQuery({type}); + const searchQueryJSON = buildSearchQueryJSON(searchQuery); + if (!searchQueryJSON) { + return; + } + return { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${searchQueryJSON.hash}`, + value: { + // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 + data, + }, + }; +} + export { isSearchDatePreset, isFilterSupported, @@ -1652,4 +1672,5 @@ export { getUserFriendlyValue, getUserFriendlyKey, shouldSkipSuggestedSearchNavigation, + buildOptimisticSnapshotData, }; diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 68b40e155555f..f48835c2e92eb 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -159,7 +159,7 @@ import { isValidReportIDFromPath, prepareOnboardingOnyxData, } from '@libs/ReportUtils'; -import {getCurrentSearchQueryJSON, buildCannedSearchQuery, buildSearchQueryJSON} from '@libs/SearchQueryUtils'; +import {getCurrentSearchQueryJSON, buildOptimisticSnapshotData} from '@libs/SearchQueryUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import {getAmount, getCurrency, hasValidModifiedAmount, isOnHold, shouldClearConvertedAmount} from '@libs/TransactionUtils'; import addTrailingForwardSlash from '@libs/UrlUtils'; @@ -738,22 +738,15 @@ function addActions({ }, ]; - // We are pushing the optimistic report and report actions into the chat snapshot so that the newly sent message appears immediately in "Reports > Chats" while offline. - const chatSearchQuery = buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.CHAT}); - const chatSearchQueryJSON = buildSearchQueryJSON(chatSearchQuery); + const snapshotDataToStore = { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: optimisticReport, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: optimisticReportActions, + }; + const optimisticSnapshotUpdate = buildOptimisticSnapshotData(CONST.SEARCH.DATA_TYPES.CHAT, snapshotDataToStore); - if (chatSearchQueryJSON) { - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${chatSearchQueryJSON.hash}`, - value: { - data: { - [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: optimisticReport, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: optimisticReportActions, - }, - }, - }); + // We are pushing the optimistic report and report actions into the chat snapshot so that the newly sent message appears immediately in "Reports > Chats" while offline. + if (optimisticSnapshotUpdate) { + optimisticData.push(optimisticSnapshotUpdate); } optimisticData.push(...getOptimisticDataForAncestors(ancestors, currentTime, CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD)); diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index e7aca7dadfeb8..2e21a0226636a 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -26,7 +26,7 @@ import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import type {OnyxData} from '@src/types/onyx/Request'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import {buildCannedSearchQuery, buildSearchQueryJSON} from '@libs/SearchQueryUtils'; +import {buildOptimisticSnapshotData} from '@libs/SearchQueryUtils'; import {getMostRecentReportID, navigateToConciergeChatAndDeleteReport, notifyNewAction, optimisticReportLastData} from './Report'; import {setSelfTourViewed} from './Welcome'; @@ -255,33 +255,26 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { const searchDataTypes = [CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.DATA_TYPES.TASK]; + const snapshotDataToStore = { + [`${ONYXKEYS.COLLECTION.REPORT}${optimisticTaskReport.reportID}`]: { + ...completeOptimisticTaskReport, + accountID: currentUserAccountID, + }, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTaskReport.reportID}`]: { + [optimisticTaskCreatedAction.reportActionID]: optimisticTaskCreatedAction as OnyxTypes.ReportAction, + }, + [`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`]: optimisticParentReport, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`]: { + [optimisticAddCommentReport.reportAction.reportActionID]: optimisticAddCommentReport.reportAction as OnyxTypes.ReportAction, + }, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetailsList, + }; + // We push the optimistic task data into chat and task snapshot hashes so it appears immediately in "Reports > Chats" and "Reports > Task" while offline. for (const type of searchDataTypes) { - const searchQuery = buildCannedSearchQuery({type}); - const searchQueryJSON = buildSearchQueryJSON(searchQuery); - - if (searchQueryJSON) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${searchQueryJSON.hash}`, - value: { - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 - data: { - [`${ONYXKEYS.COLLECTION.REPORT}${optimisticTaskReport.reportID}`]: { - ...completeOptimisticTaskReport, - accountID: currentUserAccountID, - }, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTaskReport.reportID}`]: { - [optimisticTaskCreatedAction.reportActionID]: optimisticTaskCreatedAction as OnyxTypes.ReportAction, - }, - [`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`]: optimisticParentReport, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`]: { - [optimisticAddCommentReport.reportAction.reportActionID]: optimisticAddCommentReport.reportAction as OnyxTypes.ReportAction, - }, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetailsList, - }, - }, - }); + const optimisticSnapshotUpdate = buildOptimisticSnapshotData(type, snapshotDataToStore); + if (optimisticSnapshotUpdate) { + optimisticData.push(optimisticSnapshotUpdate); } } diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 0592afed8ece6..50c07165f069e 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -27,6 +27,7 @@ import DateUtils from '@src/libs/DateUtils'; import Log from '@src/libs/Log'; import * as SequentialQueue from '@src/libs/Network/SequentialQueue'; import * as ReportUtils from '@src/libs/ReportUtils'; +import type * as SearchQueryUtilsType from '@src/libs/SearchQueryUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; @@ -73,15 +74,19 @@ jest.mock('@libs/ReportUtils', () => { /* eslint-enable @typescript-eslint/no-unsafe-assignment */ const currentHash = 12345; -jest.mock('@src/libs/SearchQueryUtils', () => ({ - getCurrentSearchQueryJSON: jest.fn().mockImplementation(() => ({ - hash: currentHash, - query: 'test', - type: 'expense', - status: '', - flatFilters: [], - })), -})); +jest.mock('@src/libs/SearchQueryUtils', () => { + const originalSearchQueryModule = jest.requireActual('@src/libs/SearchQueryUtils'); + return { + ...originalSearchQueryModule, + getCurrentSearchQueryJSON: jest.fn().mockImplementation(() => ({ + hash: currentHash, + query: 'test', + type: 'expense', + status: '', + flatFilters: [], + })), + }; +}); const UTC = 'UTC'; jest.mock('@src/libs/actions/Report', () => { From b255b2b161f1ca70dd94ccef6cd3d51c990c1570 Mon Sep 17 00:00:00 2001 From: Uzaifm127 Date: Sun, 22 Feb 2026 21:15:51 +0530 Subject: [PATCH 3/4] applied codex suggestion to clear the snapshot data on API failure --- src/libs/actions/Report/index.ts | 13 ++++++++++++- src/libs/actions/Task.ts | 22 +++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index f48835c2e92eb..5e0993802096e 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -798,7 +798,7 @@ function addActions({ failureReportActions[pregeneratedResponseParams.optimisticConciergeReportActionID] = null; } - const failureData: Array> = [ + const failureData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, @@ -811,6 +811,17 @@ function addActions({ }, ]; + const snapshotDataToClear = { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: null, + }; + const failureSnapshotUpdate = buildOptimisticSnapshotData(CONST.SEARCH.DATA_TYPES.CHAT, snapshotDataToClear); + + // We are clearing out the snapshot data for chat snapshot on API failure so that there won't be any ghost chat message on "Reports > Chats". + if (failureSnapshotUpdate) { + failureData.push(failureSnapshotUpdate); + } + // Update the timezone if it's been 5 minutes from the last time the user added a comment if (DateUtils.canUpdateTimezone() && currentUserAccountID) { const timezone = DateUtils.getCurrentTimezone(timezoneParam); diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 2e21a0226636a..399a8730318ba 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -208,7 +208,13 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { // FOR TASK REPORT const failureData: Array< - OnyxUpdate + OnyxUpdate< + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.PERSONAL_DETAILS_LIST + | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE + | typeof ONYXKEYS.COLLECTION.SNAPSHOT + > > = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -336,6 +342,20 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { }, }); + const snapshotDataToClear = { + [`${ONYXKEYS.COLLECTION.REPORT}${optimisticTaskReport.reportID}`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTaskReport.reportID}`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`]: null, + }; + + // We are clearing out the snapshot data for task snapshot on API failure so that there won't be any ghost task on "Reports > Task". + for (const type of searchDataTypes) { + const failureSnapshotUpdate = buildOptimisticSnapshotData(type, snapshotDataToClear); + if (failureSnapshotUpdate) { + failureData.push(failureSnapshotUpdate); + } + } + const parameters: CreateTaskParams = { parentReportActionID: optimisticAddCommentReport.reportAction.reportActionID, parentReportID, From 51498d98480ea030d85774d3e437fe445ee6c4f9 Mon Sep 17 00:00:00 2001 From: Uzaifm127 Date: Fri, 27 Feb 2026 19:50:38 +0530 Subject: [PATCH 4/4] Added the removal of snapshot data on API failure when dismissing the error message --- src/libs/actions/Report/index.ts | 13 +----------- src/libs/actions/ReportActions.ts | 22 +++++++++++++++++++++ src/libs/actions/Task.ts | 33 +++++-------------------------- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index d0dae2fa90b6a..036429eebb64c 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -160,7 +160,7 @@ import { isValidReportIDFromPath, prepareOnboardingOnyxData, } from '@libs/ReportUtils'; -import {getCurrentSearchQueryJSON, buildOptimisticSnapshotData} from '@libs/SearchQueryUtils'; +import {buildOptimisticSnapshotData, getCurrentSearchQueryJSON} from '@libs/SearchQueryUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import {getAmount, getCurrency, hasValidModifiedAmount, isOnHold, shouldClearConvertedAmount} from '@libs/TransactionUtils'; import addTrailingForwardSlash from '@libs/UrlUtils'; @@ -813,17 +813,6 @@ function addActions({ }, ]; - const snapshotDataToClear = { - [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: null, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: null, - }; - const failureSnapshotUpdate = buildOptimisticSnapshotData(CONST.SEARCH.DATA_TYPES.CHAT, snapshotDataToClear); - - // We are clearing out the snapshot data for chat snapshot on API failure so that there won't be any ghost chat message on "Reports > Chats". - if (failureSnapshotUpdate) { - failureData.push(failureSnapshotUpdate); - } - // Update the timezone if it's been 5 minutes from the last time the user added a comment if (DateUtils.canUpdateTimezone() && currentUserAccountID) { const timezone = DateUtils.getCurrentTimezone(timezoneParam); diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 39529ea608844..acc723e47e79e 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -2,6 +2,7 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import {getLinkedTransactionID, getReportAction, getReportActionMessage, isCreatedTaskReportAction} from '@libs/ReportActionsUtils'; import {getOriginalReportID} from '@libs/ReportUtils'; +import {buildOptimisticSnapshotData} from '@libs/SearchQueryUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; @@ -49,6 +50,27 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, o if (taskReportID && isCreatedTaskReportAction(reportAction)) { deleteReport(taskReportID); } + + // Clear the chat snapshot entry for the failed optimistic action so it disappears from Reports > Chats. + const snapshotDataToClear: Record = { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${originalReportID}`]: { + [reportAction.reportActionID]: null, + }, + }; + if (taskReportID && isCreatedTaskReportAction(reportAction)) { + // If this is a failed optimistic task-create action, also remove the task report snapshot data so it disappears from Reports > Task when the user dismiss the error. + snapshotDataToClear[`${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`] = null; + snapshotDataToClear[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReportID}`] = null; + } + + // Apply the same cleanup to snapshot hashes used by Reports > Chats and Reports > Task. + for (const type of [CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.DATA_TYPES.TASK]) { + const snapshotUpdate = buildOptimisticSnapshotData(type, snapshotDataToClear); + if (!snapshotUpdate) { + continue; + } + Onyx.merge(snapshotUpdate.key, snapshotUpdate.value as OnyxTypes.SearchResults); + } return; } diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 399a8730318ba..7ed7ed7f9d6dd 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -15,6 +15,7 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import {buildOptimisticSnapshotData} from '@libs/SearchQueryUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -26,7 +27,6 @@ import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import type {OnyxData} from '@src/types/onyx/Request'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import {buildOptimisticSnapshotData} from '@libs/SearchQueryUtils'; import {getMostRecentReportID, navigateToConciergeChatAndDeleteReport, notifyNewAction, optimisticReportLastData} from './Report'; import {setSelfTourViewed} from './Welcome'; @@ -206,7 +206,9 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { }, ]; - // FOR TASK REPORT + // We intentionally aren't deleting the optimistic + // task report/action on API failure so the task stays visible until the user dismiss the + // error from chat. const failureData: Array< OnyxUpdate< | typeof ONYXKEYS.COLLECTION.REPORT @@ -215,18 +217,7 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE | typeof ONYXKEYS.COLLECTION.SNAPSHOT > - > = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTaskReport.reportID}`, - value: null, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTaskReport.reportID}`, - value: null, - }, - ]; + > = []; if (assigneeChatReport && assigneeChatReportID) { assigneeChatReportOnyxData = ReportUtils.getTaskAssigneeChatOnyxData( @@ -342,20 +333,6 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { }, }); - const snapshotDataToClear = { - [`${ONYXKEYS.COLLECTION.REPORT}${optimisticTaskReport.reportID}`]: null, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTaskReport.reportID}`]: null, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`]: null, - }; - - // We are clearing out the snapshot data for task snapshot on API failure so that there won't be any ghost task on "Reports > Task". - for (const type of searchDataTypes) { - const failureSnapshotUpdate = buildOptimisticSnapshotData(type, snapshotDataToClear); - if (failureSnapshotUpdate) { - failureData.push(failureSnapshotUpdate); - } - } - const parameters: CreateTaskParams = { parentReportActionID: optimisticAddCommentReport.reportAction.reportActionID, parentReportID,