From c53cf6e79e525cd4ba048cb4f78eee284fff59a7 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Tue, 28 Jan 2025 17:37:30 +0100 Subject: [PATCH 01/13] 1# attempt to fix performance of importing onyx state on web --- .../ImportOnyxState/index.native.tsx | 11 ++- src/components/ImportOnyxState/index.tsx | 78 +++++++++++++++++-- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/src/components/ImportOnyxState/index.native.tsx b/src/components/ImportOnyxState/index.native.tsx index bdd805241c55..411e16f87218 100644 --- a/src/components/ImportOnyxState/index.native.tsx +++ b/src/components/ImportOnyxState/index.native.tsx @@ -21,6 +21,7 @@ function readOnyxFile(fileUri: string) { if (!exists) { throw new Error('File does not exist'); } + console.timeEnd('Read Onyx file'); return ReactNativeBlobUtil.fs.readFile(filePath, 'utf8'); }); } @@ -34,6 +35,7 @@ function chunkArray(array: T[], size: number): T[][] { } function applyStateInChunks(state: OnyxValues) { + console.time('applyStateInChunks'); const entries = Object.entries(state); const chunks = chunkArray(entries, CHUNK_SIZE); @@ -42,7 +44,7 @@ function applyStateInChunks(state: OnyxValues) { const partialOnyxState = Object.fromEntries(chunk) as Partial; promise = promise.then(() => Onyx.multiSet(partialOnyxState)); }); - + console.timeEnd('applyStateInChunks'); return promise; } @@ -56,18 +58,25 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { } setIsLoading(true); + console.time('Read Onyx file'); readOnyxFile(file.uri) .then((fileContent: string) => { + console.time('Clean and transform state'); const transformedState = cleanAndTransformState(fileContent); + console.timeEnd('Clean and transform state'); + console.time('current user session copy'); const currentUserSessionCopy = {...session}; + console.timeEnd('current user session copy'); setPreservedUserSession(currentUserSessionCopy); setShouldForceOffline(true); + console.time('Onyx clear and apply in chunks'); Onyx.clear(KEYS_TO_PRESERVE).then(() => { applyStateInChunks(transformedState).then(() => { setIsUsingImportedState(true); Navigation.navigate(ROUTES.HOME); }); }); + console.timeEnd('Onyx clear and apply in chunks'); }) .catch(() => { setIsErrorModalVisible(true); diff --git a/src/components/ImportOnyxState/index.tsx b/src/components/ImportOnyxState/index.tsx index 2f9a2b70b65b..014f75584a17 100644 --- a/src/components/ImportOnyxState/index.tsx +++ b/src/components/ImportOnyxState/index.tsx @@ -1,3 +1,4 @@ +import lodashGet from 'lodash/get'; import React, {useState} from 'react'; import Onyx, {useOnyx} from 'react-native-onyx'; import type {FileObject} from '@components/AttachmentModal'; @@ -15,33 +16,96 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { const [isErrorModalVisible, setIsErrorModalVisible] = useState(false); const [session] = useOnyx(ONYXKEYS.SESSION); + const processStateImport = (transformedState: OnyxValues) => { + console.time('Processing state import'); + + const collectionKeys = new Set(Object.values(ONYXKEYS.COLLECTION || {})); + // Use Map for better performance with large datasets + const collectionsMap = new Map>(); + const regularState: Partial = {}; + + console.time('Grouping collections'); + Object.entries(transformedState).forEach(([key, value]) => { + const baseKey = key.split('_').at(0); + const collectionKey = `${baseKey}_`; + + if (collectionKeys.has(collectionKey)) { + if (!collectionsMap.has(collectionKey)) { + collectionsMap.set(collectionKey, {}); + } + // Add to existing collection group + const collection = collectionsMap.get(collectionKey)!; + collection[key] = value; + } else { + regularState[key as keyof OnyxValues] = value; + } + }); + console.timeEnd('Grouping collections'); + + return Onyx.clear(KEYS_TO_PRESERVE) + .then(() => { + console.time('Collections processing'); + const collectionPromises = Array.from(collectionsMap.entries()).map(([baseKey, items]) => { + console.log(`Setting collection ${baseKey} with ${Object.keys(items).length} items`); + return Onyx.setCollection(baseKey, items); + }); + return Promise.all(collectionPromises); + }) + .then(() => { + console.timeEnd('Collections processing'); + console.time('Regular keys processing'); + + if (Object.keys(regularState).length > 0) { + return Onyx.multiSet(regularState); + } + return Promise.resolve(); + }) + .then(() => { + console.timeEnd('Regular keys processing'); + console.timeEnd('Processing state import'); + }); + }; + const handleFileRead = (file: FileObject) => { if (!file.uri) { return; } + console.time('Total import operation'); setIsLoading(true); + + console.time('File processing'); const blob = new Blob([file as BlobPart]); const response = new Response(blob); response .text() .then((text) => { + console.timeEnd('File processing'); const fileContent = text; + + console.time('State transformation'); const transformedState = cleanAndTransformState(fileContent); + console.timeEnd('State transformation'); + + console.time('Session handling'); const currentUserSessionCopy = {...session}; setPreservedUserSession(currentUserSessionCopy); setShouldForceOffline(true); - Onyx.clear(KEYS_TO_PRESERVE).then(() => { - Onyx.multiSet(transformedState).then(() => { - setIsUsingImportedState(true); - Navigation.navigate(ROUTES.HOME); - }); - }); + console.timeEnd('Session handling'); + + return processStateImport(transformedState); + }) + .then(() => { + setIsUsingImportedState(true); + Navigation.navigate(ROUTES.HOME); + console.timeEnd('Total import operation'); }) - .catch(() => { + .catch((error) => { + console.error('Error importing state:', error); setIsErrorModalVisible(true); setIsLoading(false); + console.timeEnd('Total import operation'); }); }; From 12a1c9af0d5137e11dc5c6c58035180b23322a7b Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Wed, 29 Jan 2025 10:11:24 +0100 Subject: [PATCH 02/13] use same process method on native --- .../ImportOnyxState/index.native.tsx | 50 +++----------- src/components/ImportOnyxState/index.tsx | 66 +------------------ src/components/ImportOnyxState/utils.ts | 42 +++++++++++- 3 files changed, 53 insertions(+), 105 deletions(-) diff --git a/src/components/ImportOnyxState/index.native.tsx b/src/components/ImportOnyxState/index.native.tsx index 411e16f87218..f2e4b0e72ad6 100644 --- a/src/components/ImportOnyxState/index.native.tsx +++ b/src/components/ImportOnyxState/index.native.tsx @@ -2,7 +2,7 @@ import React, {useState} from 'react'; import ReactNativeBlobUtil from 'react-native-blob-util'; import Onyx, {useOnyx} from 'react-native-onyx'; import type {FileObject} from '@components/AttachmentModal'; -import {KEYS_TO_PRESERVE, setIsUsingImportedState, setPreservedUserSession} from '@libs/actions/App'; +import {setIsUsingImportedState, setPreservedUserSession} from '@libs/actions/App'; import {setShouldForceOffline} from '@libs/actions/Network'; import Navigation from '@libs/Navigation/Navigation'; import type {OnyxValues} from '@src/ONYXKEYS'; @@ -10,9 +10,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import BaseImportOnyxState from './BaseImportOnyxState'; import type ImportOnyxStateProps from './types'; -import {cleanAndTransformState} from './utils'; - -const CHUNK_SIZE = 100; +import {cleanAndTransformState, processStateImport} from './utils'; function readOnyxFile(fileUri: string) { const filePath = decodeURIComponent(fileUri.replace('file://', '')); @@ -21,33 +19,10 @@ function readOnyxFile(fileUri: string) { if (!exists) { throw new Error('File does not exist'); } - console.timeEnd('Read Onyx file'); return ReactNativeBlobUtil.fs.readFile(filePath, 'utf8'); }); } -function chunkArray(array: T[], size: number): T[][] { - const result = []; - for (let i = 0; i < array.length; i += size) { - result.push(array.slice(i, i + size)); - } - return result; -} - -function applyStateInChunks(state: OnyxValues) { - console.time('applyStateInChunks'); - const entries = Object.entries(state); - const chunks = chunkArray(entries, CHUNK_SIZE); - - let promise = Promise.resolve(); - chunks.forEach((chunk) => { - const partialOnyxState = Object.fromEntries(chunk) as Partial; - promise = promise.then(() => Onyx.multiSet(partialOnyxState)); - }); - console.timeEnd('applyStateInChunks'); - return promise; -} - export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { const [isErrorModalVisible, setIsErrorModalVisible] = useState(false); const [session] = useOnyx(ONYXKEYS.SESSION); @@ -58,28 +33,23 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { } setIsLoading(true); - console.time('Read Onyx file'); readOnyxFile(file.uri) .then((fileContent: string) => { - console.time('Clean and transform state'); const transformedState = cleanAndTransformState(fileContent); - console.timeEnd('Clean and transform state'); - console.time('current user session copy'); const currentUserSessionCopy = {...session}; - console.timeEnd('current user session copy'); setPreservedUserSession(currentUserSessionCopy); setShouldForceOffline(true); - console.time('Onyx clear and apply in chunks'); - Onyx.clear(KEYS_TO_PRESERVE).then(() => { - applyStateInChunks(transformedState).then(() => { - setIsUsingImportedState(true); - Navigation.navigate(ROUTES.HOME); - }); - }); - console.timeEnd('Onyx clear and apply in chunks'); + return processStateImport(transformedState); + }) + .then(() => { + setIsUsingImportedState(true); + Navigation.navigate(ROUTES.HOME); }) .catch(() => { setIsErrorModalVisible(true); + }) + .finally(() => { + setIsLoading(false); }); }; diff --git a/src/components/ImportOnyxState/index.tsx b/src/components/ImportOnyxState/index.tsx index 014f75584a17..13a837a2c885 100644 --- a/src/components/ImportOnyxState/index.tsx +++ b/src/components/ImportOnyxState/index.tsx @@ -1,8 +1,7 @@ -import lodashGet from 'lodash/get'; import React, {useState} from 'react'; -import Onyx, {useOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import type {FileObject} from '@components/AttachmentModal'; -import {KEYS_TO_PRESERVE, setIsUsingImportedState, setPreservedUserSession} from '@libs/actions/App'; +import {setIsUsingImportedState, setPreservedUserSession} from '@libs/actions/App'; import {setShouldForceOffline} from '@libs/actions/Network'; import Navigation from '@libs/Navigation/Navigation'; import type {OnyxValues} from '@src/ONYXKEYS'; @@ -10,102 +9,43 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import BaseImportOnyxState from './BaseImportOnyxState'; import type ImportOnyxStateProps from './types'; -import {cleanAndTransformState} from './utils'; +import {cleanAndTransformState, processStateImport} from './utils'; export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { const [isErrorModalVisible, setIsErrorModalVisible] = useState(false); const [session] = useOnyx(ONYXKEYS.SESSION); - const processStateImport = (transformedState: OnyxValues) => { - console.time('Processing state import'); - - const collectionKeys = new Set(Object.values(ONYXKEYS.COLLECTION || {})); - // Use Map for better performance with large datasets - const collectionsMap = new Map>(); - const regularState: Partial = {}; - - console.time('Grouping collections'); - Object.entries(transformedState).forEach(([key, value]) => { - const baseKey = key.split('_').at(0); - const collectionKey = `${baseKey}_`; - - if (collectionKeys.has(collectionKey)) { - if (!collectionsMap.has(collectionKey)) { - collectionsMap.set(collectionKey, {}); - } - // Add to existing collection group - const collection = collectionsMap.get(collectionKey)!; - collection[key] = value; - } else { - regularState[key as keyof OnyxValues] = value; - } - }); - console.timeEnd('Grouping collections'); - - return Onyx.clear(KEYS_TO_PRESERVE) - .then(() => { - console.time('Collections processing'); - const collectionPromises = Array.from(collectionsMap.entries()).map(([baseKey, items]) => { - console.log(`Setting collection ${baseKey} with ${Object.keys(items).length} items`); - return Onyx.setCollection(baseKey, items); - }); - return Promise.all(collectionPromises); - }) - .then(() => { - console.timeEnd('Collections processing'); - console.time('Regular keys processing'); - - if (Object.keys(regularState).length > 0) { - return Onyx.multiSet(regularState); - } - return Promise.resolve(); - }) - .then(() => { - console.timeEnd('Regular keys processing'); - console.timeEnd('Processing state import'); - }); - }; - const handleFileRead = (file: FileObject) => { if (!file.uri) { return; } - console.time('Total import operation'); setIsLoading(true); - console.time('File processing'); const blob = new Blob([file as BlobPart]); const response = new Response(blob); response .text() .then((text) => { - console.timeEnd('File processing'); const fileContent = text; - console.time('State transformation'); const transformedState = cleanAndTransformState(fileContent); - console.timeEnd('State transformation'); - console.time('Session handling'); const currentUserSessionCopy = {...session}; setPreservedUserSession(currentUserSessionCopy); setShouldForceOffline(true); - console.timeEnd('Session handling'); return processStateImport(transformedState); }) .then(() => { setIsUsingImportedState(true); Navigation.navigate(ROUTES.HOME); - console.timeEnd('Total import operation'); }) .catch((error) => { console.error('Error importing state:', error); setIsErrorModalVisible(true); setIsLoading(false); - console.timeEnd('Total import operation'); }); }; diff --git a/src/components/ImportOnyxState/utils.ts b/src/components/ImportOnyxState/utils.ts index 94779868384d..225987f328fd 100644 --- a/src/components/ImportOnyxState/utils.ts +++ b/src/components/ImportOnyxState/utils.ts @@ -1,5 +1,8 @@ import cloneDeep from 'lodash/cloneDeep'; +import Onyx from 'react-native-onyx'; import type {UnknownRecord} from 'type-fest'; +import {KEYS_TO_PRESERVE} from '@libs/actions/App'; +import type {OnyxCollectionKey, OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; // List of Onyx keys from the .txt file we want to keep for the local override @@ -12,7 +15,7 @@ function isRecord(value: unknown): value is Record { function transformNumericKeysToArray(data: UnknownRecord): UnknownRecord | unknown[] { const dataCopy = cloneDeep(data); if (!isRecord(dataCopy)) { - return Array.isArray(dataCopy) ? (dataCopy as UnknownRecord[]).map(transformNumericKeysToArray) : (dataCopy as UnknownRecord); + return Array.isArray(dataCopy) ? (dataCopy as UnknownRecord[]).map(transformNumericKeysToArray) : dataCopy; } const keys = Object.keys(dataCopy); @@ -50,4 +53,39 @@ function cleanAndTransformState(state: string): T { return transformedState; } -export {transformNumericKeysToArray, cleanAndTransformState}; +const processStateImport = (transformedState: OnyxValues) => { + const collectionKeys = new Set(Object.values(ONYXKEYS.COLLECTION || {})); + const collectionsMap = new Map(); + const regularState: Partial = {}; + + Object.entries(transformedState).forEach(([key, value]) => { + const baseKey = key.split('_').at(0); + const collectionKey = `${baseKey}_` as OnyxCollectionKey; + + if (collectionKeys.has(collectionKey)) { + if (!collectionsMap.has(collectionKey)) { + collectionsMap.set(collectionKey, {}); + } + const collection = collectionsMap.get(collectionKey)!; + collection[key] = value; + } else { + regularState[key as keyof OnyxValues] = value; + } + }); + + return Onyx.clear(KEYS_TO_PRESERVE) + .then(() => { + const collectionPromises = Array.from(collectionsMap.entries()).map(([baseKey, items]) => { + return Onyx.setCollection(baseKey, items); + }); + return Promise.all(collectionPromises); + }) + .then(() => { + if (Object.keys(regularState).length > 0) { + return Onyx.multiSet(regularState); + } + return Promise.resolve(); + }); +}; + +export {transformNumericKeysToArray, cleanAndTransformState, processStateImport}; From de9a57ed893e00360b27c3e55b991e68547e5e27 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Thu, 30 Jan 2025 10:33:04 +0100 Subject: [PATCH 03/13] try to fix types --- src/components/ImportOnyxState/index.native.tsx | 2 +- src/components/ImportOnyxState/utils.ts | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/ImportOnyxState/index.native.tsx b/src/components/ImportOnyxState/index.native.tsx index f2e4b0e72ad6..cad42bf60de2 100644 --- a/src/components/ImportOnyxState/index.native.tsx +++ b/src/components/ImportOnyxState/index.native.tsx @@ -1,6 +1,6 @@ import React, {useState} from 'react'; import ReactNativeBlobUtil from 'react-native-blob-util'; -import Onyx, {useOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import type {FileObject} from '@components/AttachmentModal'; import {setIsUsingImportedState, setPreservedUserSession} from '@libs/actions/App'; import {setShouldForceOffline} from '@libs/actions/Network'; diff --git a/src/components/ImportOnyxState/utils.ts b/src/components/ImportOnyxState/utils.ts index 225987f328fd..1178f7df5508 100644 --- a/src/components/ImportOnyxState/utils.ts +++ b/src/components/ImportOnyxState/utils.ts @@ -1,8 +1,9 @@ import cloneDeep from 'lodash/cloneDeep'; +import type {OnyxKey, OnyxMergeCollectionInput, OnyxValue} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import type {UnknownRecord} from 'type-fest'; +import type {UnknownRecord, ValueOf} from 'type-fest'; import {KEYS_TO_PRESERVE} from '@libs/actions/App'; -import type {OnyxCollectionKey, OnyxValues} from '@src/ONYXKEYS'; +import type {OnyxCollectionKey, OnyxCollectionValuesMapping, OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; // List of Onyx keys from the .txt file we want to keep for the local override @@ -55,7 +56,7 @@ function cleanAndTransformState(state: string): T { const processStateImport = (transformedState: OnyxValues) => { const collectionKeys = new Set(Object.values(ONYXKEYS.COLLECTION || {})); - const collectionsMap = new Map(); + const collectionsMap = new Map>(); const regularState: Partial = {}; Object.entries(transformedState).forEach(([key, value]) => { @@ -66,10 +67,14 @@ const processStateImport = (transformedState: OnyxValues) => { if (!collectionsMap.has(collectionKey)) { collectionsMap.set(collectionKey, {}); } - const collection = collectionsMap.get(collectionKey)!; + const collection = collectionsMap.get(collectionKey); + if (!collection) { + return; + } + collection[key] = value; } else { - regularState[key as keyof OnyxValues] = value; + regularState[key] = value; } }); From d0b53dff1a97d6c728c5b660f8ee59895c3fefe4 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Thu, 30 Jan 2025 17:32:49 +0100 Subject: [PATCH 04/13] add logs --- src/components/ImportOnyxState/utils.ts | 2 +- src/libs/actions/App.ts | 7 +++++-- src/libs/actions/PersistedRequests.ts | 7 ++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/ImportOnyxState/utils.ts b/src/components/ImportOnyxState/utils.ts index 1178f7df5508..09359542310f 100644 --- a/src/components/ImportOnyxState/utils.ts +++ b/src/components/ImportOnyxState/utils.ts @@ -55,7 +55,7 @@ function cleanAndTransformState(state: string): T { } const processStateImport = (transformedState: OnyxValues) => { - const collectionKeys = new Set(Object.values(ONYXKEYS.COLLECTION || {})); + const collectionKeys = new Set(Object.values(ONYXKEYS.COLLECTION)); const collectionsMap = new Map>(); const regularState: Partial = {}; diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index ddf9701a6c8e..fbf95fed55bf 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -26,7 +26,7 @@ import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {OnyxData} from '@src/types/onyx/Request'; import {setShouldForceOffline} from './Network'; -import {getAll, save} from './PersistedRequests'; +import {getAll, getOngoingRequest, save} from './PersistedRequests'; import {createDraftInitialWorkspace, createWorkspace, generatePolicyID} from './Policy/Policy'; import {resolveDuplicationConflictAction} from './RequestConflictUtils'; import {isAnonymousUser} from './Session'; @@ -564,6 +564,9 @@ function clearOnyxAndResetApp(shouldNavigateToHomepage?: boolean) { const isStateImported = isUsingImportedState; const shouldUseStagingServer = preservedShouldUseStagingServer; const sequentialQueue = getAll(); + const ongoingRequests = getOngoingRequest(); + console.log('sequentialQueue', sequentialQueue); + console.log('ongoingRequests', ongoingRequests); Onyx.clear(KEYS_TO_PRESERVE).then(() => { // Network key is preserved, so when using imported state, we should stop forcing offline mode so that the app can re-fetch the network if (isStateImported) { @@ -587,7 +590,7 @@ function clearOnyxAndResetApp(shouldNavigateToHomepage?: boolean) { // However, the OpenApp request must be called before any other request in a queue to ensure data consistency. // To do that, sequential queue is cleared together with other keys, and then it's restored once the OpenApp request is resolved. openApp().then(() => { - if (!sequentialQueue || isStateImported) { + if (!sequentialQueue.length && isStateImported) { return; } diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index c4df73067659..e473b5c7c23b 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -10,15 +10,20 @@ let ongoingRequest: Request | null = null; Onyx.connect({ key: ONYXKEYS.PERSISTED_REQUESTS, callback: (val) => { + console.log('val', val); Log.info('[PersistedRequests] hit Onyx connect callback', false, {isValNullish: val == null}); persistedRequests = val ?? []; + console.log('Onyx connect persistedRequests', persistedRequests); + console.log('ongoingRequest', ongoingRequest); + console.log('ongoingRequest && persistedRequests.length > 0', ongoingRequest && persistedRequests.length > 0); if (ongoingRequest && persistedRequests.length > 0) { const nextRequestToProcess = persistedRequests.at(0); - + console.log('nextRequestToProcess', nextRequestToProcess); // We try to remove the next request from the persistedRequests if it is the same as ongoingRequest // so we don't process it twice. if (isEqual(nextRequestToProcess, ongoingRequest)) { + console.log('Removing the next request from the persistedRequests'); persistedRequests = persistedRequests.slice(1); } } From a0a25ee4383ad1e4471e0e6bbe310fe2c4325731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 30 Jan 2025 19:09:05 +0000 Subject: [PATCH 05/13] Fix types and logic --- src/components/ImportOnyxState/utils.ts | 29 ++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/components/ImportOnyxState/utils.ts b/src/components/ImportOnyxState/utils.ts index 09359542310f..fe0dc430ce28 100644 --- a/src/components/ImportOnyxState/utils.ts +++ b/src/components/ImportOnyxState/utils.ts @@ -1,9 +1,9 @@ import cloneDeep from 'lodash/cloneDeep'; -import type {OnyxKey, OnyxMergeCollectionInput, OnyxValue} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry, OnyxKey} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import type {UnknownRecord, ValueOf} from 'type-fest'; +import type {UnknownRecord} from 'type-fest'; import {KEYS_TO_PRESERVE} from '@libs/actions/App'; -import type {OnyxCollectionKey, OnyxCollectionValuesMapping, OnyxValues} from '@src/ONYXKEYS'; +import type {OnyxCollectionValuesMapping, OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; // List of Onyx keys from the .txt file we want to keep for the local override @@ -55,42 +55,41 @@ function cleanAndTransformState(state: string): T { } const processStateImport = (transformedState: OnyxValues) => { - const collectionKeys = new Set(Object.values(ONYXKEYS.COLLECTION)); - const collectionsMap = new Map>(); - const regularState: Partial = {}; + const collectionKeys = [...new Set(Object.values(ONYXKEYS.COLLECTION))]; + const collectionsMap = new Map>(); + const regularState: Partial>> = {}; Object.entries(transformedState).forEach(([key, value]) => { - const baseKey = key.split('_').at(0); - const collectionKey = `${baseKey}_` as OnyxCollectionKey; - - if (collectionKeys.has(collectionKey)) { + const collectionKey = collectionKeys.find((cKey) => key.startsWith(cKey)); + if (collectionKey) { if (!collectionsMap.has(collectionKey)) { collectionsMap.set(collectionKey, {}); } + const collection = collectionsMap.get(collectionKey); if (!collection) { return; } - collection[key] = value; + collection[key] = value as OnyxEntry; } else { - regularState[key] = value; + regularState[key as OnyxKey] = value as OnyxEntry; } }); return Onyx.clear(KEYS_TO_PRESERVE) .then(() => { const collectionPromises = Array.from(collectionsMap.entries()).map(([baseKey, items]) => { - return Onyx.setCollection(baseKey, items); + return items ? Onyx.setCollection(baseKey, items) : Promise.resolve(); }); return Promise.all(collectionPromises); }) .then(() => { if (Object.keys(regularState).length > 0) { - return Onyx.multiSet(regularState); + return Onyx.multiSet(regularState as Partial); } return Promise.resolve(); }); }; -export {transformNumericKeysToArray, cleanAndTransformState, processStateImport}; +export {cleanAndTransformState, processStateImport, transformNumericKeysToArray}; From 407dbfd0195a3c72bc2e006a92eade72a4b79907 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Fri, 31 Jan 2025 15:04:08 +0100 Subject: [PATCH 06/13] fix reseting to original state --- .../ImportOnyxState/index.native.tsx | 3 + src/libs/actions/App.ts | 58 +++++++++---------- src/libs/actions/PersistedRequests.ts | 6 -- 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/components/ImportOnyxState/index.native.tsx b/src/components/ImportOnyxState/index.native.tsx index cad42bf60de2..d1781425ec3d 100644 --- a/src/components/ImportOnyxState/index.native.tsx +++ b/src/components/ImportOnyxState/index.native.tsx @@ -4,6 +4,7 @@ import {useOnyx} from 'react-native-onyx'; import type {FileObject} from '@components/AttachmentModal'; import {setIsUsingImportedState, setPreservedUserSession} from '@libs/actions/App'; import {setShouldForceOffline} from '@libs/actions/Network'; +import {rollbackOngoingRequest} from '@libs/actions/PersistedRequests'; import Navigation from '@libs/Navigation/Navigation'; import type {OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -32,6 +33,8 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { return; } + rollbackOngoingRequest(); + setIsLoading(true); readOnyxFile(file.uri) .then((fileContent: string) => { diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index 82aa5eff6b74..c502adbf65be 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -27,7 +27,7 @@ import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {OnyxData} from '@src/types/onyx/Request'; import {setShouldForceOffline} from './Network'; -import {getAll, getOngoingRequest, save} from './PersistedRequests'; +import {getAll, save} from './PersistedRequests'; import {createDraftInitialWorkspace, createWorkspace, generatePolicyID} from './Policy/Policy'; import {resolveDuplicationConflictAction} from './RequestConflictUtils'; import {isAnonymousUser} from './Session'; @@ -570,41 +570,41 @@ function clearOnyxAndResetApp(shouldNavigateToHomepage?: boolean) { const isStateImported = isUsingImportedState; const shouldUseStagingServer = preservedShouldUseStagingServer; const sequentialQueue = getAll(); - const ongoingRequests = getOngoingRequest(); - console.log('sequentialQueue', sequentialQueue); - console.log('ongoingRequests', ongoingRequests); - Onyx.clear(KEYS_TO_PRESERVE).then(() => { - // Network key is preserved, so when using imported state, we should stop forcing offline mode so that the app can re-fetch the network - if (isStateImported) { - setShouldForceOffline(false); - } - - if (shouldNavigateToHomepage) { - Navigation.navigate(ROUTES.HOME); - } - if (preservedUserSession) { - Onyx.set(ONYXKEYS.SESSION, preservedUserSession); - Onyx.set(ONYXKEYS.PRESERVED_USER_SESSION, null); - } + Onyx.clear(KEYS_TO_PRESERVE) + .then(() => { + // Network key is preserved, so when using imported state, we should stop forcing offline mode so that the app can re-fetch the network + if (isStateImported) { + setShouldForceOffline(false); + } - if (shouldUseStagingServer) { - Onyx.set(ONYXKEYS.USER, {shouldUseStagingServer}); - } + if (shouldNavigateToHomepage) { + Navigation.navigate(ROUTES.HOME); + } - // Requests in a sequential queue should be called even if the Onyx state is reset, so we do not lose any pending data. - // However, the OpenApp request must be called before any other request in a queue to ensure data consistency. - // To do that, sequential queue is cleared together with other keys, and then it's restored once the OpenApp request is resolved. - openApp().then(() => { - if (!sequentialQueue.length && isStateImported) { - return; + if (preservedUserSession) { + Onyx.set(ONYXKEYS.SESSION, preservedUserSession); + Onyx.set(ONYXKEYS.PRESERVED_USER_SESSION, null); } - sequentialQueue.forEach((request) => { - save(request); + if (shouldUseStagingServer) { + Onyx.set(ONYXKEYS.USER, {shouldUseStagingServer}); + } + }) + .then(() => { + // Requests in a sequential queue should be called even if the Onyx state is reset, so we do not lose any pending data. + // However, the OpenApp request must be called before any other request in a queue to ensure data consistency. + // To do that, sequential queue is cleared together with other keys, and then it's restored once the OpenApp request is resolved. + openApp().then(() => { + if (!sequentialQueue || isStateImported) { + return; + } + + sequentialQueue.forEach((request) => { + save(request); + }); }); }); - }); clearSoundAssetsCache(); } diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index e473b5c7c23b..d8dcbe4ed3ee 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -10,20 +10,14 @@ let ongoingRequest: Request | null = null; Onyx.connect({ key: ONYXKEYS.PERSISTED_REQUESTS, callback: (val) => { - console.log('val', val); Log.info('[PersistedRequests] hit Onyx connect callback', false, {isValNullish: val == null}); persistedRequests = val ?? []; - console.log('Onyx connect persistedRequests', persistedRequests); - console.log('ongoingRequest', ongoingRequest); - console.log('ongoingRequest && persistedRequests.length > 0', ongoingRequest && persistedRequests.length > 0); if (ongoingRequest && persistedRequests.length > 0) { const nextRequestToProcess = persistedRequests.at(0); - console.log('nextRequestToProcess', nextRequestToProcess); // We try to remove the next request from the persistedRequests if it is the same as ongoingRequest // so we don't process it twice. if (isEqual(nextRequestToProcess, ongoingRequest)) { - console.log('Removing the next request from the persistedRequests'); persistedRequests = persistedRequests.slice(1); } } From 4f5862f35ff89e46c948edbdb3f8853d4b799a51 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Fri, 31 Jan 2025 16:24:57 +0100 Subject: [PATCH 07/13] resolve comments --- src/components/ImportOnyxState/index.native.tsx | 1 - src/components/ImportOnyxState/index.tsx | 2 ++ src/libs/actions/PersistedRequests.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ImportOnyxState/index.native.tsx b/src/components/ImportOnyxState/index.native.tsx index d1781425ec3d..43d3742a3411 100644 --- a/src/components/ImportOnyxState/index.native.tsx +++ b/src/components/ImportOnyxState/index.native.tsx @@ -34,7 +34,6 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { } rollbackOngoingRequest(); - setIsLoading(true); readOnyxFile(file.uri) .then((fileContent: string) => { diff --git a/src/components/ImportOnyxState/index.tsx b/src/components/ImportOnyxState/index.tsx index 13a837a2c885..39b8b46a9ed1 100644 --- a/src/components/ImportOnyxState/index.tsx +++ b/src/components/ImportOnyxState/index.tsx @@ -3,6 +3,7 @@ import {useOnyx} from 'react-native-onyx'; import type {FileObject} from '@components/AttachmentModal'; import {setIsUsingImportedState, setPreservedUserSession} from '@libs/actions/App'; import {setShouldForceOffline} from '@libs/actions/Network'; +import {rollbackOngoingRequest} from '@libs/actions/PersistedRequests'; import Navigation from '@libs/Navigation/Navigation'; import type {OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -20,6 +21,7 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { return; } + rollbackOngoingRequest(); setIsLoading(true); const blob = new Blob([file as BlobPart]); diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index d8dcbe4ed3ee..c4df73067659 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -15,6 +15,7 @@ Onyx.connect({ if (ongoingRequest && persistedRequests.length > 0) { const nextRequestToProcess = persistedRequests.at(0); + // We try to remove the next request from the persistedRequests if it is the same as ongoingRequest // so we don't process it twice. if (isEqual(nextRequestToProcess, ongoingRequest)) { From 5d009fe8b4c0c48fb714e18005cff2bfee804993 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Fri, 31 Jan 2025 17:01:26 +0100 Subject: [PATCH 08/13] resolve comments --- src/components/ImportOnyxState/index.native.tsx | 7 ++++--- src/components/ImportOnyxState/index.tsx | 6 ++++-- src/components/ImportOnyxState/utils.ts | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/ImportOnyxState/index.native.tsx b/src/components/ImportOnyxState/index.native.tsx index 43d3742a3411..97fb56a0f2b5 100644 --- a/src/components/ImportOnyxState/index.native.tsx +++ b/src/components/ImportOnyxState/index.native.tsx @@ -11,7 +11,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import BaseImportOnyxState from './BaseImportOnyxState'; import type ImportOnyxStateProps from './types'; -import {cleanAndTransformState, processStateImport} from './utils'; +import {cleanAndTransformState, importState} from './utils'; function readOnyxFile(fileUri: string) { const filePath = decodeURIComponent(fileUri.replace('file://', '')); @@ -41,13 +41,14 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { const currentUserSessionCopy = {...session}; setPreservedUserSession(currentUserSessionCopy); setShouldForceOffline(true); - return processStateImport(transformedState); + return importState(transformedState); }) .then(() => { setIsUsingImportedState(true); Navigation.navigate(ROUTES.HOME); }) - .catch(() => { + .catch((error) => { + console.error('Error importing state:', error); setIsErrorModalVisible(true); }) .finally(() => { diff --git a/src/components/ImportOnyxState/index.tsx b/src/components/ImportOnyxState/index.tsx index 39b8b46a9ed1..06619e41791e 100644 --- a/src/components/ImportOnyxState/index.tsx +++ b/src/components/ImportOnyxState/index.tsx @@ -10,7 +10,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import BaseImportOnyxState from './BaseImportOnyxState'; import type ImportOnyxStateProps from './types'; -import {cleanAndTransformState, processStateImport} from './utils'; +import {cleanAndTransformState, importState} from './utils'; export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { const [isErrorModalVisible, setIsErrorModalVisible] = useState(false); @@ -38,7 +38,7 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { setPreservedUserSession(currentUserSessionCopy); setShouldForceOffline(true); - return processStateImport(transformedState); + return importState(transformedState); }) .then(() => { setIsUsingImportedState(true); @@ -47,6 +47,8 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { .catch((error) => { console.error('Error importing state:', error); setIsErrorModalVisible(true); + }) + .finally(() => { setIsLoading(false); }); }; diff --git a/src/components/ImportOnyxState/utils.ts b/src/components/ImportOnyxState/utils.ts index fe0dc430ce28..7efccdfa2f34 100644 --- a/src/components/ImportOnyxState/utils.ts +++ b/src/components/ImportOnyxState/utils.ts @@ -54,7 +54,7 @@ function cleanAndTransformState(state: string): T { return transformedState; } -const processStateImport = (transformedState: OnyxValues) => { +const importState = (transformedState: OnyxValues) => { const collectionKeys = [...new Set(Object.values(ONYXKEYS.COLLECTION))]; const collectionsMap = new Map>(); const regularState: Partial>> = {}; @@ -92,4 +92,4 @@ const processStateImport = (transformedState: OnyxValues) => { }); }; -export {cleanAndTransformState, processStateImport, transformNumericKeysToArray}; +export {cleanAndTransformState, importState, transformNumericKeysToArray}; From f32532ac585dc4f99059086096bd4b293336f78b Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Fri, 31 Jan 2025 18:03:10 +0100 Subject: [PATCH 09/13] add rollingOngoidRequest before reseting to original state --- src/libs/actions/App.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index c502adbf65be..a17caeb4fbed 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -27,7 +27,7 @@ import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {OnyxData} from '@src/types/onyx/Request'; import {setShouldForceOffline} from './Network'; -import {getAll, save} from './PersistedRequests'; +import {getAll, rollbackOngoingRequest, save} from './PersistedRequests'; import {createDraftInitialWorkspace, createWorkspace, generatePolicyID} from './Policy/Policy'; import {resolveDuplicationConflictAction} from './RequestConflictUtils'; import {isAnonymousUser} from './Session'; @@ -571,6 +571,7 @@ function clearOnyxAndResetApp(shouldNavigateToHomepage?: boolean) { const shouldUseStagingServer = preservedShouldUseStagingServer; const sequentialQueue = getAll(); + rollbackOngoingRequest(); Onyx.clear(KEYS_TO_PRESERVE) .then(() => { // Network key is preserved, so when using imported state, we should stop forcing offline mode so that the app can re-fetch the network From 17d4a9ba8cb11c435b7f49e6fa6b5d7de2734ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 31 Jan 2025 19:33:26 +0000 Subject: [PATCH 10/13] minor adjustment --- src/components/ImportOnyxState/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ImportOnyxState/utils.ts b/src/components/ImportOnyxState/utils.ts index 7efccdfa2f34..4ba16db7ccaa 100644 --- a/src/components/ImportOnyxState/utils.ts +++ b/src/components/ImportOnyxState/utils.ts @@ -54,7 +54,7 @@ function cleanAndTransformState(state: string): T { return transformedState; } -const importState = (transformedState: OnyxValues) => { +function importState(transformedState: OnyxValues): Promise { const collectionKeys = [...new Set(Object.values(ONYXKEYS.COLLECTION))]; const collectionsMap = new Map>(); const regularState: Partial>> = {}; @@ -90,6 +90,6 @@ const importState = (transformedState: OnyxValues) => { } return Promise.resolve(); }); -}; +} export {cleanAndTransformState, importState, transformNumericKeysToArray}; From 17b433405d0b4b26d9233c0ed61a77aa977f7d98 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Mon, 3 Feb 2025 13:55:26 +0100 Subject: [PATCH 11/13] fix issue when sometimes reset onyx state not work --- src/components/ImportOnyxState/index.native.tsx | 2 +- src/components/ImportOnyxState/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ImportOnyxState/index.native.tsx b/src/components/ImportOnyxState/index.native.tsx index 97fb56a0f2b5..61b306c43212 100644 --- a/src/components/ImportOnyxState/index.native.tsx +++ b/src/components/ImportOnyxState/index.native.tsx @@ -33,10 +33,10 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { return; } - rollbackOngoingRequest(); setIsLoading(true); readOnyxFile(file.uri) .then((fileContent: string) => { + rollbackOngoingRequest(); const transformedState = cleanAndTransformState(fileContent); const currentUserSessionCopy = {...session}; setPreservedUserSession(currentUserSessionCopy); diff --git a/src/components/ImportOnyxState/index.tsx b/src/components/ImportOnyxState/index.tsx index 06619e41791e..bf734d20a3e9 100644 --- a/src/components/ImportOnyxState/index.tsx +++ b/src/components/ImportOnyxState/index.tsx @@ -21,7 +21,6 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { return; } - rollbackOngoingRequest(); setIsLoading(true); const blob = new Blob([file as BlobPart]); @@ -30,6 +29,7 @@ export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { response .text() .then((text) => { + rollbackOngoingRequest(); const fileContent = text; const transformedState = cleanAndTransformState(fileContent); From 19ed0b6d91483f586c3e114a76725c6022bc5e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 6 Feb 2025 10:46:24 -0800 Subject: [PATCH 12/13] Fix TS types --- src/components/ImportOnyxState/utils.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/ImportOnyxState/utils.ts b/src/components/ImportOnyxState/utils.ts index 4ba16db7ccaa..d0b0dcca5a70 100644 --- a/src/components/ImportOnyxState/utils.ts +++ b/src/components/ImportOnyxState/utils.ts @@ -1,10 +1,11 @@ import cloneDeep from 'lodash/cloneDeep'; -import type {OnyxCollection, OnyxEntry, OnyxKey} from 'react-native-onyx'; +import type {OnyxEntry, OnyxKey} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {UnknownRecord} from 'type-fest'; import {KEYS_TO_PRESERVE} from '@libs/actions/App'; -import type {OnyxCollectionValuesMapping, OnyxValues} from '@src/ONYXKEYS'; +import type {OnyxCollectionKey, OnyxCollectionValuesMapping, OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; +import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; // List of Onyx keys from the .txt file we want to keep for the local override const keysToOmit = [ONYXKEYS.ACTIVE_CLIENTS, ONYXKEYS.FREQUENTLY_USED_EMOJIS, ONYXKEYS.NETWORK, ONYXKEYS.CREDENTIALS, ONYXKEYS.PREFERRED_THEME]; @@ -56,10 +57,13 @@ function cleanAndTransformState(state: string): T { function importState(transformedState: OnyxValues): Promise { const collectionKeys = [...new Set(Object.values(ONYXKEYS.COLLECTION))]; - const collectionsMap = new Map>(); + const collectionsMap = new Map>(); const regularState: Partial>> = {}; - Object.entries(transformedState).forEach(([key, value]) => { + Object.entries(transformedState).forEach(([entryKey, entryValue]) => { + const key = entryKey as OnyxKey; + const value = entryValue as NonNullable>; + const collectionKey = collectionKeys.find((cKey) => key.startsWith(cKey)); if (collectionKey) { if (!collectionsMap.has(collectionKey)) { @@ -71,9 +75,9 @@ function importState(transformedState: OnyxValues): Promise { return; } - collection[key] = value as OnyxEntry; + collection[key as OnyxCollectionKey] = value; } else { - regularState[key as OnyxKey] = value as OnyxEntry; + regularState[key] = value; } }); From 37e623a5625778bf22d3d19ff2830cb60107d01a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 6 Feb 2025 11:23:55 -0800 Subject: [PATCH 13/13] Refactor utils to lib and action file --- .../ImportOnyxState/index.native.tsx | 2 +- src/components/ImportOnyxState/index.tsx | 2 +- .../utils.ts => libs/ImportOnyxStateUtils.ts} | 19 +++----------- src/libs/actions/ImportOnyxState.ts | 25 +++++++++++++++++++ tests/unit/ImportOnyxStateTest.ts | 2 +- 5 files changed, 32 insertions(+), 18 deletions(-) rename src/{components/ImportOnyxState/utils.ts => libs/ImportOnyxStateUtils.ts} (83%) create mode 100644 src/libs/actions/ImportOnyxState.ts diff --git a/src/components/ImportOnyxState/index.native.tsx b/src/components/ImportOnyxState/index.native.tsx index 61b306c43212..e3eca1993313 100644 --- a/src/components/ImportOnyxState/index.native.tsx +++ b/src/components/ImportOnyxState/index.native.tsx @@ -5,13 +5,13 @@ import type {FileObject} from '@components/AttachmentModal'; import {setIsUsingImportedState, setPreservedUserSession} from '@libs/actions/App'; import {setShouldForceOffline} from '@libs/actions/Network'; import {rollbackOngoingRequest} from '@libs/actions/PersistedRequests'; +import {cleanAndTransformState, importState} from '@libs/ImportOnyxStateUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import BaseImportOnyxState from './BaseImportOnyxState'; import type ImportOnyxStateProps from './types'; -import {cleanAndTransformState, importState} from './utils'; function readOnyxFile(fileUri: string) { const filePath = decodeURIComponent(fileUri.replace('file://', '')); diff --git a/src/components/ImportOnyxState/index.tsx b/src/components/ImportOnyxState/index.tsx index bf734d20a3e9..8f199a176539 100644 --- a/src/components/ImportOnyxState/index.tsx +++ b/src/components/ImportOnyxState/index.tsx @@ -4,13 +4,13 @@ import type {FileObject} from '@components/AttachmentModal'; import {setIsUsingImportedState, setPreservedUserSession} from '@libs/actions/App'; import {setShouldForceOffline} from '@libs/actions/Network'; import {rollbackOngoingRequest} from '@libs/actions/PersistedRequests'; +import {cleanAndTransformState, importState} from '@libs/ImportOnyxStateUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import BaseImportOnyxState from './BaseImportOnyxState'; import type ImportOnyxStateProps from './types'; -import {cleanAndTransformState, importState} from './utils'; export default function ImportOnyxState({setIsLoading}: ImportOnyxStateProps) { const [isErrorModalVisible, setIsErrorModalVisible] = useState(false); diff --git a/src/components/ImportOnyxState/utils.ts b/src/libs/ImportOnyxStateUtils.ts similarity index 83% rename from src/components/ImportOnyxState/utils.ts rename to src/libs/ImportOnyxStateUtils.ts index d0b0dcca5a70..249924291a92 100644 --- a/src/components/ImportOnyxState/utils.ts +++ b/src/libs/ImportOnyxStateUtils.ts @@ -1,11 +1,10 @@ import cloneDeep from 'lodash/cloneDeep'; import type {OnyxEntry, OnyxKey} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; import type {UnknownRecord} from 'type-fest'; -import {KEYS_TO_PRESERVE} from '@libs/actions/App'; import type {OnyxCollectionKey, OnyxCollectionValuesMapping, OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; +import {clearOnyxStateBeforeImport, importOnyxCollectionState, importOnyxRegularState} from './actions/ImportOnyxState'; // List of Onyx keys from the .txt file we want to keep for the local override const keysToOmit = [ONYXKEYS.ACTIVE_CLIENTS, ONYXKEYS.FREQUENTLY_USED_EMOJIS, ONYXKEYS.NETWORK, ONYXKEYS.CREDENTIALS, ONYXKEYS.PREFERRED_THEME]; @@ -81,19 +80,9 @@ function importState(transformedState: OnyxValues): Promise { } }); - return Onyx.clear(KEYS_TO_PRESERVE) - .then(() => { - const collectionPromises = Array.from(collectionsMap.entries()).map(([baseKey, items]) => { - return items ? Onyx.setCollection(baseKey, items) : Promise.resolve(); - }); - return Promise.all(collectionPromises); - }) - .then(() => { - if (Object.keys(regularState).length > 0) { - return Onyx.multiSet(regularState as Partial); - } - return Promise.resolve(); - }); + return clearOnyxStateBeforeImport() + .then(() => importOnyxCollectionState(collectionsMap)) + .then(() => importOnyxRegularState(regularState)); } export {cleanAndTransformState, importState, transformNumericKeysToArray}; diff --git a/src/libs/actions/ImportOnyxState.ts b/src/libs/actions/ImportOnyxState.ts new file mode 100644 index 000000000000..772b58283807 --- /dev/null +++ b/src/libs/actions/ImportOnyxState.ts @@ -0,0 +1,25 @@ +import type {OnyxEntry, OnyxKey} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import type {OnyxCollectionKey, OnyxCollectionValuesMapping, OnyxValues} from '@src/ONYXKEYS'; +import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; +import {KEYS_TO_PRESERVE} from './App'; + +function clearOnyxStateBeforeImport(): Promise { + return Onyx.clear(KEYS_TO_PRESERVE); +} + +function importOnyxCollectionState(collectionsMap: Map>): Promise { + const collectionPromises = Array.from(collectionsMap.entries()).map(([baseKey, items]) => { + return items ? Onyx.setCollection(baseKey, items) : Promise.resolve(); + }); + return Promise.all(collectionPromises); +} + +function importOnyxRegularState(state: Partial>>): Promise { + if (Object.keys(state).length > 0) { + return Onyx.multiSet(state as Partial); + } + return Promise.resolve(); +} + +export {clearOnyxStateBeforeImport, importOnyxCollectionState, importOnyxRegularState}; diff --git a/tests/unit/ImportOnyxStateTest.ts b/tests/unit/ImportOnyxStateTest.ts index 4a62a1e1a7e1..9a88a07d1d31 100644 --- a/tests/unit/ImportOnyxStateTest.ts +++ b/tests/unit/ImportOnyxStateTest.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import {cleanAndTransformState, transformNumericKeysToArray} from '@components/ImportOnyxState/utils'; import ONYXKEYS from '@src/ONYXKEYS'; +import {cleanAndTransformState, transformNumericKeysToArray} from '../../src/libs/ImportOnyxStateUtils'; describe('transformNumericKeysToArray', () => { it('converts object with numeric keys to array', () => {