diff --git a/package-lock.json b/package-lock.json index 9cb31a77c5aa..dd907ee5569c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,7 +115,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.35.0", "react-native-nitro-sqlite": "9.6.0", - "react-native-onyx": "3.0.73", + "react-native-onyx": "git+https://github.com/callstack-internal/react-native-onyx.git#98ddd1304d2fd4fa1d37aa853014db59dd346155", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", @@ -34993,9 +34993,9 @@ } }, "node_modules/react-native-onyx": { - "version": "3.0.73", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-3.0.73.tgz", - "integrity": "sha512-0fvL8q7Rx3QBoHJJLB4e2wd0a9/4f//FvTBGKJliU5rkO/05APKZKFxMprSQpBm+O+i3T2R1lwSQbeaj9AbIDA==", + "version": "3.0.79", + "resolved": "git+ssh://git@github.com/callstack-internal/react-native-onyx.git#98ddd1304d2fd4fa1d37aa853014db59dd346155", + "integrity": "sha512-90vcEp5nk9dd2WXdDp5V8tN0lgLviI/sUptkvQVKG+uUYIN2qwJmrqhtikyBEvbfm9SLxjPzvfy0RpToKoNxkw==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", diff --git a/package.json b/package.json index 3fdfbf1b6c9f..e94f26072d0a 100644 --- a/package.json +++ b/package.json @@ -179,7 +179,7 @@ "react-native-localize": "^3.5.4", "react-native-nitro-modules": "0.35.0", "react-native-nitro-sqlite": "9.6.0", - "react-native-onyx": "3.0.73", + "react-native-onyx": "git+https://github.com/callstack-internal/react-native-onyx.git#98ddd1304d2fd4fa1d37aa853014db59dd346155", "react-native-pager-view": "8.0.0", "react-native-pdf": "7.0.2", "react-native-permissions": "^5.4.0", diff --git a/patches/react-native-onyx/details.md b/patches/react-native-onyx/details.md index 8d435bf932d1..839eda61743e 100644 --- a/patches/react-native-onyx/details.md +++ b/patches/react-native-onyx/details.md @@ -1,11 +1,3 @@ # `react-native-onyx` patches -### [react-native-onyx+3.0.73.patch](react-native-onyx+3.0.73.patch) - -- Reason: - - > Reverts [Onyx PR #770 (the subscription-side skip for skippable collection member ids in subscribeToKey)](https://github.com/Expensify/react-native-onyx/pull/770) and the line [PR #779](https://github.com/Expensify/react-native-onyx/pull/779) added to work around [PR #770](https://github.com/Expensify/react-native-onyx/pull/770)'s silent-no-callback contract. - -- Upstream PR/issue: https://github.com/Expensify/react-native-onyx/pull/785 -- E/App issue: https://github.com/Expensify/App/issues/86181 -- PR Introducing Patch: https://github.com/Expensify/App/pull/90764 \ No newline at end of file +_No active patches._ \ No newline at end of file diff --git a/patches/react-native-onyx/react-native-onyx+3.0.73.patch b/patches/react-native-onyx/react-native-onyx+3.0.73.patch deleted file mode 100644 index 81d048105d69..000000000000 --- a/patches/react-native-onyx/react-native-onyx+3.0.73.patch +++ /dev/null @@ -1,67 +0,0 @@ -diff --git a/node_modules/react-native-onyx/dist/OnyxUtils.js b/node_modules/react-native-onyx/dist/OnyxUtils.js -index 87d163f..1971cd2 100644 ---- a/node_modules/react-native-onyx/dist/OnyxUtils.js -+++ b/node_modules/react-native-onyx/dist/OnyxUtils.js -@@ -886,24 +886,6 @@ function subscribeToKey(connectOptions) { - const subscriptionID = lastSubscriptionID++; - callbackToStateMapping[subscriptionID] = mapping; - callbackToStateMapping[subscriptionID].subscriptionID = subscriptionID; -- // If the subscriber is attempting to connect to a collection member whose ID is skippable (e.g. "undefined", "null", etc.) -- // we suppress wiring the subscription fully to avoid unnecessary callback emissions such as for "report_undefined". -- // We still return a valid subscriptionID so callers can disconnect safely. -- try { -- const skippableIDs = getSkippableCollectionMemberIDs(); -- if (skippableIDs.size) { -- const [, collectionMemberID] = OnyxKeys_1.default.splitCollectionMemberKey(mapping.key); -- if (skippableIDs.has(collectionMemberID)) { -- // Clean up the provisional mapping to avoid retaining unused subscribers. -- OnyxCache_1.default.addNullishStorageKey(mapping.key); -- delete callbackToStateMapping[subscriptionID]; -- return subscriptionID; -- } -- } -- } -- catch (e) { -- // Not a collection member key, proceed as usual. -- } - // When keyChanged is called, a key is passed and the method looks through all the Subscribers in callbackToStateMapping for the matching key to get the subscriptionID - // to avoid having to loop through all the Subscribers all the time (even when just one connection belongs to one key), - // We create a mapping from key to lists of subscriptionIDs to access the specific list of subscriptionIDs. -@@ -1440,12 +1422,6 @@ function logKeyChanged(onyxMethod, key, value, hasChanged) { - function logKeyRemoved(onyxMethod, key) { - Logger.logInfo(`${onyxMethod} called for key: ${key} => null passed, so key was removed`); - } --/** -- * Getter - returns the callback to state mapping, useful in test environments. -- */ --function getCallbackToStateMapping() { -- return callbackToStateMapping; --} - /** - * Clear internal variables used in this file, useful in test environments. - */ -@@ -1504,6 +1480,5 @@ const OnyxUtils = { - setWithRetry, - multiSetWithRetry, - setCollectionWithRetry, -- getCallbackToStateMapping, - }; - exports.default = OnyxUtils; -diff --git a/node_modules/react-native-onyx/dist/useOnyx.js b/node_modules/react-native-onyx/dist/useOnyx.js -index 9213cff..2e48c73 100644 ---- a/node_modules/react-native-onyx/dist/useOnyx.js -+++ b/node_modules/react-native-onyx/dist/useOnyx.js -@@ -220,12 +220,8 @@ function useOnyx(key, options, dependencies = []) { - newValueRef.current = null; - sourceValueRef.current = undefined; - resultRef.current = [undefined, { status: (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false ? 'loaded' : 'loading' }]; -+ shouldGetCachedValueRef.current = true; - } -- // Force a cache re-read on every (re)subscription so any side effects from -- // subscribeToKey (e.g. addNullishStorageKey for skippable collection member ids) -- // are reflected in the next getSnapshot. Resetting this flag does not change -- // resultRef by itself, so it doesn't cause an extra mount render. -- shouldGetCachedValueRef.current = true; - hasMountedRef.current = true; - isConnectingRef.current = true; - onStoreChangeFnRef.current = onStoreChange; diff --git a/src/CONST/index.ts b/src/CONST/index.ts index a1be80816c35..100556a69745 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -2061,6 +2061,7 @@ const CONST = { EMOJI_TRIE_BUILD: 'LocaleEmojiTrieBuild', }, SPAN_ONYX_DERIVED_COMPUTE: 'OnyxDerivedCompute', + SPAN_ONYX_UPDATES_APPLY_HTTPS: 'OnyxUpdates.applyHTTPSOnyxUpdates', SPAN_NAVIGATION: { ROOT: 'BootsplashVisibleNavigation', PUSHER_INIT: 'NavigationPusherInit', diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 47df0ee60635..474963002e62 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -4,6 +4,7 @@ import type {Merge} from 'type-fest'; import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import Log from '@libs/Log'; import PusherUtils from '@libs/PusherUtils'; +import {endSpan, startSpan} from '@libs/telemetry/activeSpans'; import {trackExpenseApiError} from '@libs/telemetry/trackExpenseCreationError'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -30,6 +31,23 @@ let airshipEventsPromise = Promise.resolve(); function applyHTTPSOnyxUpdates(request: Request, response: Response, lastUpdateID: number) { Log.info('[OnyxUpdateManager] Applying https update', false, {lastUpdateID}); + + // Perf-test instrumentation: measure local Onyx-update processing time per API response + // (excludes network time). Cold-cache mergeCollection batches dominate this duration during + // OpenApp / ReconnectApp / Search; comparing this span across branches isolates the impact + // of mergeCollectionWithPatches changes (cache-first ordering, multiGet pre-warm). + const spanId = `${CONST.TELEMETRY.SPAN_ONYX_UPDATES_APPLY_HTTPS}:${request?.command ?? 'unknown'}:${lastUpdateID}`; + startSpan(spanId, { + name: CONST.TELEMETRY.SPAN_ONYX_UPDATES_APPLY_HTTPS, + op: 'onyx.updates.apply.https', + attributes: { + command: String(request?.command ?? 'unknown'), + onyx_data_count: Array.isArray(response.onyxData) ? response.onyxData.length : 0, + success_data_count: Array.isArray(request?.successData) ? request.successData.length : 0, + failure_data_count: Array.isArray(request?.failureData) ? request.failureData.length : 0, + }, + }); + // For most requests we can immediately update Onyx. For write requests we queue the updates and apply them after the sequential queue has flushed to prevent a replay effect in // the UI. See https://github.com/Expensify/App/issues/12775 for more info. const updateHandler: (updates: Array>) => Promise = request?.data?.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? queueOnyxUpdates : Onyx.update; @@ -75,6 +93,9 @@ function applyHTTPSOnyxUpdates(request: Request, res .then(() => { Log.info('[OnyxUpdateManager] Done applying HTTPS update', false, {lastUpdateID}); return Promise.resolve(response); + }) + .finally(() => { + endSpan(spanId); }); }