diff --git a/docs/assets/images/new-expensify-rules.png b/docs/assets/images/new-expensify-rules.png new file mode 100644 index 000000000000..5d37df0ed95b Binary files /dev/null and b/docs/assets/images/new-expensify-rules.png differ diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 2ca42f2af036..2474f521872c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -447,6 +447,9 @@ const ONYXKEYS = { /** Indicates whether we should record troubleshoot data or not */ SHOULD_RECORD_TROUBLESHOOT_DATA: 'shouldRecordTroubleshootData', + /** Timestamp when troubleshoot recording was started (for auto-off after 10 minutes) */ + TROUBLESHOOT_RECORDING_START_TIME: 'troubleshootRecordingStartTime', + /** Indicates whether we should mask fragile user data while exporting onyx state or not */ SHOULD_MASK_ONYX_STATE: 'shouldMaskOnyxState', @@ -1283,6 +1286,7 @@ type OnyxValuesMapping = { [ONYXKEYS.LOGS]: OnyxTypes.CapturedLogs; [ONYXKEYS.SHOULD_STORE_LOGS]: boolean; [ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA]: boolean; + [ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME]: number | null; [ONYXKEYS.SHOULD_MASK_ONYX_STATE]: boolean; [ONYXKEYS.SHOULD_USE_STAGING_SERVER]: boolean; [ONYXKEYS.IS_DEBUG_MODE_ENABLED]: boolean; diff --git a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx index 6f973e58f488..ca4b46d94a9a 100644 --- a/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx +++ b/src/components/RecordTroubleshootDataToolMenu/BaseRecordTroubleshootDataToolMenu.tsx @@ -1,10 +1,8 @@ import type JSZip from 'jszip'; import type {RefObject} from 'react'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {Alert} from 'react-native'; -import RNFetchBlob from 'react-native-blob-util'; import DeviceInfo from 'react-native-device-info'; -import {startProfiling, stopProfiling} from 'react-native-release-profiler'; import Button from '@components/Button'; import Switch from '@components/Switch'; import TestToolRow from '@components/TestToolRow'; @@ -12,21 +10,16 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; -import {disableLoggingAndFlushLogs, setShouldStoreLogs} from '@libs/actions/Console'; -import toggleProfileTool from '@libs/actions/ProfilingTool'; -import {setShouldRecordTroubleshootData} from '@libs/actions/Troubleshoot'; +import {cleanupAfterDisable, disableRecording, enableRecording, stopProfilingAndGetData} from '@libs/actions/Troubleshoot'; +import type {ProfilingData} from '@libs/actions/Troubleshoot'; import {parseStringifiedMessages} from '@libs/Console'; import getPlatform from '@libs/getPlatform'; -import Log from '@libs/Log'; -import {Memoize} from '@libs/memoize'; -import Performance from '@libs/Performance'; -import {shouldShowProfileTool as shouldShowProfileToolUtil} from '@userActions/TestTool'; import CONFIG from '@src/CONFIG'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Log as OnyxLog} from '@src/types/onyx'; import pkg from '../../../package.json'; -import RNFS from './RNFS'; +import handleStopRecording from './handleStopRecording'; +import type StopRecordingParams from './handleStopRecording.types'; import Share from './Share'; type File = { @@ -88,52 +81,26 @@ function BaseRecordTroubleshootDataToolMenu({ const styles = useThemeStyles(); const [shouldRecordTroubleshootData] = useOnyx(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, {canBeMissing: true}); const [capturedLogs] = useOnyx(ONYXKEYS.LOGS, {canBeMissing: true}); - const [isProfilingInProgress] = useOnyx(ONYXKEYS.APP_PROFILING_IN_PROGRESS, {canBeMissing: true}); const [shareUrls, setShareUrls] = useState(); const [isDisabled, setIsDisabled] = useState(false); const [profileTracePath, setProfileTracePath] = useState(); - const shouldShowProfileTool = useMemo(() => shouldShowProfileToolUtil(), []); - - const onToggleProfiling = useCallback(() => { - const shouldProfiling = !isProfilingInProgress; - if (shouldProfiling) { - setShareUrls(undefined); - Memoize.startMonitoring(); - Performance.enableMonitoring(); - startProfiling(); - } else { - Performance.disableMonitoring(); - } - toggleProfileTool(shouldProfiling); - return () => { - Performance.disableMonitoring(); - }; - }, [isProfilingInProgress]); - - const getAppInfo = useCallback(() => { - return Promise.all([DeviceInfo.getTotalMemory(), DeviceInfo.getUsedMemory()]).then(([totalMemory, usedMemory]) => { - return JSON.stringify({ - appVersion: pkg.version, - environment: CONFIG.ENVIRONMENT, - platform: getPlatform(), - totalMemory: formatBytes(totalMemory, 2), - usedMemory: formatBytes(usedMemory, 2), - memoizeStats: Memoize.stopMonitoring(), - performance: shouldShowProfileTool ? Performance.getPerformanceMeasures() : undefined, - }); + const getAppInfo = async (profilingData: ProfilingData) => { + const [totalMemory, usedMemory] = await Promise.all([DeviceInfo.getTotalMemory(), DeviceInfo.getUsedMemory()]); + return JSON.stringify({ + appVersion: pkg.version, + environment: CONFIG.ENVIRONMENT, + platform: getPlatform(), + totalMemory: formatBytes(totalMemory, 2), + usedMemory: formatBytes(usedMemory, 2), + memoizeStats: profilingData.memoizeStats, + performance: profilingData.performanceMeasures, }); - }, [shouldShowProfileTool]); - - const onStopProfiling = useMemo(() => (shouldShowProfileTool ? stopProfiling : () => Promise.resolve()), [shouldShowProfileTool]); + }; - const onToggle = () => { - if (shouldShowProfileTool) { - onToggleProfiling(); - } + const onToggle = async () => { if (!shouldRecordTroubleshootData) { - setShouldStoreLogs(true); - setShouldRecordTroubleshootData(true); + enableRecording(); if (onEnableLogging) { onEnableLogging(); @@ -146,8 +113,7 @@ function BaseRecordTroubleshootDataToolMenu({ if (!capturedLogs) { Alert.alert(translate('initialSettingsPage.troubleshoot.noLogsToShare')); - disableLoggingAndFlushLogs(); - setShouldRecordTroubleshootData(false); + disableRecording(); return; } @@ -156,103 +122,30 @@ function BaseRecordTroubleshootDataToolMenu({ const infoFileName = `App_Info_${pkg.version}.json`; - if (getPlatform() === CONST.PLATFORM.WEB) { - onStopProfiling(true, newFileName).then(() => { - getAppInfo().then((appInfo) => { - zipRef.current?.file(infoFileName, appInfo); - - onDisableLogging(logsWithParsedMessages).then(() => { - disableLoggingAndFlushLogs(); - setShouldRecordTroubleshootData(false); - setIsDisabled(false); - onDownloadZip?.(); - }); - }); - }); - } else if (getPlatform() === CONST.PLATFORM.IOS) { - onStopProfiling(true, newFileName).then((path) => { - if (!path) { - return; - } - - const newFilePath = `${pathToBeUsed}/${newFileName}`; - - RNFS.exists(newFilePath) - .then((fileExists) => { - if (!fileExists) { - return; - } - - return RNFS.unlink(newFilePath).then(() => { - Log.hmmm('[ProfilingToolMenu] existing file deleted successfully'); - }); - }) - .catch((error) => { - const typedError = error as Error; - Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', typedError.message); - }) - .then(() => { - RNFS.copyFile(path, newFilePath) - .then(() => { - getAppInfo().then((appInfo) => { - zipRef.current?.file(infoFileName, appInfo); - - onDisableLogging(logsWithParsedMessages).then(() => { - disableLoggingAndFlushLogs(); - setShouldRecordTroubleshootData(false); - setIsDisabled(false); - onDownloadZip?.(); - }); - }); - Log.hmmm('[ProfilingToolMenu] file copied successfully'); - - setProfileTracePath(newFilePath); - }) - .catch((err) => { - console.error('[ProfilingToolMenu] error copying file: ', err); - }); - }) - .catch((error: Record) => { - console.error('[ProfilingToolMenu] error copying file: ', error); - Log.hmmm('[ProfilingToolMenu] error copying file: ', error); - }); - }); - } else if (getPlatform() === CONST.PLATFORM.ANDROID) { - onStopProfiling(true, newFileName).then((path) => { - if (!path) { - return; - } - - RNFetchBlob.fs - // Check if it is an internal path of `DownloadManager` then append content://media to create a valid url - .stat(!path.startsWith('content://media/') && path.match(/\/downloads\/\d+$/) ? `content://media/${path}` : path) - .then(({path: realPath}) => setProfileTracePath(realPath)) - .catch(() => setProfileTracePath(path)); - - getAppInfo().then((appInfo) => { - zipRef.current?.file(infoFileName, appInfo); - - onDisableLogging(logsWithParsedMessages).then(() => { - disableLoggingAndFlushLogs(); - setShouldRecordTroubleshootData(false); - setIsDisabled(false); - }); - }); - }); - } else { - // Desktop - onStopProfiling(true, newFileName).then(() => { - getAppInfo().then((appInfo) => { - zipRef.current?.file(infoFileName, appInfo); - - onDisableLogging(logsWithParsedMessages).then(() => { - disableLoggingAndFlushLogs(); - setShouldRecordTroubleshootData(false); - setIsDisabled(false); - }); - }); - }); + try { + const profilingData = await stopProfilingAndGetData(newFileName); + const appInfo = await getAppInfo(profilingData); + + const params: StopRecordingParams = { + profilingData, + infoFileName, + profileFileName: newFileName, + appInfo, + logsWithParsedMessages, + onDisableLogging, + cleanupAfterDisable, + zipRef, + pathToBeUsed, + onDownloadZip, + setProfileTracePath, + }; + + await handleStopRecording(params); + } catch (error) { + console.error('[ProfilingToolMenu] error handling stop recording', error); } + + setIsDisabled(false); }; useEffect(() => { @@ -275,7 +168,9 @@ function BaseRecordTroubleshootDataToolMenu({ { + onToggle().catch((error) => console.error('[ProfilingToolMenu] toggle failed', error)); + }} disabled={isDisabled} /> diff --git a/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts b/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts new file mode 100644 index 000000000000..7fb670ff1bfd --- /dev/null +++ b/src/components/RecordTroubleshootDataToolMenu/finalizeStopRecording.ts @@ -0,0 +1,19 @@ +import type StopRecordingParams from './handleStopRecording.types'; + +type FinalizeStopRecordingParams = Pick; + +export default async function finalizeStopRecording({ + infoFileName, + appInfo, + logsWithParsedMessages, + onDisableLogging, + cleanupAfterDisable, + zipRef, + onDownloadZip, +}: FinalizeStopRecordingParams): Promise { + zipRef.current?.file(infoFileName, appInfo); + + await onDisableLogging(logsWithParsedMessages); + cleanupAfterDisable(); + onDownloadZip?.(); +} diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts new file mode 100644 index 000000000000..194f9810b989 --- /dev/null +++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.android.ts @@ -0,0 +1,37 @@ +import RNFetchBlob from 'react-native-blob-util'; +import finalizeStopRecording from './finalizeStopRecording'; +import type StopRecordingParams from './handleStopRecording.types'; + +export default async function handleStopRecording({ + profilingData, + infoFileName, + appInfo, + logsWithParsedMessages, + onDisableLogging, + cleanupAfterDisable, + zipRef, + onDownloadZip, + setProfileTracePath, +}: StopRecordingParams): Promise { + const {profilePath} = profilingData; + + if (profilePath) { + try { + // Check if it is an internal path of `DownloadManager` then append content://media to create a valid url + const {path} = await RNFetchBlob.fs.stat(!profilePath.startsWith('content://media/') && profilePath.match(/\/downloads\/\d+$/) ? `content://media/${profilePath}` : profilePath); + setProfileTracePath?.(path); + } catch { + setProfileTracePath?.(profilePath); + } + } + + await finalizeStopRecording({ + infoFileName, + appInfo, + logsWithParsedMessages, + onDisableLogging, + cleanupAfterDisable, + zipRef, + onDownloadZip, + }); +} diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts new file mode 100644 index 000000000000..1f4a28d17953 --- /dev/null +++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ios.ts @@ -0,0 +1,57 @@ +import RNFS from 'react-native-fs'; +import Log from '@libs/Log'; +import finalizeStopRecording from './finalizeStopRecording'; +import type StopRecordingParams from './handleStopRecording.types'; + +export default async function handleStopRecording({ + profilingData, + infoFileName, + profileFileName, + appInfo, + logsWithParsedMessages, + onDisableLogging, + cleanupAfterDisable, + zipRef, + pathToBeUsed, + onDownloadZip, + setProfileTracePath, +}: StopRecordingParams): Promise { + const {profilePath} = profilingData; + + if (!profilePath) { + cleanupAfterDisable(); + return; + } + + const newFilePath = `${pathToBeUsed}/${profileFileName}`; + + try { + const fileExists = await RNFS.exists(newFilePath); + if (fileExists) { + await RNFS.unlink(newFilePath); + Log.hmmm('[ProfilingToolMenu] existing file deleted successfully'); + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + Log.hmmm('[ProfilingToolMenu] error checking/deleting existing file: ', message); + } + + try { + await RNFS.copyFile(profilePath, newFilePath); + await finalizeStopRecording({ + infoFileName, + appInfo, + logsWithParsedMessages, + onDisableLogging, + cleanupAfterDisable, + zipRef, + onDownloadZip, + }); + + setProfileTracePath?.(newFilePath); + Log.hmmm('[ProfilingToolMenu] file copied successfully'); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + Log.hmmm('[ProfilingToolMenu] error copying file: ', message); + } +} diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts new file mode 100644 index 000000000000..90f32b3b584a --- /dev/null +++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.ts @@ -0,0 +1,17 @@ +import finalizeStopRecording from './finalizeStopRecording'; +import type StopRecordingParams from './handleStopRecording.types'; + +type HandleStopRecording = (params: StopRecordingParams) => Promise; + +const handleStopRecording: HandleStopRecording = ({infoFileName, appInfo, logsWithParsedMessages, onDisableLogging, cleanupAfterDisable, zipRef, onDownloadZip}) => + finalizeStopRecording({ + infoFileName, + appInfo, + logsWithParsedMessages, + onDisableLogging, + cleanupAfterDisable, + zipRef, + onDownloadZip, + }); + +export default handleStopRecording; diff --git a/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts new file mode 100644 index 000000000000..19eebde4df72 --- /dev/null +++ b/src/components/RecordTroubleshootDataToolMenu/handleStopRecording.types.ts @@ -0,0 +1,20 @@ +import type JSZip from 'jszip'; +import type {RefObject} from 'react'; +import type {ProfilingData} from '@libs/actions/Troubleshoot'; +import type {Log as OnyxLog} from '@src/types/onyx'; + +type StopRecordingParams = { + profilingData: ProfilingData; + infoFileName: string; + profileFileName: string; + appInfo: string; + logsWithParsedMessages: OnyxLog[]; + onDisableLogging: (logs: OnyxLog[]) => Promise; + cleanupAfterDisable: () => void; + zipRef: RefObject>; + pathToBeUsed: string; + onDownloadZip?: () => void; + setProfileTracePath?: (path: string) => void; +}; + +export default StopRecordingParams; diff --git a/src/libs/ExportOnyxState/index.native.ts b/src/libs/ExportOnyxState/index.native.ts index 12cfb612ab1c..8e7e5ad04193 100644 --- a/src/libs/ExportOnyxState/index.native.ts +++ b/src/libs/ExportOnyxState/index.native.ts @@ -7,9 +7,18 @@ import type OnyxState from '@src/types/onyx/OnyxState'; import {maskOnyxState} from './common'; import type {ExportOnyxStateModule, ReadFromOnyxDatabase, ShareAsFile} from './types'; +let onyxDb: ReturnType | null = null; + +function getOnyxDb() { + if (!onyxDb) { + onyxDb = open({name: CONST.DEFAULT_DB_NAME}); + } + return onyxDb; +} + const readFromOnyxDatabase: ReadFromOnyxDatabase = () => new Promise((resolve) => { - const db = open({name: CONST.DEFAULT_DB_NAME}); + const db = getOnyxDb(); const query = `SELECT * FROM ${CONST.DEFAULT_TABLE_NAME}`; db.executeAsync(query, []).then(({rows}) => { diff --git a/src/libs/actions/Troubleshoot.ts b/src/libs/actions/Troubleshoot.ts index 8f149d09d132..4b483c69627b 100644 --- a/src/libs/actions/Troubleshoot.ts +++ b/src/libs/actions/Troubleshoot.ts @@ -1,13 +1,210 @@ import Onyx from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {startProfiling, stopProfiling} from 'react-native-release-profiler'; +import {Memoize} from '@libs/memoize'; +import Performance from '@libs/Performance'; import ONYXKEYS from '@src/ONYXKEYS'; +import {disableLoggingAndFlushLogs, setShouldStoreLogs} from './Console'; +import toggleProfileTool from './ProfilingTool'; +import {shouldShowProfileTool} from './TestTool'; + +type ProfilingData = { + profilePath?: string; + memoizeStats: ReturnType; + performanceMeasures?: ReturnType; +}; + +// Auto-off timeout for troubleshoot recording (10 minutes) +const AUTO_OFF_TIMEOUT_MS = 10 * 60 * 1000; + +// Module-level state +let autoOffTimeout: NodeJS.Timeout | null = null; +let shouldRecordTroubleshootData: OnyxEntry; +let troubleshootRecordingStartTime: OnyxEntry; +let isRecordingLoaded = false; +let isStartTimeLoaded = false; +let isInitialized = false; + +/** + * Clear the auto-off timeout if it exists + */ +function clearAutoOffTimeout() { + if (!autoOffTimeout) { + return; + } + + clearTimeout(autoOffTimeout); + autoOffTimeout = null; +} + +/** + * Stop profiling and get profiling data. + * Used for manual disable to get profile path and stats before cleanup. + * @param fileName - The filename to save the profile trace as + * @returns Profile path, memoize stats, and performance measures + */ +async function stopProfilingAndGetData(fileName: string): Promise { + const showProfileTool = shouldShowProfileTool(); + + // Stop profiler and save to file (only if profiling is available) + const profilePath = showProfileTool ? await stopProfiling(true, fileName) : undefined; + + // Get stats before stopping monitoring + const memoizeStats = Memoize.stopMonitoring(); + const performanceMeasures = showProfileTool ? Performance.getPerformanceMeasures() : undefined; + + // Stop monitoring + Performance.disableMonitoring(); + toggleProfileTool(false); + + return {profilePath, memoizeStats, performanceMeasures}; +} + +/** + * Disable troubleshoot recording, stop profiling, and clean up logs. + * Used for auto-off and invalid state cleanup. + */ +function clearRecordingOnyxState() { + Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, null); + Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, false); +} + +function disableRecording() { + clearAutoOffTimeout(); + + // Stop profiling and performance monitoring + Performance.disableMonitoring(); + Memoize.stopMonitoring(); + toggleProfileTool(false); + + // Disable logging and flush logs + disableLoggingAndFlushLogs(); + + // Update Onyx state + clearRecordingOnyxState(); +} + +/** + * Clean up Onyx state and flush logs. + * Used after manual disable when profiling was already stopped via stopProfilingAndGetData. + */ +function cleanupAfterDisable() { + clearAutoOffTimeout(); + disableLoggingAndFlushLogs(); + clearRecordingOnyxState(); +} + +/** + * Schedule auto-off after the given time + */ +function scheduleAutoOff(remainingTime: number) { + clearAutoOffTimeout(); + autoOffTimeout = setTimeout(() => { + disableRecording(); + autoOffTimeout = null; + }, remainingTime); +} + +/** + * Enable troubleshoot recording, start profiling, and enable log storage. + */ +function enableRecording() { + clearAutoOffTimeout(); + + // Enable log storage + setShouldStoreLogs(true); + + // Start profiling and performance monitoring if available + if (shouldShowProfileTool()) { + Memoize.startMonitoring(); + Performance.enableMonitoring(); + startProfiling(); + toggleProfileTool(true); + } + + // Update Onyx state and schedule auto-off + Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, Date.now()); + scheduleAutoOff(AUTO_OFF_TIMEOUT_MS); + Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, true); +} + +/** + * Check recording state on app load and handle accordingly. + * - If recording ON but no start time → disable (invalid state) + * - If recording ON and start time exists → calculate remaining time or disable if exceeded + */ +function tryInitialize() { + if (isInitialized || !isRecordingLoaded || !isStartTimeLoaded) { + return; + } + isInitialized = true; + + if (!shouldRecordTroubleshootData) { + return; + } + + // Recording is ON but no start time - invalid state, disable + if (!troubleshootRecordingStartTime) { + disableRecording(); + return; + } + + // Calculate remaining time + const elapsedTime = Date.now() - troubleshootRecordingStartTime; + + if (elapsedTime >= AUTO_OFF_TIMEOUT_MS) { + // Time exceeded, disable recording + disableRecording(); + } else { + // Schedule auto-off for remaining time + const remainingTime = AUTO_OFF_TIMEOUT_MS - elapsedTime; + scheduleAutoOff(remainingTime); + } +} + +/** + * Listen for changes to the troubleshoot recording flag. + * On app load, triggers initialization to handle auto-off timer. + */ +Onyx.connectWithoutView({ + key: ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, + initWithStoredValues: true, + callback: (value) => { + shouldRecordTroubleshootData = value; + isRecordingLoaded = true; + tryInitialize(); + }, +}); + +/** + * Listen for changes to the recording start time. + * On app load, triggers initialization to calculate remaining time for auto-off. + */ +Onyx.connectWithoutView({ + key: ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, + initWithStoredValues: true, + callback: (value) => { + troubleshootRecordingStartTime = value; + isStartTimeLoaded = true; + tryInitialize(); + }, +}); /** * Set whether or not to record troubleshoot data * @param shouldRecord Whether or not to record troubleshoot data */ function setShouldRecordTroubleshootData(shouldRecord: boolean) { - Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, shouldRecord); + clearAutoOffTimeout(); + + if (shouldRecord) { + Onyx.set(ONYXKEYS.TROUBLESHOOT_RECORDING_START_TIME, Date.now()); + scheduleAutoOff(AUTO_OFF_TIMEOUT_MS); + Onyx.set(ONYXKEYS.SHOULD_RECORD_TROUBLESHOOT_DATA, true); + } else { + clearRecordingOnyxState(); + } } -// eslint-disable-next-line import/prefer-default-export -export {setShouldRecordTroubleshootData}; +export {setShouldRecordTroubleshootData, enableRecording, disableRecording, stopProfilingAndGetData, cleanupAfterDisable}; +export type {ProfilingData}; diff --git a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx index df6ac6966a54..c6ddcea9e2db 100644 --- a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx +++ b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx @@ -198,7 +198,7 @@ function TroubleshootPage() { > - + {!isProduction && }