From f496a175d57d6195cad7f1007e03469e2ff8fbe8 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 16 Jul 2024 14:24:20 +0100 Subject: [PATCH 01/55] feat: debug mode for report and report actions --- src/CONST.ts | 8 + src/ROUTES.ts | 32 ++++ src/SCREENS.ts | 6 + src/components/AccountSwitcher.tsx | 10 ++ src/components/TabSelector/TabSelector.tsx | 8 + src/components/TestToolMenu.tsx | 9 ++ src/languages/en.ts | 7 + src/languages/es.ts | 1 + .../ModalStackNavigators/index.tsx | 8 + .../Navigators/RightModalNavigator.tsx | 4 + src/libs/Navigation/linkingConfig/config.ts | 40 +++++ src/libs/Navigation/types.ts | 15 ++ src/libs/actions/User.ts | 5 + src/pages/Debug/DebugDetails.tsx | 102 +++++++++++++ src/pages/Debug/DebugJSON.tsx | 20 +++ .../Report/DebugReportActionCreatePage.tsx | 140 ++++++++++++++++++ .../Debug/Report/DebugReportActionPage.tsx | 73 +++++++++ .../Debug/Report/DebugReportActionPreview.tsx | 35 +++++ src/pages/Debug/Report/DebugReportActions.tsx | 59 ++++++++ src/pages/Debug/Report/DebugReportPage.tsx | 66 +++++++++ src/pages/ProfilePage.tsx | 8 + .../BaseReportActionContextMenu.tsx | 1 + src/types/onyx/User.ts | 3 + 23 files changed, 660 insertions(+) create mode 100644 src/pages/Debug/DebugDetails.tsx create mode 100644 src/pages/Debug/DebugJSON.tsx create mode 100644 src/pages/Debug/Report/DebugReportActionCreatePage.tsx create mode 100644 src/pages/Debug/Report/DebugReportActionPage.tsx create mode 100644 src/pages/Debug/Report/DebugReportActionPreview.tsx create mode 100644 src/pages/Debug/Report/DebugReportActions.tsx create mode 100644 src/pages/Debug/Report/DebugReportPage.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 78b01d67b78e6..554560b014d78 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4140,6 +4140,7 @@ const CONST = { CARD_AUTHENTICATION_REQUIRED: 'authentication_required', }, TAB: { + DEBUG_TAB_ID: 'DebugTab', NEW_CHAT_TAB_ID: 'NewChatTab', NEW_CHAT: 'chat', NEW_ROOM: 'room', @@ -5754,6 +5755,13 @@ const CONST = { CATEGORIES_ARTICLE_LINK: 'https://help.expensify.com/articles/expensify-classic/workspaces/Create-categories#import-custom-categories', TAGS_ARTICLE_LINK: 'https://help.expensify.com/articles/expensify-classic/workspaces/Create-tags#import-a-spreadsheet-1', }, + + DEBUG: { + DETAILS: 'details', + JSON: 'json', + REPORT_ACTIONS: 'actions', + REPORT_ACTION_PREVIEW: 'preview', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 04cc8a125fbbc..d169e98e7a28a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1483,6 +1483,38 @@ const ROUTES = { route: 'settings/workspaces/:policyID/accounting/sage-intacct/advanced/payment-account', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/advanced/payment-account` as const, }, + DEBUG_REPORT: { + route: 'debug/report/:reportID', + getRoute: (reportID: string) => `debug/report/${reportID}`, + }, + DEBUG_REPORT_TAB_DETAILS: { + route: 'debug/report/:reportID/details', + getRoute: (reportID: string) => `debug/report/${reportID}/details`, + }, + DEBUG_REPORT_TAB_JSON: { + route: 'debug/report/:reportID/json', + getRoute: (reportID: string) => `debug/report/${reportID}/json`, + }, + DEBUG_REPORT_TAB_ACTIONS: { + route: 'debug/report/:reportID/actions', + getRoute: (reportID: string) => `debug/report/${reportID}/actions`, + }, + DEBUG_REPORT_ACTION: { + route: 'debug/report/:reportID/actions/:reportActionID', + getRoute: (reportID: string, reportActionID: string) => `debug/report/${reportID}/actions/${reportActionID}`, + }, + DEBUG_REPORT_ACTION_CREATE: { + route: 'debug/report/:reportID/actions/create', + getRoute: (reportID: string) => `debug/report/${reportID}/actions/create`, + }, + DEBUG_REPORT_ACTION_TAB_DETAILS: { + route: 'debug/report/:reportID/actions/:reportActionID/details', + getRoute: (reportID: string, reportActionID: string) => `debug/report/${reportID}/actions/${reportActionID}/details`, + }, + DEBUG_REPORT_ACTION_TAB_JSON: { + route: 'debug/report/:reportID/actions/:reportActionID/json', + getRoute: (reportID: string, reportActionID: string) => `debug/report/${reportID}/actions/${reportActionID}/json`, + }, } as const; /** diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 2369e231f5199..418ae00280c88 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -178,6 +178,7 @@ const SCREENS = { RESTRICTED_ACTION: 'RestrictedAction', REPORT_EXPORT: 'Report_Export', MISSING_PERSONAL_DETAILS: 'MissingPersonalDetails', + DEBUG: 'Debug', }, ONBOARDING_MODAL: { ONBOARDING: 'Onboarding', @@ -551,6 +552,11 @@ const SCREENS = { FEATURE_TRAINING_ROOT: 'FeatureTraining_Root', RESTRICTED_ACTION_ROOT: 'RestrictedAction_Root', MISSING_PERSONAL_DETAILS_ROOT: 'MissingPersonalDetails_Root', + DEBUG: { + REPORT: 'Debug_Report', + REPORT_ACTION: 'Debug_Report_Action', + REPORT_ACTION_CREATE: 'Debug_Report_Action_Create', + }, } as const; type Screen = DeepValueOf; diff --git a/src/components/AccountSwitcher.tsx b/src/components/AccountSwitcher.tsx index a9e223e566324..7e5f4dc156bea 100644 --- a/src/components/AccountSwitcher.tsx +++ b/src/components/AccountSwitcher.tsx @@ -38,6 +38,8 @@ function AccountSwitcher() { const {canUseNewDotCopilot} = usePermissions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const [session] = useOnyx(ONYXKEYS.SESSION); + const [user] = useOnyx(ONYXKEYS.USER); const buttonRef = useRef(null); const [shouldShowDelegatorMenu, setShouldShowDelegatorMenu] = useState(false); @@ -166,6 +168,14 @@ function AccountSwitcher() { > {Str.removeSMSDomain(currentUserPersonalDetails?.login ?? '')} + {!!user?.isDebugModeEnabled && ( + + AccountID: {session?.accountID} + + )} diff --git a/src/components/TabSelector/TabSelector.tsx b/src/components/TabSelector/TabSelector.tsx index 7fca66b5f8c76..1bf753cd4aa49 100644 --- a/src/components/TabSelector/TabSelector.tsx +++ b/src/components/TabSelector/TabSelector.tsx @@ -27,6 +27,14 @@ type IconAndTitle = { function getIconAndTitle(route: string, translate: LocaleContextProps['translate']): IconAndTitle { switch (route) { + case CONST.DEBUG.DETAILS: + return {icon: Expensicons.Info, title: translate('debug.details')}; + case CONST.DEBUG.JSON: + return {icon: Expensicons.Eye, title: translate('debug.JSON')}; + case CONST.DEBUG.REPORT_ACTIONS: + return {icon: Expensicons.Document, title: translate('debug.reportActions')}; + case CONST.DEBUG.REPORT_ACTION_PREVIEW: + return {icon: Expensicons.Document, title: translate('debug.reportActionPreview')}; case CONST.TAB_REQUEST.MANUAL: return {icon: Expensicons.Pencil, title: translate('tabSelector.manual')}; case CONST.TAB_REQUEST.SCAN: diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 2c553386aff00..ce044b4c61985 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -41,6 +41,15 @@ function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) { > {translate('initialSettingsPage.troubleshoot.testingPreferences')} + {/* When toggled the app will be put into debug mode. */} + + User.setIsDebugModeEnabled(!user.isDebugModeEnabled)} + /> + + {/* Option to switch between staging and default api endpoints. This enables QA, internal testers and external devs to take advantage of sandbox environments for 3rd party services like Plaid and Onfido. This toggle is not rendered for internal devs as they make environment changes directly to the .env file. */} diff --git a/src/languages/en.ts b/src/languages/en.ts index a7ddea880161f..bdd2990b299b6 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1102,6 +1102,7 @@ export default { maskExportOnyxStateData: 'Mask fragile user data while exporting Onyx state', exportOnyxState: 'Export Onyx state', testCrash: 'Test crash', + debugMode: 'Debug mode', }, debugConsole: { saveLog: 'Save log', @@ -4797,4 +4798,10 @@ export default { notAllowedMessageHyperLinked: ' limited access', notAllowedMessageEnd: ' copilot', }, + debug: { + details: 'Details', + JSON: 'JSON', + reportActions: 'Actions', + reportActionPreview: 'Preview', + }, } satisfies TranslationBase; diff --git a/src/languages/es.ts b/src/languages/es.ts index a8481ec305fcd..6e3a7bb23a630 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1105,6 +1105,7 @@ export default { maskExportOnyxStateData: 'Enmascare los datos frágiles del usuario mientras exporta el estado Onyx', exportOnyxState: 'Exportar estado Onyx', testCrash: 'Prueba de fallo', + debugMode: 'Modo depuración', }, debugConsole: { saveLog: 'Guardar registro', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index b4b676bbdec55..8635adde6456e 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -4,6 +4,7 @@ import {createStackNavigator} from '@react-navigation/stack'; import React from 'react'; import type { AddPersonalBankAccountNavigatorParamList, + DebugParamList, EditRequestNavigatorParamList, EnablePaymentsNavigatorParamList, FlagCommentNavigatorParamList, @@ -572,6 +573,12 @@ const MissingPersonalDetailsModalStackNavigator = createModalStackNavigator require('../../../../pages/MissingPersonalDetails').default, }); +const DebugModalStackNavigator = createModalStackNavigator({ + [SCREENS.DEBUG.REPORT]: () => require('../../../../pages/debug/Report/DebugReportPage').default, + [SCREENS.DEBUG.REPORT_ACTION]: () => require('../../../../pages/debug/Report/DebugReportActionPage').default, + [SCREENS.DEBUG.REPORT_ACTION_CREATE]: () => require('../../../../pages/debug/Report/DebugReportActionCreatePage').default, +}); + export { AddPersonalBankAccountModalStackNavigator, EditRequestStackNavigator, @@ -604,4 +611,5 @@ export { SearchAdvancedFiltersModalStackNavigator, SearchSavedSearchModalStackNavigator, MissingPersonalDetailsModalStackNavigator, + DebugModalStackNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index 481df0b1c9ad5..cd6a93165990c 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -83,6 +83,10 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { name={SCREENS.RIGHT_MODAL.PROFILE} component={ModalStackNavigators.ProfileModalStackNavigator} /> + ['config'] = { [SCREENS.RIGHT_MODAL.MISSING_PERSONAL_DETAILS]: { screens: { [SCREENS.MISSING_PERSONAL_DETAILS_ROOT]: ROUTES.MISSING_PERSONAL_DETAILS.route, + }, + }, + [SCREENS.RIGHT_MODAL.DEBUG]: { + screens: { + [SCREENS.DEBUG.REPORT]: { + path: ROUTES.DEBUG_REPORT.route, + exact: true, + screens: { + details: { + path: ROUTES.DEBUG_REPORT_TAB_DETAILS.route, + exact: true, + }, + json: { + path: ROUTES.DEBUG_REPORT_TAB_JSON.route, + exact: true, + }, + actions: { + path: ROUTES.DEBUG_REPORT_TAB_ACTIONS.route, + exact: true, + }, + }, + }, + [SCREENS.DEBUG.REPORT_ACTION]: { + path: ROUTES.DEBUG_REPORT_ACTION.route, + exact: true, + screens: { + details: { + path: ROUTES.DEBUG_REPORT_ACTION_TAB_DETAILS.route, + exact: true, + }, + json: { + path: ROUTES.DEBUG_REPORT_ACTION_TAB_JSON.route, + exact: true, + }, + }, + }, + [SCREENS.DEBUG.REPORT_ACTION_CREATE]: { + path: ROUTES.DEBUG_REPORT_ACTION_CREATE.route, + exact: true, + }, }, }, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 9f76814740d3a..4bc524bc4b90a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1190,6 +1190,7 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.SEARCH_ADVANCED_FILTERS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.SEARCH_SAVED_SEARCH]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.MISSING_PERSONAL_DETAILS]: NavigatorScreenParams; + [SCREENS.RIGHT_MODAL.DEBUG]: NavigatorScreenParams; }; type TravelNavigatorParamList = { @@ -1429,6 +1430,19 @@ type MissingPersonalDetailsParamList = { }; }; +type DebugParamList = { + [SCREENS.DEBUG.REPORT]: { + reportID: string; + }; + [SCREENS.DEBUG.REPORT_ACTION]: { + reportID: string; + reportActionID: string; + }; + [SCREENS.DEBUG.REPORT_ACTION_CREATE]: { + reportID: string; + }; +}; + type RootStackParamList = PublicScreensParamList & AuthScreensParamList & LeftModalNavigatorParamList; type BottomTabName = keyof BottomTabNavigatorParamList; @@ -1502,4 +1516,5 @@ export type { SearchSavedSearchParamList, RestrictedActionParamList, MissingPersonalDetailsParamList, + DebugParamList, }; diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 4f85bbdcd1394..eb78da49cdda3 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -1327,6 +1327,10 @@ function requestRefund() { API.write(WRITE_COMMANDS.REQUEST_REFUND, null); } +function setIsDebugModeEnabled(isDebugModeEnabled: boolean) { + Onyx.merge(ONYXKEYS.USER, {isDebugModeEnabled}); +} + export { clearFocusModeNotification, closeAccount, @@ -1366,4 +1370,5 @@ export { addPendingContactMethod, clearValidateCodeActionError, dismissGBRTooltip, + setIsDebugModeEnabled, }; diff --git a/src/pages/Debug/DebugDetails.tsx b/src/pages/Debug/DebugDetails.tsx new file mode 100644 index 0000000000000..ab786acc5b6c4 --- /dev/null +++ b/src/pages/Debug/DebugDetails.tsx @@ -0,0 +1,102 @@ +import React, {useState} from 'react'; +import type {OnyxEntry, OnyxKey} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import Button from '@components/Button'; +import ScrollView from '@components/ScrollView'; +import TextInput from '@components/TextInput'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {OnyxValues, OnyxKey as OnyxValuesKey} from '@src/ONYXKEYS'; + +type DebugDetailsProps = { + data: OnyxEntry; + onyxKey: OnyxKey; + isCollection?: boolean; + idSelector?: (data: OnyxEntry>) => string; +}; + +function DebugDetails({data, onyxKey, isCollection = false, idSelector = () => ''}: DebugDetailsProps) { + const styles = useThemeStyles(); + const [draftData, setDraftData] = useState>(data); + const [errors, setErrors] = useState>({}); + const [hasChanges, setHasChanges] = useState(false); + return ( + + {Object.entries(data ?? {}).map(([key, value]) => ( + { + setDraftData((currentDraftData) => { + if (typeof currentDraftData === 'object') { + return {...currentDraftData, [key]: updatedValue}; + } + return updatedValue; + }); + setHasChanges(true); + }} + /> + ))} +