Skip to content

Commit 6f65e2c

Browse files
fix: add search snapshot updates for transaction edits
1 parent 35debc7 commit 6f65e2c

2 files changed

Lines changed: 151 additions & 37 deletions

File tree

src/libs/actions/IOU/UpdateMoneyRequest.ts

Lines changed: 100 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,71 @@ type UpdateMoneyRequestDateParams = {
6868
hash?: number;
6969
};
7070

71+
type SearchSnapshotOnyxData = {
72+
optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.SNAPSHOT>>;
73+
successData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.SNAPSHOT>>;
74+
failureData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.SNAPSHOT>>;
75+
};
76+
77+
type SearchSnapshotUpdateParams = {
78+
hash?: number;
79+
transactionID: string | undefined;
80+
updatedTransaction: OnyxTypes.Transaction;
81+
pendingFields: OnyxTypes.Transaction['pendingFields'];
82+
clearedPendingFields: OnyxTypes.Transaction['pendingFields'];
83+
transaction: OnyxEntry<OnyxTypes.Transaction>;
84+
optimisticViolations?: OnyxEntry<OnyxTypes.TransactionViolations>;
85+
currentTransactionViolations?: OnyxEntry<OnyxTypes.TransactionViolations>;
86+
};
87+
88+
/**
89+
* Builds Onyx writes for the Search snapshot.
90+
* Search result rows render from snapshot data, so TRANSACTION writes alone do not refresh the list.
91+
*/
92+
function getSearchSnapshotUpdates({
93+
hash,
94+
transactionID,
95+
updatedTransaction,
96+
pendingFields,
97+
clearedPendingFields,
98+
transaction,
99+
optimisticViolations,
100+
currentTransactionViolations,
101+
}: SearchSnapshotUpdateParams): SearchSnapshotOnyxData {
102+
if (!hash || !transactionID) {
103+
return {
104+
optimisticData: [],
105+
successData: [],
106+
failureData: [],
107+
};
108+
}
109+
110+
const snapshotKey = `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}` as const;
111+
const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}` as const;
112+
const violationsKey = `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}` as const;
113+
114+
const optimisticSnapshotData: SearchResultDataType = {};
115+
optimisticSnapshotData[transactionKey] = {...updatedTransaction, pendingFields};
116+
if (optimisticViolations !== undefined) {
117+
optimisticSnapshotData[violationsKey] = optimisticViolations;
118+
}
119+
120+
const successSnapshotData: NullishDeep<SearchResultDataType> = {};
121+
successSnapshotData[transactionKey] = {pendingFields: clearedPendingFields};
122+
123+
const failureSnapshotData: NullishDeep<SearchResultDataType> = {};
124+
failureSnapshotData[transactionKey] = {...transaction, pendingFields: clearedPendingFields};
125+
if (currentTransactionViolations !== undefined) {
126+
failureSnapshotData[violationsKey] = currentTransactionViolations;
127+
}
128+
129+
return {
130+
optimisticData: [{onyxMethod: Onyx.METHOD.MERGE, key: snapshotKey, value: {data: optimisticSnapshotData}}],
131+
successData: [{onyxMethod: Onyx.METHOD.MERGE, key: snapshotKey, value: {data: successSnapshotData}}],
132+
failureData: [{onyxMethod: Onyx.METHOD.MERGE, key: snapshotKey, value: {data: failureSnapshotData}}],
133+
};
134+
}
135+
71136
/** Updates the created date of an expense */
72137
function updateMoneyRequestDate({
73138
transactionID,
@@ -1432,27 +1497,21 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
14321497
value: currentTransactionViolations,
14331498
});
14341499

1435-
// Optimistically update the search snapshot so the search list reflects the
1436-
// new values immediately (the snapshot is the exclusive data source for search
1437-
// result rendering and is not automatically updated by the TRANSACTION write above).
14381500
if (hash) {
1439-
const snapshotKey = `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}` as const;
1440-
const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}` as const;
1441-
const violationsKey = `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}` as const;
1442-
1443-
const optimisticSnapshotData: SearchResultDataType = {};
1444-
optimisticSnapshotData[transactionKey] = {...updatedTransaction, pendingFields};
1445-
optimisticSnapshotData[violationsKey] = Array.isArray(violationsOnyxData.value) ? violationsOnyxData.value : [];
1446-
optimisticData.push({onyxMethod: Onyx.METHOD.MERGE, key: snapshotKey, value: {data: optimisticSnapshotData}});
1447-
1448-
const successSnapshotData: NullishDeep<SearchResultDataType> = {};
1449-
successSnapshotData[transactionKey] = {pendingFields: clearedPendingFields};
1450-
successData.push({onyxMethod: Onyx.METHOD.MERGE, key: snapshotKey, value: {data: successSnapshotData}});
1451-
1452-
const failureSnapshotData: NullishDeep<SearchResultDataType> = {};
1453-
failureSnapshotData[transactionKey] = {...transaction, pendingFields: clearedPendingFields};
1454-
failureSnapshotData[violationsKey] = currentTransactionViolations;
1455-
failureData.push({onyxMethod: Onyx.METHOD.MERGE, key: snapshotKey, value: {data: failureSnapshotData}});
1501+
const snapshotUpdates = getSearchSnapshotUpdates({
1502+
hash,
1503+
transactionID,
1504+
updatedTransaction,
1505+
pendingFields,
1506+
clearedPendingFields,
1507+
transaction,
1508+
optimisticViolations: Array.isArray(violationsOnyxData.value) ? violationsOnyxData.value : [],
1509+
currentTransactionViolations,
1510+
});
1511+
1512+
optimisticData.push(...snapshotUpdates.optimisticData);
1513+
successData.push(...snapshotUpdates.successData);
1514+
failureData.push(...snapshotUpdates.failureData);
14561515
}
14571516

