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 78b01d67b78e..554560b014d7 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 04cc8a125fbb..d169e98e7a28 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 2369e231f519..418ae00280c8 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 a9e223e56632..7e5f4dc156be 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 7fca66b5f8c7..1bf753cd4aa4 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 2c553386aff0..ce044b4c6198 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 a7ddea880161..bdd2990b299b 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 a8481ec305fc..6e3a7bb23a63 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 b4b676bbdec5..8635adde6456 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 481df0b1c9ad..cd6a93165990 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 9f76814740d3..4bc524bc4b90 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 4f85bbdcd139..eb78da49cdda 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 000000000000..ab786acc5b6c --- /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); + }} + /> + ))} +