14581517
if (
@@ -1563,8 +1622,12 @@ function getUpdateTrackExpenseParams(
15631622
| typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT
15641623
| typeof ONYXKEYS.COLLECTION.SNAPSHOT
15651624
> {
1566-
const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.COLLECTION.TRANSACTION | typeof ONYXKEYS.COLLECTION.REPORT>> = [];
1567-
const successData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.COLLECTION.TRANSACTION>> = [];
1625+
const optimisticData: Array<
1626+
OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.COLLECTION.TRANSACTION | typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.SNAPSHOT>
1627+
> = [];
1628+
const successData: Array<
1629+
OnyxUpdate<typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.COLLECTION.TRANSACTION | typeof ONYXKEYS.COLLECTION.SNAPSHOT>
1630+
> = [];
15681631
const failureData: Array<
15691632
OnyxUpdate<typeof ONYXKEYS.COLLECTION.TRANSACTION | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.SNAPSHOT>
15701633
> = [];
@@ -1675,6 +1738,21 @@ function getUpdateTrackExpenseParams(
16751738
},
16761739
});
16771740

1741+
if (updatedTransaction) {
1742+
const snapshotUpdates = getSearchSnapshotUpdates({
1743+
hash,
1744+
transactionID,
1745+
updatedTransaction,
1746+
pendingFields,
1747+
clearedPendingFields,
1748+
transaction,
1749+
});
1750+
1751+
optimisticData.push(...snapshotUpdates.optimisticData);
1752+
successData.push(...snapshotUpdates.successData);
1753+
failureData.push(...snapshotUpdates.failureData);
1754+
}
1755+
16781756
if (updatedReportAction) {
16791757
optimisticData.push({
16801758
onyxMethod: Onyx.METHOD.MERGE,
@@ -1724,20 +1802,6 @@ function getUpdateTrackExpenseParams(
17241802
value: transactionThread,
17251803
});
17261804

1727-
// Roll back the snapshot copy of the transaction so the search row reverts to its pre-edit state
1728-
if (hash) {
1729-
// @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830
1730-
failureData.push({
1731-
onyxMethod: Onyx.METHOD.MERGE,
1732-
key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
1733-
value: {
1734-
data: {
1735-
[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: transaction ?? null,
1736-
},
1737-
},
1738-
});
1739-
}
1740-
17411805
return {
17421806
params: apiParams,
17431807
onyxData: {optimisticData, successData, failureData},

tests/actions/IOUTest/UpdateMoneyRequestTest.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {format} from 'date-fns';
33
import Onyx from 'react-native-onyx';
44
import type {OnyxEntry} from 'react-native-onyx';
55
import {
6+
getUpdateTrackExpenseParams,
67
updateMoneyRequestAmountAndCurrency,
78
updateMoneyRequestAttendees,
89
updateMoneyRequestBillable,
@@ -18,7 +19,7 @@ import CONST from '@src/CONST';
1819
import IntlStore from '@src/languages/IntlStore';
1920
import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager';
2021
import ONYXKEYS from '@src/ONYXKEYS';
21-
import type {Policy, PolicyTagLists, RecentlyUsedTags, RecentWaypoint, Report} from '@src/types/onyx';
22+
import type {Policy, PolicyTagLists, RecentlyUsedTags, RecentWaypoint, Report, SearchResults} from '@src/types/onyx';
2223
import type {Attendee} from '@src/types/onyx/IOU';
2324
import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails';
2425
import type Transaction from '@src/types/onyx/Transaction';
@@ -507,6 +508,55 @@ describe('actions/IOU/UpdateMoneyRequest', () => {
507508
});
508509
expect(updatedTransaction?.modifiedAmount).toBe('');
509510
});
511+
512+
it('adds search snapshot optimistic and success updates for track expense edits when hash is provided', async () => {
513+
const transactionID = 'track-expense-transaction';
514+
const snapshotHash = 918273645;
515+
const selfDMReport: Report = {
516+
...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM),
517+
reportID: 'self-dm-report',
518+
type: CONST.REPORT.TYPE.CHAT,
519+
};
520+
const transactionThreadReport: Report = {
521+
...createRandomReport(2, undefined),
522+
reportID: 'transaction-thread-report',
523+
type: CONST.REPORT.TYPE.CHAT,
524+
parentReportID: selfDMReport.reportID,
525+
parentReportActionID: 'parent-report-action',
526+
};
527+
const transaction: Transaction = {
528+
...createRandomTransaction(3),
529+
transactionID,
530+
reportID: CONST.REPORT.UNREPORTED_REPORT_ID,
531+
amount: 10000,
532+
currency: CONST.CURRENCY.USD,
533+
};
534+
535+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport);
536+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport.reportID}`, transactionThreadReport);
537+
await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction);
538+
await waitForBatchedUpdates();
539+
540+
const {onyxData} = getUpdateTrackExpenseParams(transactionID, transactionThreadReport.reportID, {amount: 20000}, createRandomPolicy(1), snapshotHash);
541+
const snapshotKey = `${ONYXKEYS.COLLECTION.SNAPSHOT}${snapshotHash}` as const;
542+
const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}` as const;
543+
544+
const optimisticSnapshot = onyxData.optimisticData?.find((update) => update.key === snapshotKey)?.value as OnyxEntry<SearchResults>;
545+
expect(optimisticSnapshot?.data?.[transactionKey]).toMatchObject({
546+
modifiedAmount: -20000,
547+
pendingFields: {amount: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
548+
});
549+
550+
const successSnapshot = onyxData.successData?.find((update) => update.key === snapshotKey)?.value as OnyxEntry<SearchResults>;
551+
expect(successSnapshot?.data?.[transactionKey]).toEqual({pendingFields: {amount: null}});
552+
553+
const failureSnapshot = onyxData.failureData?.find((update) => update.key === snapshotKey)?.value as OnyxEntry<SearchResults>;
554+
expect(failureSnapshot?.data?.[transactionKey]).toMatchObject({
555+
transactionID,
556+
amount: 10000,
557+
pendingFields: {amount: null},
558+
});
559+
});
510560
});
511561

512562
describe('updateMoneyRequestAmountAndCurrency', () => {

0 commit comments

Comments
 (0)