From ece998d26fe90da12b61ffe3a869189a635eb79b Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 15 Oct 2024 15:27:55 +0200 Subject: [PATCH 001/111] android support, ios wip --- src/libs/actions/Delegate.ts | 7 +++++++ src/libs/actions/Session/index.ts | 22 +++++++++++++++------- src/types/modules/react-native.d.ts | 1 + 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index 06d7093df385..b75801779f5b 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -1,3 +1,4 @@ +import {NativeModules} from 'react-native'; import Onyx from 'react-native-onyx'; import type {OnyxUpdate} from 'react-native-onyx'; import * as API from '@libs/API'; @@ -6,6 +7,7 @@ import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import Log from '@libs/Log'; import * as NetworkStore from '@libs/Network/NetworkStore'; +import {getCurrentUserEmail} from '@libs/Network/NetworkStore'; import * as SequentialQueue from '@libs/Network/SequentialQueue'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -97,6 +99,8 @@ function connect(email: string) { NetworkStore.setAuthToken(response?.restrictedToken ?? null); confirmReadyToOpenApp(); openApp(); + + NativeModules.HybridAppModule.switchAccounts(email); }); }) .catch((error) => { @@ -160,6 +164,8 @@ function disconnect() { NetworkStore.setAuthToken(response?.authToken ?? null); confirmReadyToOpenApp(); openApp(); + + NativeModules.HybridAppModule.switchAccounts(getCurrentUserEmail() ?? ''); }); }) .catch((error) => { @@ -573,4 +579,5 @@ export { clearDelegateRolePendingAction, updateDelegateRole, removeDelegate, + KEYS_TO_PRESERVE_DELEGATE_ACCESS, }; diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 4d6ba6cfa774..4d621c15d39f 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -40,6 +40,7 @@ import Timers from '@libs/Timers'; import {hideContextMenu} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import {KEYS_TO_PRESERVE, openApp} from '@userActions/App'; import * as App from '@userActions/App'; +import {KEYS_TO_PRESERVE_DELEGATE_ACCESS} from '@userActions/Delegate'; import * as Device from '@userActions/Device'; import * as PriorityMode from '@userActions/PriorityMode'; import redirectToSignIn from '@userActions/SignInRedirect'; @@ -482,19 +483,26 @@ function signUpUser() { function signInAfterTransitionFromOldDot(transitionURL: string) { const [route, queryParams] = transitionURL.split('?'); - const {email, authToken, encryptedAuthToken, accountID, autoGeneratedLogin, autoGeneratedPassword, clearOnyxOnStart, completedHybridAppOnboarding} = Object.fromEntries( - queryParams.split('&').map((param) => { - const [key, value] = param.split('='); - return [key, value]; - }), - ); + const {email, authToken, encryptedAuthToken, accountID, autoGeneratedLogin, autoGeneratedPassword, clearOnyxOnStart, completedHybridAppOnboarding, shouldRemoveDelegatedAccess} = + Object.fromEntries( + queryParams.split('&').map((param) => { + const [key, value] = param.split('='); + return [key, value]; + }), + ); const setSessionDataAndOpenApp = () => { Onyx.multiSet({ [ONYXKEYS.SESSION]: {email, authToken, encryptedAuthToken: decodeURIComponent(encryptedAuthToken), accountID: Number(accountID)}, [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin, autoGeneratedPassword}, [ONYXKEYS.NVP_TRYNEWDOT]: {classicRedirect: {completedHybridAppOnboarding: completedHybridAppOnboarding === 'true'}}, - }).then(App.openApp); + }).then(() => { + if (shouldRemoveDelegatedAccess) { + Onyx.clear(KEYS_TO_PRESERVE_DELEGATE_ACCESS).then(App.openApp); + } else { + App.openApp(); + } + }); }; if (clearOnyxOnStart === 'true') { diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index 40c5b72ce1e4..1e4a942deb04 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -8,6 +8,7 @@ import type StartupTimer from '@libs/StartupTimer/types'; type HybridAppModule = { closeReactNativeApp: (shouldSignOut: boolean, shouldSetNVP: boolean) => void; completeOnboarding: (status: boolean) => void; + switchAccounts: (newDotCurrentAccount: string) => void; exitApp: () => void; }; From 54f0842cbee9356f20ecb15e6909f116f66cdbed Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Tue, 15 Oct 2024 16:24:25 -0400 Subject: [PATCH 002/111] check for new authTokens --- src/libs/actions/OnyxUpdateManager/index.ts | 33 +++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/OnyxUpdateManager/index.ts b/src/libs/actions/OnyxUpdateManager/index.ts index db00a55aa25b..085e05b0a449 100644 --- a/src/libs/actions/OnyxUpdateManager/index.ts +++ b/src/libs/actions/OnyxUpdateManager/index.ts @@ -1,11 +1,13 @@ -import type {OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as ActiveClientManager from '@libs/ActiveClientManager'; import Log from '@libs/Log'; +import * as NetworkStore from '@libs/Network/NetworkStore'; import * as SequentialQueue from '@libs/Network/SequentialQueue'; import * as App from '@userActions/App'; +import updateSessionAuthTokens from '@userActions/Session/updateSessionAuthTokens'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxUpdatesFromServer} from '@src/types/onyx'; +import type {OnyxUpdatesFromServer, Session} from '@src/types/onyx'; import {isValidOnyxUpdateFromServer} from '@src/types/onyx/OnyxUpdatesFromServer'; import * as OnyxUpdateManagerUtils from './utils'; import * as DeferredOnyxUpdates from './utils/DeferredOnyxUpdates'; @@ -90,6 +92,10 @@ function handleOnyxUpdateGap(onyxUpdatesFromServer: OnyxEntry): void { + // Consolidate all of the given Onyx updates + const onyxUpdates: OnyxUpdate[] = []; + onyxUpdatesFromServer?.updates?.forEach((updateEvent) => onyxUpdates.push(...updateEvent.data)); + onyxUpdates.push(...(onyxUpdatesFromServer?.response?.onyxData ?? [])); + + // Find any session updates + const sessionUpdates = onyxUpdates?.filter((onyxUpdate) => onyxUpdate.key === ONYXKEYS.SESSION); + + // If any of the updates changes the authToken, let's update it now + sessionUpdates?.forEach((sessionUpdate) => { + const session = (sessionUpdate.value ?? {}) as Session; + const newAuthToken = session.authToken ?? ''; + if (!newAuthToken) { + return; + } + + Log.info('[OnyxUpdateManager] Found an authToken update while handling an Onyx update gap. Updating the authToken.'); + updateSessionAuthTokens(newAuthToken); + NetworkStore.setAuthToken(newAuthToken); + }); +} + export default () => { console.debug('[OnyxUpdateManager] Listening for updates from the server'); Onyx.connect({ From b4093f2c3a6908cc0e37ead4bb215ca680d860ef Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Tue, 15 Oct 2024 17:31:50 -0400 Subject: [PATCH 003/111] navigate to the enable payments page after validating --- src/components/SettlementButton/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index f371545ab7b0..ff522a494b1c 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -190,7 +190,7 @@ function SettlementButton({ if (iouPaymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || iouPaymentType === CONST.IOU.PAYMENT_TYPE.VBBA) { if (!isUserValidated) { - Navigation.navigate(ROUTES.SETTINGS_WALLET_VERIFY_ACCOUNT.getRoute()); + Navigation.navigate(ROUTES.SETTINGS_WALLET_VERIFY_ACCOUNT.getRoute(ROUTES.SETTINGS_ENABLE_PAYMENTS)); return; } triggerKYCFlow(event, iouPaymentType); From 92a86acf372ce0bdd141cf43b687f951ca100d62 Mon Sep 17 00:00:00 2001 From: war-in Date: Wed, 16 Oct 2024 13:00:15 +0200 Subject: [PATCH 004/111] fix typo --- src/libs/actions/Delegate.ts | 4 ++-- src/types/modules/react-native.d.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index b75801779f5b..af1dd7600b04 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -100,7 +100,7 @@ function connect(email: string) { confirmReadyToOpenApp(); openApp(); - NativeModules.HybridAppModule.switchAccounts(email); + NativeModules.HybridAppModule.switchAccount(email); }); }) .catch((error) => { @@ -165,7 +165,7 @@ function disconnect() { confirmReadyToOpenApp(); openApp(); - NativeModules.HybridAppModule.switchAccounts(getCurrentUserEmail() ?? ''); + NativeModules.HybridAppModule.switchAccount(getCurrentUserEmail() ?? ''); }); }) .catch((error) => { diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index 1e4a942deb04..a08e17c28f4a 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -8,7 +8,7 @@ import type StartupTimer from '@libs/StartupTimer/types'; type HybridAppModule = { closeReactNativeApp: (shouldSignOut: boolean, shouldSetNVP: boolean) => void; completeOnboarding: (status: boolean) => void; - switchAccounts: (newDotCurrentAccount: string) => void; + switchAccount: (newDotCurrentAccount: string) => void; exitApp: () => void; }; From 4374ed18dd160a238f88a943c3e4b81625492430 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Thu, 17 Oct 2024 16:41:23 -0400 Subject: [PATCH 005/111] use validateLogin --- src/pages/settings/Wallet/VerifyAccountPage.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Wallet/VerifyAccountPage.tsx b/src/pages/settings/Wallet/VerifyAccountPage.tsx index e375f03ba58c..484ebbb4225e 100644 --- a/src/pages/settings/Wallet/VerifyAccountPage.tsx +++ b/src/pages/settings/Wallet/VerifyAccountPage.tsx @@ -32,6 +32,7 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) { const styles = useThemeStyles(); const validateLoginError = ErrorUtils.getEarliestErrorField(loginData, 'validateLogin'); const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); + const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID ?? 0}); const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE); @@ -43,10 +44,10 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) { }, []); const handleSubmitForm = useCallback( - (submitCode: string) => { - User.validateSecondaryLogin(loginList, contactMethod ?? '', submitCode); + (validateCode: string) => { + User.validateLogin(accountID ?? 0, validateCode); }, - [loginList, contactMethod], + [accountID], ); const clearError = useCallback(() => { From 81b4a2f7feaa8c5c4464af7b4ae6750b49502e8f Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Thu, 17 Oct 2024 17:47:29 -0400 Subject: [PATCH 006/111] check both user and account validated state --- src/pages/settings/Wallet/VerifyAccountPage.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pages/settings/Wallet/VerifyAccountPage.tsx b/src/pages/settings/Wallet/VerifyAccountPage.tsx index 484ebbb4225e..ff0fa1ba3cb2 100644 --- a/src/pages/settings/Wallet/VerifyAccountPage.tsx +++ b/src/pages/settings/Wallet/VerifyAccountPage.tsx @@ -22,6 +22,7 @@ type VerifyAccountPageProps = StackScreenProps !!user?.validated}); const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID ?? 0}); - const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE); + // We store validated state in two places so this is a bit of a workaround to check both + const isUserValidated = user?.validated ?? false; + const isAccountValidated = account?.validated ?? false; + const isValidated = isUserValidated || isAccountValidated; + const navigateBackTo = route?.params?.backTo ?? ROUTES.SETTINGS_WALLET; useEffect(() => { @@ -55,11 +59,11 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) { }, [contactMethod]); useEffect(() => { - if (!isUserValidated) { + if (!isValidated) { return; } Navigation.navigate(navigateBackTo); - }, [isUserValidated, navigateBackTo]); + }, [isValidated, navigateBackTo]); return ( Date: Sat, 26 Oct 2024 10:30:37 +1300 Subject: [PATCH 007/111] Fix KeyboardAvoidingView not aware of the keyboard closing after it is unmounted --- ...ive+0.75.2+018+keyboard-avoiding-view.patch | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 patches/react-native+0.75.2+018+keyboard-avoiding-view.patch diff --git a/patches/react-native+0.75.2+018+keyboard-avoiding-view.patch b/patches/react-native+0.75.2+018+keyboard-avoiding-view.patch new file mode 100644 index 000000000000..2ee8aa1fd0de --- /dev/null +++ b/patches/react-native+0.75.2+018+keyboard-avoiding-view.patch @@ -0,0 +1,18 @@ +diff --git a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js +index e26d677..597be5a 100644 +--- a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js ++++ b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js +@@ -175,6 +175,13 @@ class KeyboardAvoidingView extends React.Component { + } + + componentDidMount(): void { ++ // Fix KeyboardAvoidingView not aware of the keyboard closing after it is unmounted. ++ // Remove this patch after the upstream fix https://github.com/facebook/react-native/commit/08bd8ac47da60121225e7b281bbf566e2c5a291e is released. ++ if (!Keyboard.isVisible()) { ++ this._keyboardEvent = null; ++ this._setBottom(0); ++ } ++ + if (Platform.OS === 'ios') { + this._subscriptions = [ + Keyboard.addListener('keyboardWillChangeFrame', this._onKeyboardChange), From 635475acba22c1b522fae510feb549d1abe50fc8 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 28 Oct 2024 18:28:17 +0100 Subject: [PATCH 008/111] Hide add payment card button once a card is added --- src/libs/SubscriptionUtils.ts | 8 +------- src/pages/home/report/ReportActionItem.tsx | 3 ++- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index f2ceef9069fa..749f3937df33 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -116,12 +116,6 @@ Onyx.connect({ callback: (value) => (lastDayFreeTrial = value), }); -let userBillingFundID: OnyxEntry; -Onyx.connect({ - key: ONYXKEYS.NVP_BILLING_FUND_ID, - callback: (value) => (userBillingFundID = value), -}); - let userBillingGraceEndPeriodCollection: OnyxCollection; Onyx.connect({ key: ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END, @@ -428,7 +422,7 @@ function hasUserFreeTrialEnded(): boolean { * Whether the user has a payment card added to its account. */ function doesUserHavePaymentCardAdded(): boolean { - return userBillingFundID !== undefined; + return getCardForSubscriptionBilling() !== undefined; } /** diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 0af9bd61120d..d0b33ee9f801 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -53,6 +53,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import SelectionScraper from '@libs/SelectionScraper'; import shouldRenderAddPaymentCard from '@libs/shouldRenderAppPaymentCard'; +import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; import * as BankAccounts from '@userActions/BankAccounts'; @@ -400,7 +401,7 @@ function ReportActionItem({ const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); const actionableItemButtons: ActionableItem[] = useMemo(() => { - if (ReportActionsUtils.isActionableAddPaymentCard(action) && shouldRenderAddPaymentCard()) { + if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded()) { return [ { text: 'subscription.cardSection.addCardButton', From 3eaf2cde6f1cb103f1f18e6aa98827ec335964d6 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 28 Oct 2024 18:46:50 +0100 Subject: [PATCH 009/111] Revert change. Fix in the backend instead and send NVP_BILLING_FUND_ID --- src/libs/SubscriptionUtils.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index 749f3937df33..f2ceef9069fa 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -116,6 +116,12 @@ Onyx.connect({ callback: (value) => (lastDayFreeTrial = value), }); +let userBillingFundID: OnyxEntry; +Onyx.connect({ + key: ONYXKEYS.NVP_BILLING_FUND_ID, + callback: (value) => (userBillingFundID = value), +}); + let userBillingGraceEndPeriodCollection: OnyxCollection; Onyx.connect({ key: ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END, @@ -422,7 +428,7 @@ function hasUserFreeTrialEnded(): boolean { * Whether the user has a payment card added to its account. */ function doesUserHavePaymentCardAdded(): boolean { - return getCardForSubscriptionBilling() !== undefined; + return userBillingFundID !== undefined; } /** From 69d2b3835ce0ce57001e6f6fb587181a461a40eb Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 28 Oct 2024 19:04:43 +0100 Subject: [PATCH 010/111] Keep shouldRenderAddPaymentCard as we use it to not render the button in native --- src/pages/home/report/ReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index d0b33ee9f801..b3714b195b25 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -401,7 +401,7 @@ function ReportActionItem({ const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); const actionableItemButtons: ActionableItem[] = useMemo(() => { - if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded()) { + if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded() && shouldRenderAddPaymentCard()) { return [ { text: 'subscription.cardSection.addCardButton', From 87f6644cb83af0dbe23851d7b3aaa9c053fdca72 Mon Sep 17 00:00:00 2001 From: truph01 Date: Tue, 29 Oct 2024 17:46:03 +0700 Subject: [PATCH 011/111] =?UTF-8?q?fix:=20Inbox=20showing=20GBR=20when=20t?= =?UTF-8?q?here=E2=80=99s=20a=20report=20with=20RBR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libs/WorkspacesSettingsUtils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index a27d518fe727..b16d0e2b17c8 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -62,6 +62,14 @@ const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection const reportErrors = ReportUtils.getAllReportErrors(report, reportActions); const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions); let doesReportContainErrors = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; + const parentReportActions = (altReportActions ?? allReportActions)?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`]; + const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; + const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(report, allTransactionViolations, parentReportAction); + const shouldDisplayReportViolations = ReportUtils.isReportOwner(report) && ReportUtils.hasReportViolations(report.reportID); + const hasViolations = shouldDisplayViolations || shouldDisplayReportViolations; + if (hasViolations) { + doesReportContainErrors = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; + } if (oneTransactionThreadReportID) { const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); From 6d00a658f563dd4e7d8e6774abc7ba064f3184d0 Mon Sep 17 00:00:00 2001 From: truph01 Date: Thu, 31 Oct 2024 09:20:38 +0700 Subject: [PATCH 012/111] fix: optimize code --- src/libs/WorkspacesSettingsUtils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index b16d0e2b17c8..0b88c80ea9e7 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -62,16 +62,17 @@ const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection const reportErrors = ReportUtils.getAllReportErrors(report, reportActions); const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions); let doesReportContainErrors = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; + const parentReportActions = (altReportActions ?? allReportActions)?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`]; const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(report, allTransactionViolations, parentReportAction); const shouldDisplayReportViolations = ReportUtils.isReportOwner(report) && ReportUtils.hasReportViolations(report.reportID); const hasViolations = shouldDisplayViolations || shouldDisplayReportViolations; - if (hasViolations) { + if (hasViolations && !doesReportContainErrors) { doesReportContainErrors = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } - if (oneTransactionThreadReportID) { + if (oneTransactionThreadReportID && !doesReportContainErrors) { const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); if ( From 8b3bd1503a55b055bd680b22b658a95da8d1c66e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 1 Nov 2024 10:30:56 +0700 Subject: [PATCH 013/111] fix: Skip onboarding screen with company size if signupQualifier is VSB --- src/libs/actions/Welcome/OnboardingFlow.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Welcome/OnboardingFlow.ts b/src/libs/actions/Welcome/OnboardingFlow.ts index 9b7dfc894b6a..a6f9b45bb279 100644 --- a/src/libs/actions/Welcome/OnboardingFlow.ts +++ b/src/libs/actions/Welcome/OnboardingFlow.ts @@ -5,6 +5,8 @@ import linkingConfig from '@libs/Navigation/linkingConfig'; import getAdaptedStateFromPath from '@libs/Navigation/linkingConfig/getAdaptedStateFromPath'; import {navigationRef} from '@libs/Navigation/Navigation'; import type {RootStackParamList} from '@libs/Navigation/types'; +import * as Policy from '@userActions/Policy/Policy'; +import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -34,6 +36,17 @@ Onyx.connect({ }, }); +let onboardingPolicyID: string; +Onyx.connect({ + key: ONYXKEYS.ONBOARDING_POLICY_ID, + callback: (value) => { + if (value === undefined) { + return; + } + onboardingPolicyID = value; + }, +}); + /** * Start a new onboarding flow or continue from the last visited onboarding page. */ @@ -57,7 +70,13 @@ function getOnboardingInitialPath(): string { if (isVsb) { Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.MANAGE_TEAM); - return `/${ROUTES.ONBOARDING_EMPLOYEES.route}`; + Onyx.set(ONYXKEYS.ONBOARDING_COMPANY_SIZE, CONST.ONBOARDING_COMPANY_SIZE.MICRO); + if (!onboardingPolicyID) { + const {adminsChatReportID, policyID} = Policy.createWorkspace(undefined, true, '', Policy.generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM); + Welcome.setOnboardingAdminsChatReportID(adminsChatReportID); + Welcome.setOnboardingPolicyID(policyID); + } + return `/${ROUTES.ONBOARDING_ACCOUNTING.route}`; } const isIndividual = onboardingValues.signupQualifier === CONST.ONBOARDING_SIGNUP_QUALIFIERS.INDIVIDUAL; if (isIndividual) { From 616cef797a567bf6b4f75fffdb0bf9c6f1dc536d Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 1 Nov 2024 10:38:30 +0700 Subject: [PATCH 014/111] fix lint --- src/libs/actions/Welcome/OnboardingFlow.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Welcome/OnboardingFlow.ts b/src/libs/actions/Welcome/OnboardingFlow.ts index a6f9b45bb279..b0885e97de9c 100644 --- a/src/libs/actions/Welcome/OnboardingFlow.ts +++ b/src/libs/actions/Welcome/OnboardingFlow.ts @@ -6,7 +6,6 @@ import getAdaptedStateFromPath from '@libs/Navigation/linkingConfig/getAdaptedSt import {navigationRef} from '@libs/Navigation/Navigation'; import type {RootStackParamList} from '@libs/Navigation/types'; import * as Policy from '@userActions/Policy/Policy'; -import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -73,8 +72,8 @@ function getOnboardingInitialPath(): string { Onyx.set(ONYXKEYS.ONBOARDING_COMPANY_SIZE, CONST.ONBOARDING_COMPANY_SIZE.MICRO); if (!onboardingPolicyID) { const {adminsChatReportID, policyID} = Policy.createWorkspace(undefined, true, '', Policy.generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM); - Welcome.setOnboardingAdminsChatReportID(adminsChatReportID); - Welcome.setOnboardingPolicyID(policyID); + Onyx.set(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID, adminsChatReportID ?? null); + Onyx.set(ONYXKEYS.ONBOARDING_POLICY_ID, policyID ?? null); } return `/${ROUTES.ONBOARDING_ACCOUNTING.route}`; } From 50daec071c009da8e0ba371d9d2c2d4d873090ba Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 1 Nov 2024 15:43:42 +0700 Subject: [PATCH 015/111] move the logic create workspace to accounting page --- src/libs/actions/Welcome/OnboardingFlow.ts | 17 ----------------- .../BaseOnboardingAccounting.tsx | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/libs/actions/Welcome/OnboardingFlow.ts b/src/libs/actions/Welcome/OnboardingFlow.ts index b0885e97de9c..5a1b4fc0474a 100644 --- a/src/libs/actions/Welcome/OnboardingFlow.ts +++ b/src/libs/actions/Welcome/OnboardingFlow.ts @@ -5,7 +5,6 @@ import linkingConfig from '@libs/Navigation/linkingConfig'; import getAdaptedStateFromPath from '@libs/Navigation/linkingConfig/getAdaptedStateFromPath'; import {navigationRef} from '@libs/Navigation/Navigation'; import type {RootStackParamList} from '@libs/Navigation/types'; -import * as Policy from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -35,17 +34,6 @@ Onyx.connect({ }, }); -let onboardingPolicyID: string; -Onyx.connect({ - key: ONYXKEYS.ONBOARDING_POLICY_ID, - callback: (value) => { - if (value === undefined) { - return; - } - onboardingPolicyID = value; - }, -}); - /** * Start a new onboarding flow or continue from the last visited onboarding page. */ @@ -70,11 +58,6 @@ function getOnboardingInitialPath(): string { if (isVsb) { Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.MANAGE_TEAM); Onyx.set(ONYXKEYS.ONBOARDING_COMPANY_SIZE, CONST.ONBOARDING_COMPANY_SIZE.MICRO); - if (!onboardingPolicyID) { - const {adminsChatReportID, policyID} = Policy.createWorkspace(undefined, true, '', Policy.generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM); - Onyx.set(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID, adminsChatReportID ?? null); - Onyx.set(ONYXKEYS.ONBOARDING_POLICY_ID, policyID ?? null); - } return `/${ROUTES.ONBOARDING_ACCOUNTING.route}`; } const isIndividual = onboardingValues.signupQualifier === CONST.ONBOARDING_SIGNUP_QUALIFIERS.INDIVIDUAL; diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index ef5a58caf4ea..a6af41d9ddd2 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -1,4 +1,4 @@ -import React, {useMemo, useState} from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; @@ -20,6 +20,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import navigateAfterOnboarding from '@libs/navigateAfterOnboarding'; import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; +import * as Policy from '@userActions/Policy/Policy'; import * as Report from '@userActions/Report'; import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; @@ -41,6 +42,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding // We need to use isSmallScreenWidth, see navigateAfterOnboarding function comment // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {onboardingIsMediumOrLargerScreenWidth, isSmallScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); + const [onboardingValues] = useOnyx(ONYXKEYS.NVP_ONBOARDING); const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID); const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID); @@ -50,6 +52,17 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding const [userReportedIntegration, setUserReportedIntegration] = useState(undefined); const [error, setError] = useState(''); + const isVsb = onboardingValues && 'signupQualifier' in onboardingValues && onboardingValues.signupQualifier === CONST.ONBOARDING_SIGNUP_QUALIFIERS.VSB; + + useEffect(() => { + if (!isVsb || !!onboardingPolicyID) { + return; + } + + const {adminsChatReportID, policyID} = Policy.createWorkspace(undefined, true, '', Policy.generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM); + Welcome.setOnboardingAdminsChatReportID(adminsChatReportID); + Welcome.setOnboardingPolicyID(policyID); + }, [isVsb, onboardingPolicyID]); const accountingOptions: OnboardingListItem[] = useMemo(() => { const policyAccountingOptions = Object.values(CONST.POLICY.CONNECTIONS.NAME) From f4de5a0aa09cfa0adaba562c57efcca6c38da1d6 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 1 Nov 2024 15:45:16 +0700 Subject: [PATCH 016/111] add comment --- src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index a6af41d9ddd2..63e4b435bcd3 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -54,6 +54,8 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding const [error, setError] = useState(''); const isVsb = onboardingValues && 'signupQualifier' in onboardingValues && onboardingValues.signupQualifier === CONST.ONBOARDING_SIGNUP_QUALIFIERS.VSB; + // If the signupQualifier is VSB, the company size step is skip. + // So we need to create the new workspace in the accounting step useEffect(() => { if (!isVsb || !!onboardingPolicyID) { return; From 1604913877f97eb083e2e857848a420eddb87c3d Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Fri, 1 Nov 2024 20:29:10 +0700 Subject: [PATCH 017/111] fix image not displayed when tapped on search page --- src/CONST.ts | 1 + src/ROUTES.ts | 6 ++++-- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx | 8 +++++++- src/libs/Navigation/types.ts | 1 + src/pages/home/report/ReportAttachments.tsx | 2 ++ 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index e793ea70cfc8..0cc426eba250 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -192,6 +192,7 @@ const CONST = { DEFAULT_TABLE_NAME: 'keyvaluepairs', DEFAULT_ONYX_DUMP_FILE_NAME: 'onyx-state.txt', DEFAULT_POLICY_ROOM_CHAT_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL], + DEFAULT_IMAGE_FILE_NAME: 'image', DISABLED_MAX_EXPENSE_VALUE: 10000000000, POLICY_BILLABLE_MODES: { BILLABLE: 'billable', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 45501bf46374..6b3bbc05111c 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -310,11 +310,13 @@ const ROUTES = { }, ATTACHMENTS: { route: 'attachment', - getRoute: (reportID: string, type: ValueOf, url: string, accountID?: number, isAuthTokenRequired?: boolean) => { + getRoute: (reportID: string, type: ValueOf, url: string, accountID?: number, isAuthTokenRequired?: boolean, fileName?: string) => { const reportParam = reportID ? `&reportID=${reportID}` : ''; const accountParam = accountID ? `&accountID=${accountID}` : ''; const authTokenParam = isAuthTokenRequired ? '&isAuthTokenRequired=true' : ''; - return `attachment?source=${encodeURIComponent(url)}&type=${type}${reportParam}${accountParam}${authTokenParam}` as const; + const fileNameParam = fileName ? `&fileName=${fileName}` : ''; + + return `attachment?source=${encodeURIComponent(url)}&type=${type}${reportParam}${accountParam}${authTokenParam}${fileNameParam}` as const; }, }, REPORT_PARTICIPANTS: { diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 17fbe1656020..06512f04bb95 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -70,6 +70,12 @@ function ImageRenderer({tnode}: ImageRendererProps) { const [hasLoadFailed, setHasLoadFailed] = useState(true); const theme = useTheme(); + let fileName = htmlAttribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || FileUtils.getFileName(`${isAttachmentOrReceipt ? attachmentSourceAttribute : htmlAttribs.src}`); + const fileInfo = FileUtils.splitExtensionFromFileName(fileName); + if (!fileInfo.fileExtension) { + fileName = `${fileInfo.fileName || CONST.DEFAULT_IMAGE_FILE_NAME}.jpg`; + } + const thumbnailImageComponent = ( { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3eae46ac2855..07f2fac2e46a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1549,6 +1549,7 @@ type AuthScreensParamList = CentralPaneScreensParamList & type: ValueOf; accountID: string; isAuthTokenRequired?: string; + fileName?: string; }; [SCREENS.PROFILE_AVATAR]: { accountID: string; diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index d30d8e9aabc1..c4895de3fea5 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -20,6 +20,7 @@ function ReportAttachments({route}: ReportAttachmentsProps) { const isAuthTokenRequired = route.params.isAuthTokenRequired; const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID || -1}`); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const fileName = route.params.fileName; // In native the imported images sources are of type number. Ref: https://reactnative.dev/docs/image#imagesource const source = Number(route.params.source) || route.params.source; @@ -48,6 +49,7 @@ function ReportAttachments({route}: ReportAttachmentsProps) { onCarouselAttachmentChange={onCarouselAttachmentChange} shouldShowNotFoundPage={!isLoadingApp && type !== CONST.ATTACHMENT_TYPE.SEARCH && !report?.reportID} isAuthTokenRequired={!!isAuthTokenRequired} + originalFileName={fileName ?? ''} /> ); } From 4b8f300a73eb401de7f6bb1a5e4e857430eafbaa Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Fri, 1 Nov 2024 15:44:05 -0300 Subject: [PATCH 018/111] Move RBR to workspace chats instead of transaction threads --- .../LHNOptionsList/OptionRowLHNData.tsx | 2 +- src/libs/DebugUtils.ts | 3 +- src/libs/OptionsListUtils.ts | 3 +- src/libs/ReportUtils.ts | 79 ++++++------------- src/libs/SidebarUtils.ts | 11 +-- src/libs/WorkspacesSettingsUtils.ts | 8 +- src/pages/Debug/Report/DebugReportPage.tsx | 2 +- 7 files changed, 34 insertions(+), 74 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 3c40210a5d99..48b7f1c75088 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -37,7 +37,7 @@ function OptionRowLHNData({ const optionItemRef = useRef(); - const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(fullReport, transactionViolations, parentReportAction); + const shouldDisplayViolations = ReportUtils.shouldDisplayViolationsRBR(fullReport, transactionViolations); const shouldDisplayReportViolations = ReportUtils.isReportOwner(fullReport) && ReportUtils.hasReportViolations(reportID); const optionItem = useMemo(() => { diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index d63758761c3c..976a23c17323 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -7,6 +7,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Beta, Policy, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; import * as ReportUtils from './ReportUtils'; +import {shouldDisplayViolationsRBR} from './ReportUtils'; import SidebarUtils from './SidebarUtils'; class NumberError extends SyntaxError { @@ -597,7 +598,7 @@ function getReasonForShowingRowInLHN(report: OnyxEntry, hasRBR = false): return null; } - const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBR(report, transactionViolations); const reason = ReportUtils.reasonForReportToBeInOptionList({ report, diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index f414d2328ef6..f304f361fded 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -55,6 +55,7 @@ import * as PhoneNumber from './PhoneNumber'; import * as PolicyUtils from './PolicyUtils'; import * as ReportActionUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; +import {shouldDisplayViolationsRBR} from './ReportUtils'; import * as TaskUtils from './TaskUtils'; import * as TransactionUtils from './TransactionUtils'; import * as UserUtils from './UserUtils'; @@ -1797,7 +1798,7 @@ function getOptions( // Filter out all the reports that shouldn't be displayed const filteredReportOptions = options.reports.filter((option) => { const report = option.item; - const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBR(report, transactionViolations); return ReportUtils.shouldReportBeInOptionList({ report, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 223e94e0bdde..5db585e720c9 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6229,49 +6229,37 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b } /** - * Checks to see if a report's parentAction is an expense that contains a violation type of either violation or warning + * Should we display a RBR on this report due to violations? */ -function doesTransactionThreadHaveViolations( - report: OnyxInputOrEntry, - transactionViolations: OnyxCollection, - parentReportAction: OnyxInputOrEntry, -): boolean { - if (!ReportActionsUtils.isMoneyRequestAction(parentReportAction)) { - return false; - } - const {IOUTransactionID, IOUReportID} = ReportActionsUtils.getOriginalMessage(parentReportAction) ?? {}; - if (!IOUTransactionID || !IOUReportID) { - return false; - } - if (!isCurrentUserSubmitter(IOUReportID)) { +function shouldDisplayViolationsRBR(report: OnyxEntry, transactionViolations: OnyxCollection): boolean { + // We only show the RBR in the highest level, which is the workspace chat + if (!isPolicyExpenseChat(report)) { return false; } - if (report?.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report?.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED) { + + // We only show the RBR to the submitter + if (!isCurrentUserSubmitter(report?.reportID ?? '')) { return false; } - return ( - TransactionUtils.hasViolation(IOUTransactionID, transactionViolations) || - TransactionUtils.hasWarningTypeViolation(IOUTransactionID, transactionViolations) || - (isPaidGroupPolicy(report) && TransactionUtils.hasModifiedAmountOrDateViolation(IOUTransactionID, transactionViolations)) - ); -} -/** - * Checks if we should display violation - we display violations when the expense has violation and it is not settled - */ -function shouldDisplayTransactionThreadViolations( - report: OnyxEntry, - transactionViolations: OnyxCollection, - parentReportAction: OnyxEntry, -): boolean { - if (!ReportActionsUtils.isMoneyRequestAction(parentReportAction)) { + // We need to get all reports to check the ones inside this workspace chat, if we have no reports, then there's no RBR + const allReports = Object.values(ReportConnection.getAllReports()); + if (!allReports) { return false; } - const {IOUReportID} = ReportActionsUtils.getOriginalMessage(parentReportAction) ?? {}; - if (isSettled(IOUReportID) || isReportApproved(IOUReportID?.toString())) { - return false; + + // Now get all potential reports, which are the ones that are: + // - Owned by the same user + // - Are either open or submitted + // - Belong to the same workspace + // And if any have a violation, then it should have a RBR + const potentialReports = allReports.filter((r: Report) => r?.ownerAccountID === currentUserAccountID && r?.stateNum <= 1 && r?.policyID === report.policyID); + for (const potentialReport of potentialReports) { + if (hasViolations(potentialReport.reportID, transactionViolations) || hasWarningTypeViolations(potentialReport.reportID, transactionViolations)) { + return true; + } } - return doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); + return false; } /** @@ -6309,23 +6297,6 @@ function shouldAdminsRoomBeVisible(report: OnyxEntry): boolean { return true; } -/** - * Check whether report has violations - */ -function shouldShowViolations(report: Report, transactionViolations: OnyxCollection) { - const {parentReportID, parentReportActionID} = report ?? {}; - const canGetParentReport = parentReportID && parentReportActionID && allReportActions; - if (!canGetParentReport) { - return false; - } - const parentReportActions = allReportActions ? allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? {} : {}; - const parentReportAction = parentReportActions[parentReportActionID] ?? null; - if (!parentReportAction) { - return false; - } - return shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction); -} - type ReportErrorsAndReportActionThatRequiresAttention = { errors: ErrorFields; reportAction?: OnyxEntry; @@ -6406,7 +6377,7 @@ function hasReportErrorsOtherThanFailedReceipt(report: Report, doesReportHaveVio let doesTransactionThreadReportHasViolations = false; if (oneTransactionThreadReportID) { const transactionReport = getReport(oneTransactionThreadReportID); - doesTransactionThreadReportHasViolations = !!transactionReport && shouldShowViolations(transactionReport, transactionViolations); + doesTransactionThreadReportHasViolations = !!transactionReport && shouldDisplayViolationsRBR(transactionReport, transactionViolations); } return ( doesTransactionThreadReportHasViolations || @@ -8387,7 +8358,6 @@ export { chatIncludesConcierge, createDraftTransactionAndNavigateToParticipantSelector, doesReportBelongToWorkspace, - doesTransactionThreadHaveViolations, findLastAccessedReport, findSelfDMReportID, formatReportLastMessageText, @@ -8591,7 +8561,7 @@ export { shouldDisableRename, shouldDisableThread, shouldDisplayThreadReplies, - shouldDisplayTransactionThreadViolations, + shouldDisplayViolationsRBR, shouldReportBeInOptionList, shouldReportShowSubscript, shouldShowFlagComment, @@ -8638,7 +8608,6 @@ export { getReasonAndReportActionThatRequiresAttention, isPolicyRelatedReport, hasReportErrorsOtherThanFailedReceipt, - shouldShowViolations, getAllReportErrors, getAllReportActionsErrorsAndReportActionThatRequiresAttention, }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 7be24d4ee691..54eb4134b6ba 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -22,6 +22,7 @@ import Parser from './Parser'; import * as PolicyUtils from './PolicyUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; +import {shouldDisplayViolationsRBR} from './ReportUtils'; import * as TaskUtils from './TaskUtils'; type WelcomeMessage = {showReportName: boolean; phrase1?: string; phrase2?: string; phrase3?: string; messageText?: string; messageHtml?: string}; @@ -107,7 +108,7 @@ function getOrderedReportIDs( return; } const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); - const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBR(report, transactionViolations); const isHidden = ReportUtils.getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; const hasErrorsOtherThanFailedReceipt = ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations); @@ -241,13 +242,7 @@ function getReasonAndReportActionThatHasRedBrickRoad( if (oneTransactionThreadReportID) { const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); - if ( - ReportUtils.shouldDisplayTransactionThreadViolations( - oneTransactionThreadReport, - transactionViolations, - ReportActionsUtils.getAllReportActions(report.reportID)[oneTransactionThreadReport?.parentReportActionID ?? '-1'], - ) - ) { + if (ReportUtils.shouldDisplayViolationsRBR(oneTransactionThreadReport, transactionViolations)) { return { reason: CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS, }; diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index a27d518fe727..2c789785642e 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -66,13 +66,7 @@ const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection if (oneTransactionThreadReportID) { const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); - if ( - ReportUtils.shouldDisplayTransactionThreadViolations( - oneTransactionThreadReport, - allTransactionViolations, - reportActions[oneTransactionThreadReport?.parentReportActionID ?? '-1'], - ) - ) { + if (ReportUtils.shouldDisplayViolationsRBR(oneTransactionThreadReport, allTransactionViolations)) { doesReportContainErrors = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } } diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx index fe26fed0c9c0..e9228a9629b1 100644 --- a/src/pages/Debug/Report/DebugReportPage.tsx +++ b/src/pages/Debug/Report/DebugReportPage.tsx @@ -58,7 +58,7 @@ function DebugReportPage({ return []; } - const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction); + const shouldDisplayViolations = ReportUtils.shouldDisplayViolationsRBR(report, transactionViolations); const shouldDisplayReportViolations = ReportUtils.isReportOwner(report) && ReportUtils.hasReportViolations(reportID); const hasViolations = !!shouldDisplayViolations || shouldDisplayReportViolations; const {reason: reasonGBR, reportAction: reportActionGBR} = DebugUtils.getReasonAndReportActionForGBRInLHNRow(report) ?? {}; From f698ec4a4af6d2ad3b70228fb83348019afafa39 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Fri, 1 Nov 2024 15:49:20 -0300 Subject: [PATCH 019/111] ESLint and rename method --- src/components/LHNOptionsList/OptionRowLHNData.tsx | 2 +- src/libs/DebugUtils.ts | 3 +-- src/libs/OptionsListUtils.ts | 3 +-- src/libs/ReportUtils.ts | 8 ++++---- src/libs/SidebarUtils.ts | 5 ++--- src/libs/WorkspacesSettingsUtils.ts | 2 +- src/pages/Debug/Report/DebugReportPage.tsx | 6 ++---- 7 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 48b7f1c75088..0fe2a1542ca3 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -37,7 +37,7 @@ function OptionRowLHNData({ const optionItemRef = useRef(); - const shouldDisplayViolations = ReportUtils.shouldDisplayViolationsRBR(fullReport, transactionViolations); + const shouldDisplayViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(fullReport, transactionViolations); const shouldDisplayReportViolations = ReportUtils.isReportOwner(fullReport) && ReportUtils.hasReportViolations(reportID); const optionItem = useMemo(() => { diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 976a23c17323..7a12da4a98d9 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -7,7 +7,6 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Beta, Policy, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; import * as ReportUtils from './ReportUtils'; -import {shouldDisplayViolationsRBR} from './ReportUtils'; import SidebarUtils from './SidebarUtils'; class NumberError extends SyntaxError { @@ -598,7 +597,7 @@ function getReasonForShowingRowInLHN(report: OnyxEntry, hasRBR = false): return null; } - const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBR(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations); const reason = ReportUtils.reasonForReportToBeInOptionList({ report, diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index f304f361fded..b7c2638ff614 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -55,7 +55,6 @@ import * as PhoneNumber from './PhoneNumber'; import * as PolicyUtils from './PolicyUtils'; import * as ReportActionUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; -import {shouldDisplayViolationsRBR} from './ReportUtils'; import * as TaskUtils from './TaskUtils'; import * as TransactionUtils from './TransactionUtils'; import * as UserUtils from './UserUtils'; @@ -1798,7 +1797,7 @@ function getOptions( // Filter out all the reports that shouldn't be displayed const filteredReportOptions = options.reports.filter((option) => { const report = option.item; - const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBR(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations); return ReportUtils.shouldReportBeInOptionList({ report, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5db585e720c9..c8975f0da85c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6229,9 +6229,9 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b } /** - * Should we display a RBR on this report due to violations? + * Should we display a RBR on the LHN on this report due to violations? */ -function shouldDisplayViolationsRBR(report: OnyxEntry, transactionViolations: OnyxCollection): boolean { +function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionViolations: OnyxCollection): boolean { // We only show the RBR in the highest level, which is the workspace chat if (!isPolicyExpenseChat(report)) { return false; @@ -6377,7 +6377,7 @@ function hasReportErrorsOtherThanFailedReceipt(report: Report, doesReportHaveVio let doesTransactionThreadReportHasViolations = false; if (oneTransactionThreadReportID) { const transactionReport = getReport(oneTransactionThreadReportID); - doesTransactionThreadReportHasViolations = !!transactionReport && shouldDisplayViolationsRBR(transactionReport, transactionViolations); + doesTransactionThreadReportHasViolations = !!transactionReport && shouldDisplayViolationsRBRInLHN(transactionReport, transactionViolations); } return ( doesTransactionThreadReportHasViolations || @@ -8561,7 +8561,7 @@ export { shouldDisableRename, shouldDisableThread, shouldDisplayThreadReplies, - shouldDisplayViolationsRBR, + shouldDisplayViolationsRBRInLHN, shouldReportBeInOptionList, shouldReportShowSubscript, shouldShowFlagComment, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 54eb4134b6ba..5d5d3632fe41 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -22,7 +22,6 @@ import Parser from './Parser'; import * as PolicyUtils from './PolicyUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; -import {shouldDisplayViolationsRBR} from './ReportUtils'; import * as TaskUtils from './TaskUtils'; type WelcomeMessage = {showReportName: boolean; phrase1?: string; phrase2?: string; phrase3?: string; messageText?: string; messageHtml?: string}; @@ -108,7 +107,7 @@ function getOrderedReportIDs( return; } const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); - const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBR(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations); const isHidden = ReportUtils.getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; const hasErrorsOtherThanFailedReceipt = ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations); @@ -242,7 +241,7 @@ function getReasonAndReportActionThatHasRedBrickRoad( if (oneTransactionThreadReportID) { const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); - if (ReportUtils.shouldDisplayViolationsRBR(oneTransactionThreadReport, transactionViolations)) { + if (ReportUtils.shouldDisplayViolationsRBRInLHN(oneTransactionThreadReport, transactionViolations)) { return { reason: CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS, }; diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index 2c789785642e..2fc0d82b2ff8 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -66,7 +66,7 @@ const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection if (oneTransactionThreadReportID) { const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); - if (ReportUtils.shouldDisplayViolationsRBR(oneTransactionThreadReport, allTransactionViolations)) { + if (ReportUtils.shouldDisplayViolationsRBRInLHN(oneTransactionThreadReport, allTransactionViolations)) { doesReportContainErrors = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } } diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx index e9228a9629b1..3123a25acaf7 100644 --- a/src/pages/Debug/Report/DebugReportPage.tsx +++ b/src/pages/Debug/Report/DebugReportPage.tsx @@ -50,15 +50,13 @@ function DebugReportPage({ const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); - const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID ?? '-1'}`); - const parentReportAction = parentReportActions && report?.parentReportID ? parentReportActions[report?.parentReportActionID ?? '-1'] : undefined; const metadata = useMemo(() => { if (!report) { return []; } - const shouldDisplayViolations = ReportUtils.shouldDisplayViolationsRBR(report, transactionViolations); + const shouldDisplayViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations); const shouldDisplayReportViolations = ReportUtils.isReportOwner(report) && ReportUtils.hasReportViolations(reportID); const hasViolations = !!shouldDisplayViolations || shouldDisplayReportViolations; const {reason: reasonGBR, reportAction: reportActionGBR} = DebugUtils.getReasonAndReportActionForGBRInLHNRow(report) ?? {}; @@ -110,7 +108,7 @@ function DebugReportPage({ : undefined, }, ]; - }, [parentReportAction, report, reportActions, reportID, transactionViolations, translate]); + }, [report, reportActions, reportID, transactionViolations, translate]); return ( Date: Fri, 1 Nov 2024 15:57:19 -0300 Subject: [PATCH 020/111] Use some --- src/libs/ReportUtils.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c8975f0da85c..01434d8174a4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6254,12 +6254,9 @@ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionV // - Belong to the same workspace // And if any have a violation, then it should have a RBR const potentialReports = allReports.filter((r: Report) => r?.ownerAccountID === currentUserAccountID && r?.stateNum <= 1 && r?.policyID === report.policyID); - for (const potentialReport of potentialReports) { - if (hasViolations(potentialReport.reportID, transactionViolations) || hasWarningTypeViolations(potentialReport.reportID, transactionViolations)) { - return true; - } - } - return false; + return potentialReports.some( + (potentialReport) => hasViolations(potentialReport.reportID, transactionViolations) || hasWarningTypeViolations(potentialReport.reportID, transactionViolations), + ); } /** From 025083e00924d0a9974db92bfd2d0b9af6036f58 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Fri, 1 Nov 2024 16:02:24 -0300 Subject: [PATCH 021/111] Cleanup code --- src/libs/ReportUtils.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 01434d8174a4..5f418bd6d3da 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6242,17 +6242,12 @@ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionV return false; } - // We need to get all reports to check the ones inside this workspace chat, if we have no reports, then there's no RBR - const allReports = Object.values(ReportConnection.getAllReports()); - if (!allReports) { - return false; - } - - // Now get all potential reports, which are the ones that are: + // Get all potential reports, which are the ones that are: // - Owned by the same user // - Are either open or submitted // - Belong to the same workspace // And if any have a violation, then it should have a RBR + const allReports = Object.values(ReportConnection.getAllReports() ?? {}); const potentialReports = allReports.filter((r: Report) => r?.ownerAccountID === currentUserAccountID && r?.stateNum <= 1 && r?.policyID === report.policyID); return potentialReports.some( (potentialReport) => hasViolations(potentialReport.reportID, transactionViolations) || hasWarningTypeViolations(potentialReport.reportID, transactionViolations), From 15bc38a646578efd16b7649a1a8654058afe6d7e Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Fri, 1 Nov 2024 17:08:57 -0300 Subject: [PATCH 022/111] Fix typescript --- src/libs/ReportUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5f418bd6d3da..80052d517b1a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6233,12 +6233,12 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b */ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionViolations: OnyxCollection): boolean { // We only show the RBR in the highest level, which is the workspace chat - if (!isPolicyExpenseChat(report)) { + if (!report || !isPolicyExpenseChat(report)) { return false; } // We only show the RBR to the submitter - if (!isCurrentUserSubmitter(report?.reportID ?? '')) { + if (!isCurrentUserSubmitter(report.reportID ?? '')) { return false; } @@ -6247,8 +6247,8 @@ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionV // - Are either open or submitted // - Belong to the same workspace // And if any have a violation, then it should have a RBR - const allReports = Object.values(ReportConnection.getAllReports() ?? {}); - const potentialReports = allReports.filter((r: Report) => r?.ownerAccountID === currentUserAccountID && r?.stateNum <= 1 && r?.policyID === report.policyID); + const allReports = Object.values(ReportConnection.getAllReports() ?? {}) as Report[]; + const potentialReports = allReports.filter((r) => r.ownerAccountID === currentUserAccountID && r.stateNum && r.stateNum <= 1 && r.policyID === report.policyID); return potentialReports.some( (potentialReport) => hasViolations(potentialReport.reportID, transactionViolations) || hasWarningTypeViolations(potentialReport.reportID, transactionViolations), ); From 06df162c97765b410b45c3d28183654b70b622b9 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Fri, 1 Nov 2024 16:52:49 -0400 Subject: [PATCH 023/111] allow queued onyx updates to be flushed --- src/libs/Network/SequentialQueue.ts | 4 ++-- src/libs/actions/QueuedOnyxUpdates.ts | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index 643ed64ae7f6..3f4da20c16e1 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -138,8 +138,8 @@ function flush() { return; } - if (PersistedRequests.getAll().length === 0) { - Log.info('[SequentialQueue] Unable to flush. No requests to process.'); + if (PersistedRequests.getAll().length === 0 && QueuedOnyxUpdates.isEmpty()) { + Log.info('[SequentialQueue] Unable to flush. No requests or queued Onyx updates to process.'); return; } diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index 83bc6652cb39..bc19ff12aea1 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -19,4 +19,8 @@ function flushQueue(): Promise { }); } -export {queueOnyxUpdates, flushQueue}; +function isEmpty() { + return queuedOnyxUpdates.length === 0; +} + +export {queueOnyxUpdates, flushQueue, isEmpty}; From 8fc556696496446337b6bc82e87109b8e8b2c24a Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Fri, 1 Nov 2024 17:24:35 -0400 Subject: [PATCH 024/111] Revert "check both user and account validated state" This reverts commit 81b4a2f7feaa8c5c4464af7b4ae6750b49502e8f. --- src/pages/settings/Wallet/VerifyAccountPage.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/pages/settings/Wallet/VerifyAccountPage.tsx b/src/pages/settings/Wallet/VerifyAccountPage.tsx index 30b7686ada00..3bd3c2aa7000 100644 --- a/src/pages/settings/Wallet/VerifyAccountPage.tsx +++ b/src/pages/settings/Wallet/VerifyAccountPage.tsx @@ -15,19 +15,14 @@ type VerifyAccountPageProps = StackScreenProps !!user?.validated}); const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID ?? 0}); - // We store validated state in two places so this is a bit of a workaround to check both - const isUserValidated = user?.validated ?? false; - const isAccountValidated = account?.validated ?? false; - const isValidated = isUserValidated || isAccountValidated; - const [isValidateCodeActionModalVisible, setIsValidateCodeActionModalVisible] = useState(true); const navigateBackTo = route?.params?.backTo; @@ -46,7 +41,7 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) { }, [contactMethod]); useEffect(() => { - if (!isValidated) { + if (!isUserValidated) { return; } @@ -57,19 +52,19 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) { } Navigation.navigate(navigateBackTo); - }, [isValidated, navigateBackTo]); + }, [isUserValidated, navigateBackTo]); useEffect(() => { if (isValidateCodeActionModalVisible) { return; } - if (!isValidated && navigateBackTo) { + if (!isUserValidated && navigateBackTo) { Navigation.navigate(ROUTES.SETTINGS_WALLET); } else if (!navigateBackTo) { Navigation.goBack(); } - }, [isValidateCodeActionModalVisible, isValidated, navigateBackTo]); + }, [isValidateCodeActionModalVisible, isUserValidated, navigateBackTo]); return ( Date: Sun, 3 Nov 2024 22:00:20 +0700 Subject: [PATCH 025/111] handle undefined fileName parameter in route params --- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx | 2 +- src/pages/home/report/ReportAttachments.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 06512f04bb95..43177be408ff 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -73,7 +73,7 @@ function ImageRenderer({tnode}: ImageRendererProps) { let fileName = htmlAttribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || FileUtils.getFileName(`${isAttachmentOrReceipt ? attachmentSourceAttribute : htmlAttribs.src}`); const fileInfo = FileUtils.splitExtensionFromFileName(fileName); if (!fileInfo.fileExtension) { - fileName = `${fileInfo.fileName || CONST.DEFAULT_IMAGE_FILE_NAME}.jpg`; + fileName = `${fileInfo?.fileName || CONST.DEFAULT_IMAGE_FILE_NAME}.jpg`; } const thumbnailImageComponent = ( diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index c4895de3fea5..871d692b59a3 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -20,7 +20,7 @@ function ReportAttachments({route}: ReportAttachmentsProps) { const isAuthTokenRequired = route.params.isAuthTokenRequired; const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID || -1}`); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); - const fileName = route.params.fileName; + const fileName = route.params?.fileName; // In native the imported images sources are of type number. Ref: https://reactnative.dev/docs/image#imagesource const source = Number(route.params.source) || route.params.source; From bbbe5aa1441005bc8b99ee59ce02a359e998dd79 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 4 Nov 2024 13:44:27 +0700 Subject: [PATCH 026/111] fix: refactor code --- src/libs/WorkspacesSettingsUtils.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index 0b88c80ea9e7..e8b9b363e768 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -63,14 +63,15 @@ const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions); let doesReportContainErrors = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; - const parentReportActions = (altReportActions ?? allReportActions)?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`]; - const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; - const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(report, allTransactionViolations, parentReportAction); - const shouldDisplayReportViolations = ReportUtils.isReportOwner(report) && ReportUtils.hasReportViolations(report.reportID); - const hasViolations = shouldDisplayViolations || shouldDisplayReportViolations; - if (hasViolations && !doesReportContainErrors) { - doesReportContainErrors = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; - } + if (!doesReportContainErrors) { + const parentReportActions = (altReportActions ?? allReportActions)?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`]; + const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; + const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(report, allTransactionViolations, parentReportAction); + const shouldDisplayReportViolations = ReportUtils.isReportOwner(report) && ReportUtils.hasReportViolations(report.reportID); + const hasViolations = shouldDisplayViolations || shouldDisplayReportViolations; + if (hasViolations) { + doesReportContainErrors = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; + } if (oneTransactionThreadReportID && !doesReportContainErrors) { const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); From 462c52f852513c8b1a3854f88cb29bc41fb6c1c0 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 4 Nov 2024 13:47:10 +0700 Subject: [PATCH 027/111] fix: lint --- src/libs/WorkspacesSettingsUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index e8b9b363e768..46927e430f67 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -72,6 +72,7 @@ const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection if (hasViolations) { doesReportContainErrors = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } + } if (oneTransactionThreadReportID && !doesReportContainErrors) { const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); From 01a8f9f650ac9485d3a5dd031d51fd976c73ccba Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Mon, 4 Nov 2024 18:33:55 -0300 Subject: [PATCH 028/111] Make sure we don't show violations in previews when they are marked as not showInReview --- .../MoneyRequestPreviewContent.tsx | 4 ++-- .../ReportActionItem/ReportPreview.tsx | 4 ++-- src/libs/ReportUtils.ts | 8 +++---- src/libs/TransactionUtils/index.ts | 22 +++++++------------ src/types/onyx/TransactionViolation.ts | 3 +++ 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index a774b5c18c55..65e160b6df1f 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -115,8 +115,8 @@ function MoneyRequestPreviewContent({ const isOnHold = TransactionUtils.isOnHold(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', transactionViolations); - const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', transactionViolations) && ReportUtils.isPaidGroupPolicy(iouReport); + const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', transactionViolations, true); + const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', transactionViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 6bb70a275a30..f1db90fcea65 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -156,8 +156,8 @@ function ReportPreview({ const hasErrors = (hasMissingSmartscanFields && !iouSettled) || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - ReportUtils.hasViolations(iouReportID, transactionViolations) || - ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations) || + ReportUtils.hasViolations(iouReportID, transactionViolations, true) || + ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations, true) || (ReportUtils.isReportOwner(iouReport) && ReportUtils.hasReportViolations(iouReportID)) || ReportUtils.hasActionsWithErrors(iouReportID); const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 80052d517b1a..3007cc427f72 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6257,17 +6257,17 @@ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionV /** * Checks to see if a report contains a violation */ -function hasViolations(reportID: string, transactionViolations: OnyxCollection): boolean { +function hasViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean | null): boolean { const transactions = reportsTransactions[reportID] ?? []; - return transactions.some((transaction) => TransactionUtils.hasViolation(transaction.transactionID, transactionViolations)); + return transactions.some((transaction) => TransactionUtils.hasViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); } /** * Checks to see if a report contains a violation of type `warning` */ -function hasWarningTypeViolations(reportID: string, transactionViolations: OnyxCollection): boolean { +function hasWarningTypeViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean | null): boolean { const transactions = reportsTransactions[reportID] ?? []; - return transactions.some((transaction) => TransactionUtils.hasWarningTypeViolation(transaction.transactionID, transactionViolations)); + return transactions.some((transaction) => TransactionUtils.hasWarningTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); } function hasReportViolations(reportID: string) { diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index bf5073aba27c..c7d4b38847bb 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -809,36 +809,31 @@ function isOnHoldByTransactionID(transactionID: string): boolean { /** * Checks if any violations for the provided transaction are of type 'violation' */ -function hasViolation(transactionID: string, transactionViolations: OnyxCollection): boolean { +function hasViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean | null): boolean { return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION, + (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === null || showInReview === (violation.showInReview ?? false)), ); } /** * Checks if any violations for the provided transaction are of type 'notice' */ -function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection): boolean { - return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE); +function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean | null): boolean { + return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( + (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && (showInReview === null || showInReview === (violation.showInReview ?? false)), + ); } /** * Checks if any violations for the provided transaction are of type 'warning' */ -function hasWarningTypeViolation(transactionID: string, transactionViolations: OnyxCollection): boolean { +function hasWarningTypeViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean | null): boolean { if (!Permissions.canUseDupeDetection(allBetas ?? [])) { return false; } - return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING); -} - -/** - * Checks if any violations for the provided transaction are of modifiedAmount or modifiedDate - */ -function hasModifiedAmountOrDateViolation(transactionID: string, transactionViolations: OnyxCollection): boolean { return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.MODIFIED_AMOUNT || violation.name === CONST.VIOLATIONS.MODIFIED_DATE, + (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === null || showInReview === (violation.showInReview ?? false)), ); } @@ -1227,7 +1222,6 @@ export { shouldShowBrokenConnectionViolation, hasNoticeTypeViolation, hasWarningTypeViolation, - hasModifiedAmountOrDateViolation, isCustomUnitRateIDForP2P, getRateID, getTransaction, diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index bf8ecc7ebdde..bfb215a1bbdb 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -92,6 +92,9 @@ type TransactionViolation = { /** Additional violation information to provide the user */ data?: TransactionViolationData; + + /** Indicates if this violation should be shown in review */ + showInReview?: boolean; }; /** Collection of transaction violations */ From f20b84fa0aa022fd85f63c4eb311b702bdf53274 Mon Sep 17 00:00:00 2001 From: truph01 Date: Tue, 5 Nov 2024 18:42:42 +0700 Subject: [PATCH 029/111] fix: add test --- tests/unit/WorkspaceSettingsUtilsTest.ts | 563 +++++++++++++++++++++++ 1 file changed, 563 insertions(+) create mode 100644 tests/unit/WorkspaceSettingsUtilsTest.ts diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts new file mode 100644 index 000000000000..2c7cf153c68a --- /dev/null +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -0,0 +1,563 @@ +import {OnyxCollection} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import waitForBatchedUpdates from 'tests/utils/waitForBatchedUpdates'; +import {getBrickRoadForPolicy} from '@libs/WorkspacesSettingsUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {Report, ReportActions} from '@src/types/onyx'; +import * as TestHelper from '../utils/TestHelper'; + +describe('WorkspacesSettingsUtils', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + }); + }); + + beforeEach(() => { + global.fetch = TestHelper.getGlobalFetchMock(); + return Onyx.clear().then(waitForBatchedUpdates); + }); + describe('getBrickRoadForPolicy', () => { + it('Should return "error"', async () => { + const report: Report = { + isOptimisticReport: false, + type: 'chat', + isOwnPolicyExpenseChat: false, + isPinned: false, + lastActorAccountID: 0, + lastMessageTranslationKey: '', + lastMessageHtml: '', + lastReadTime: '2024-11-05 11:19:18.288', + lastVisibleActionCreated: '2024-11-05 11:19:18.288', + oldPolicyName: '', + ownerAccountID: 0, + parentReportActionID: '8722650843049927838', + parentReportID: '6955627196303088', + participants: { + '18634488': { + notificationPreference: 'hidden', + role: 'admin', + }, + }, + policyID: '57D0F454E0BCE54B', + reportID: '4286515777714555', + reportName: 'Expense', + stateNum: 0, + statusNum: 0, + description: '', + avatarUrl: '', + avatarFileName: '', + }; + + const actions: OnyxCollection = { + reportActions_1699789757771388: { + '4007735288062946397': { + reportActionID: '4007735288062946397', + actionName: 'CREATED', + actorAccountID: 18634488, + message: [ + { + type: 'TEXT', + style: 'strong', + text: 'You', + }, + { + type: 'TEXT', + style: 'normal', + text: ' created this report', + }, + ], + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'adasdasd', + }, + ], + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', + created: '2024-11-05 11:10:47.852', + shouldShow: true, + reportActionTimestamp: 1730805047852, + sequenceNumber: 0, + lastModified: '2024-11-05 11:10:47.852', + }, + '7978085421707288417': { + reportActionID: '7978085421707288417', + reportID: '1699789757771388', + actionName: 'REPORTPREVIEW', + originalMessage: {}, + message: [ + { + deleted: '', + html: "Adasdasd's Workspace owes ₫579", + isDeletedParentAction: false, + isEdited: false, + text: "Adasdasd's Workspace owes ₫579", + type: 'COMMENT', + whisperedTo: [], + }, + ], + created: '2024-11-05 11:19:18.710', + accountID: 18634488, + actorAccountID: 18634488, + childReportID: '6955627196303088', + childMoneyRequestCount: 2, + childLastMoneyRequestComment: '', + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', + childCommenterCount: 0, + childLastActorAccountID: 18634488, + + childLastVisibleActionCreated: '', + childOldestFourAccountIDs: '', + childReportNotificationPreference: 'hidden', + childType: 'expense', + childVisibleActionCount: 0, + lastModified: '2024-11-05 11:19:18.710', + person: [ + { + style: 'strong', + text: 'adasdasd', + type: 'TEXT', + }, + ], + shouldShow: true, + automatic: false, + childManagerAccountID: 18634488, + childOwnerAccountID: 18634488, + childReportName: 'Expense Report #6955627196303088', + childStateNum: 1, + childStatusNum: 1, + + reportActionTimestamp: 1730805558710, + timestamp: 1730805558, + whisperedToAccountIDs: [], + }, + }, + reportActions_4148694821839494: { + '2964625714811661556': { + reportActionID: '2964625714811661556', + actionName: 'CREATED', + actorAccountID: 18634488, + message: [ + { + type: 'TEXT', + style: 'strong', + text: '_FAKE_', + }, + { + type: 'TEXT', + style: 'normal', + text: ' created this report', + }, + ], + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'adasdasd', + }, + ], + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', + created: '2024-11-05 11:10:47.077', + shouldShow: true, + }, + '5971844472086652538': { + actionName: 'ADDCOMMENT', + actorAccountID: 10288574, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/26271df52c9c9db57fa0221feaef04d18a045f2b_128.jpeg', + created: '2024-11-05 11:11:47.410', + lastModified: '2024-11-05 11:11:47.410', + message: [ + { + html: '

Let\'s get you set up on Expensify

Hi there 👋 , I\'m your dedicated setup specialist. I look forward to helping you explore and configure Expensify. A few important things to mention:
  • Your workspace has a ton of custom settings, just select your workspace settings to set it up.
  • You\'ve got more functionality to enable, like custom categories, tags, etc. Just ask me how.
Chat with me here in your #admins room or just reply to this message. You can also schedule a call if you have more in-depth questions. Talk soon!

Setup Guide GIF', + text: "Let's get you set up on Expensify\nHi there 👋 , I'm your dedicated setup specialist. I look forward to helping you explore and configure Expensify. A few important things to mention: \nYour workspace has a ton of custom settings, just select your workspace settings to set it up.You've got more functionality to enable, like custom categories, tags, etc. Just ask me how. Chat with me here in your #admins room or just reply to this message. You can also schedule a call if you have more in-depth questions. Talk soon!\n\n[Attachment]", + type: 'COMMENT', + whisperedTo: [], + }, + ], + originalMessage: {}, + person: [ + { + style: 'strong', + text: 'Setup Specialist - BreAnna Sumpter Mon-Friday GMT & EST', + type: 'TEXT', + }, + ], + reportActionID: '5971844472086652538', + shouldShow: true, + }, + }, + reportActions_4625283659773773: { + '7132923952865070123': { + actionName: 'ADDCOMMENT', + actorAccountID: 8392101, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png', + created: '2024-11-05 11:10:38.956', + lastModified: '2024-11-05 11:10:38.956', + message: [ + { + type: 'COMMENT', + html: "

Welcome to Expensify

👋 Hey there, I'm Concierge! If you have any questions about Expensify, you can always chat with me here 24-7 for fast and reliable support. I'm happy to help!", + text: "Welcome to Expensify\n👋 Hey there, I'm Concierge! If you have any questions about Expensify, you can always chat with me here 24-7 for fast and reliable support. I'm happy to help!", + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + deleted: '', + }, + ], + originalMessage: {}, + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Expensify Concierge', + }, + ], + reportActionID: '7132923952865070123', + shouldShow: true, + timestamp: 1730805038, + reportActionTimestamp: 1730805038956, + automatic: false, + whisperedToAccountIDs: [], + }, + '5686837203726341682': { + reportActionID: '5686837203726341682', + actionName: 'CREATED', + created: '2024-11-05 11:10:38.688', + reportActionTimestamp: 1730805038688, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', + message: [ + { + type: 'TEXT', + style: 'strong', + text: '__fake__', + }, + { + type: 'TEXT', + style: 'normal', + text: ' created this report', + }, + ], + person: [ + { + type: 'TEXT', + style: 'strong', + text: '__fake__', + }, + ], + automatic: false, + sequenceNumber: 0, + shouldShow: true, + lastModified: '2024-11-05 11:10:38.688', + }, + '536505040772198026': { + reportActionID: '536505040772198026', + actionName: 'ADDCOMMENT', + actorAccountID: 8392101, + person: [ + { + style: 'strong', + text: 'Expensify Concierge', + type: 'TEXT', + }, + ], + automatic: false, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png', + created: '2024-11-05 11:10:43.943', + message: [ + { + html: 'Let’s get you set up 🔧', + text: 'Let’s get you set up 🔧', + type: 'COMMENT', + whisperedTo: [], + }, + ], + originalMessage: {}, + isFirstItem: false, + isAttachmentWithText: false, + shouldShow: true, + isOptimisticAction: true, + lastModified: '2024-11-05 11:10:43.943', + }, + '5286007009323266706': { + reportActionID: '5286007009323266706', + actionName: 'ADDCOMMENT', + actorAccountID: 8392101, + person: [ + { + style: 'strong', + text: 'Expensify Concierge', + type: 'TEXT', + }, + ], + automatic: false, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png', + created: '2024-11-05 11:10:43.944', + message: [ + { + html: "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", + text: "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", + type: 'COMMENT', + whisperedTo: [], + }, + ], + originalMessage: {}, + isFirstItem: false, + isAttachmentWithText: false, + shouldShow: true, + isOptimisticAction: true, + lastModified: '2024-11-05 11:10:43.944', + }, + '4422060638727390382': { + actionName: 'ADDCOMMENT', + actorAccountID: 8392101, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png', + created: '2024-11-05 11:11:47.086', + lastModified: '2024-11-05 11:11:47.086', + message: [ + { + html: 'Let\'s get your company set up! Setup Specialist - BreAnna, your dedicated specialist, is online now and can answer your initial questions or provide a demo.

💬 CHAT WITH YOUR SETUP SPECIALIST', + text: "Let's get your company set up! Setup Specialist - BreAnna, your dedicated specialist, is online now and can answer your initial questions or provide a demo.\n\n💬 CHAT WITH YOUR SETUP SPECIALIST", + type: 'COMMENT', + whisperedTo: [], + }, + ], + originalMessage: {}, + person: [ + { + style: 'strong', + text: 'Expensify Concierge', + type: 'TEXT', + }, + ], + reportActionID: '4422060638727390382', + shouldShow: true, + }, + '2824688328360312239': { + actionName: 'ADDCOMMENT', + actorAccountID: 8392101, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png', + created: '2024-11-05 11:19:18.703', + lastModified: '2024-11-05 11:19:18.703', + message: [ + { + html: '

You’ve started a free trial!


Welcome to your free 7-day trial of Expensify 🎉 Use it to continue exploring your workspace\'s benefits, including tracking expenses, reimbursing employees, managing company spend, and more.

If you have any questions, chat with your dedicated Setup Specialist in #admins. Enjoy!', + text: "You’ve started a free trial!\n\nWelcome to your free 7-day trial of Expensify 🎉 Use it to continue exploring your workspace's benefits, including tracking expenses, reimbursing employees, managing company spend, and more.\n\nIf you have any questions, chat with your dedicated Setup Specialist in #admins. Enjoy!", + type: 'COMMENT', + whisperedTo: [], + }, + ], + originalMessage: {}, + person: [ + { + style: 'strong', + text: 'Expensify Concierge', + type: 'TEXT', + }, + ], + reportActionID: '2824688328360312239', + shouldShow: true, + }, + }, + reportActions_6955627196303088: { + '1493209744740418100': { + reportActionID: '1493209744740418100', + actionName: 'CREATED', + actorAccountID: 18634488, + message: [ + { + type: 'TEXT', + style: 'strong', + text: 'ajsdjajdjadjajsjajdsj123@gmail.com', + }, + { + type: 'TEXT', + style: 'normal', + text: ' created this report', + }, + ], + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'adasdasd', + }, + ], + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', + created: '2024-11-05 11:19:18.285', + shouldShow: true, + }, + '8722650843049927838': { + actionName: 'IOU', + actorAccountID: 18634488, + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', + isAttachmentOnly: false, + originalMessage: { + amount: 12300, + comment: '', + currency: 'VND', + IOUTransactionID: '3106135972713435169', + IOUReportID: '6955627196303088', + }, + message: [ + { + deleted: '', + html: '₫123 expense', + isDeletedParentAction: false, + isEdited: false, + text: '₫123 expense', + type: 'COMMENT', + whisperedTo: [], + }, + ], + person: [ + { + style: 'strong', + text: 'adasdasd', + type: 'TEXT', + }, + ], + reportActionID: '8722650843049927838', + shouldShow: true, + created: '2024-11-05 11:19:18.706', + childReportID: '4286515777714555', + lastModified: '2024-11-05 11:19:18.706', + childReportNotificationPreference: 'hidden', + childType: 'chat', + reportActionTimestamp: 1730805558706, + sequenceNumber: 1, + timestamp: 1730805558, + whisperedToAccountIDs: [], + }, + '1783566081350093529': { + actionName: 'IOU', + actorAccountID: 18634488, + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', + isAttachmentOnly: false, + originalMessage: { + amount: 45600, + comment: '', + currency: 'VND', + IOUTransactionID: '3690687111940510713', + IOUReportID: '6955627196303088', + }, + message: [ + { + deleted: '', + html: '₫456 expense', + isDeletedParentAction: false, + isEdited: false, + text: '₫456 expense', + type: 'COMMENT', + whisperedTo: [], + }, + ], + person: [ + { + style: 'strong', + text: 'adasdasd', + type: 'TEXT', + }, + ], + reportActionID: '1783566081350093529', + shouldShow: true, + created: '2024-11-05 11:20:22.065', + childReportID: '7900715127836904', + lastModified: '2024-11-05 11:20:22.065', + childReportNotificationPreference: 'hidden', + childType: 'chat', + reportActionTimestamp: 1730805622065, + sequenceNumber: 2, + timestamp: 1730805622, + whisperedToAccountIDs: [], + }, + }, + reportActions_4286515777714555: { + '1995312838979534584': { + reportActionID: '1995312838979534584', + actionName: 'CREATED', + actorAccountID: 18634488, + message: [ + { + type: 'TEXT', + style: 'strong', + text: 'ajsdjajdjadjajsjajdsj123@gmail.com', + }, + { + type: 'TEXT', + style: 'normal', + text: ' created this report', + }, + ], + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'adasdasd', + }, + ], + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', + created: '2024-11-05 11:19:18.288', + shouldShow: true, + }, + }, + reportActions_7900715127836904: { + '3536855248086336861': { + reportActionID: '3536855248086336861', + actionName: 'CREATED', + actorAccountID: 18634488, + message: [ + { + type: 'TEXT', + style: 'strong', + text: 'ajsdjajdjadjajsjajdsj123@gmail.com', + }, + { + type: 'TEXT', + style: 'normal', + text: ' created this report', + }, + ], + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'adasdasd', + }, + ], + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', + created: '2024-11-05 11:20:21.589', + shouldShow: true, + }, + }, + }; + + await Onyx.multiSet({ + ...report, + ...actions, + transactionViolations_3106135972713435169: [ + { + name: 'missingCategory', + type: 'violation', + }, + ], + transactionViolations_3690687111940510713: [ + { + name: 'missingCategory', + type: 'violation', + }, + ], + }); + + const result = getBrickRoadForPolicy(report, actions); + expect(result).toBe('error'); + }); + }); +}); From a708691fd04cd605b50a520d2dc9eb2bcd49714a Mon Sep 17 00:00:00 2001 From: truph01 Date: Tue, 5 Nov 2024 18:51:53 +0700 Subject: [PATCH 030/111] fix: lint --- tests/unit/WorkspaceSettingsUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts index 2c7cf153c68a..3fcfd6549131 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.ts +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -1,10 +1,10 @@ import {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import waitForBatchedUpdates from 'tests/utils/waitForBatchedUpdates'; import {getBrickRoadForPolicy} from '@libs/WorkspacesSettingsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import {Report, ReportActions} from '@src/types/onyx'; import * as TestHelper from '../utils/TestHelper'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; describe('WorkspacesSettingsUtils', () => { beforeAll(() => { From 85d684ff6a0e1dfad2f1818e9438548f96f7f0d1 Mon Sep 17 00:00:00 2001 From: truph01 Date: Tue, 5 Nov 2024 19:58:35 +0700 Subject: [PATCH 031/111] fix: unit test --- tests/unit/WorkspaceSettingsUtilsTest.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts index 3fcfd6549131..a25cec98c0f4 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.ts +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -3,6 +3,7 @@ import Onyx from 'react-native-onyx'; import {getBrickRoadForPolicy} from '@libs/WorkspacesSettingsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import {Report, ReportActions} from '@src/types/onyx'; +import {ReportCollectionDataSet} from '@src/types/onyx/Report'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -49,6 +50,21 @@ describe('WorkspacesSettingsUtils', () => { avatarFileName: '', }; + const MOCK_REPORTS: ReportCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.REPORT}4286515777714555` as const]: report, + [`${ONYXKEYS.COLLECTION.REPORT}6955627196303088` as const]: { + reportID: '6955627196303088', + chatReportID: '1699789757771388', + policyID: '57D0F454E0BCE54B', + type: 'expense', + ownerAccountID: 18634488, + stateNum: 1, + statusNum: 1, + parentReportID: '1699789757771388', + parentReportActionID: '7978085421707288417', + }, + }; + const actions: OnyxCollection = { reportActions_1699789757771388: { '4007735288062946397': { @@ -540,8 +556,11 @@ describe('WorkspacesSettingsUtils', () => { }; await Onyx.multiSet({ - ...report, + ...MOCK_REPORTS, ...actions, + [ONYXKEYS.SESSION]: { + accountID: 18634488, + }, transactionViolations_3106135972713435169: [ { name: 'missingCategory', @@ -556,6 +575,8 @@ describe('WorkspacesSettingsUtils', () => { ], }); + await waitForBatchedUpdates(); + const result = getBrickRoadForPolicy(report, actions); expect(result).toBe('error'); }); From 0f9e3dfd6c372c5c2fe7e9cd1a549ef3fe2db41a Mon Sep 17 00:00:00 2001 From: truph01 Date: Tue, 5 Nov 2024 20:20:15 +0700 Subject: [PATCH 032/111] fix: lint --- tests/unit/WorkspaceSettingsUtilsTest.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts index a25cec98c0f4..427356c0ccc4 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.ts +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -34,12 +34,6 @@ describe('WorkspacesSettingsUtils', () => { ownerAccountID: 0, parentReportActionID: '8722650843049927838', parentReportID: '6955627196303088', - participants: { - '18634488': { - notificationPreference: 'hidden', - role: 'admin', - }, - }, policyID: '57D0F454E0BCE54B', reportID: '4286515777714555', reportName: 'Expense', @@ -67,6 +61,7 @@ describe('WorkspacesSettingsUtils', () => { const actions: OnyxCollection = { reportActions_1699789757771388: { + // eslint-disable-next-line @typescript-eslint/naming-convention '4007735288062946397': { reportActionID: '4007735288062946397', actionName: 'CREATED', @@ -98,6 +93,7 @@ describe('WorkspacesSettingsUtils', () => { sequenceNumber: 0, lastModified: '2024-11-05 11:10:47.852', }, + // eslint-disable-next-line @typescript-eslint/naming-convention '7978085421707288417': { reportActionID: '7978085421707288417', reportID: '1699789757771388', @@ -151,6 +147,7 @@ describe('WorkspacesSettingsUtils', () => { }, }, reportActions_4148694821839494: { + // eslint-disable-next-line @typescript-eslint/naming-convention '2964625714811661556': { reportActionID: '2964625714811661556', actionName: 'CREATED', @@ -179,6 +176,7 @@ describe('WorkspacesSettingsUtils', () => { created: '2024-11-05 11:10:47.077', shouldShow: true, }, + // eslint-disable-next-line @typescript-eslint/naming-convention '5971844472086652538': { actionName: 'ADDCOMMENT', actorAccountID: 10288574, @@ -206,6 +204,7 @@ describe('WorkspacesSettingsUtils', () => { }, }, reportActions_4625283659773773: { + // eslint-disable-next-line @typescript-eslint/naming-convention '7132923952865070123': { actionName: 'ADDCOMMENT', actorAccountID: 8392101, @@ -238,6 +237,7 @@ describe('WorkspacesSettingsUtils', () => { automatic: false, whisperedToAccountIDs: [], }, + // eslint-disable-next-line @typescript-eslint/naming-convention '5686837203726341682': { reportActionID: '5686837203726341682', actionName: 'CREATED', @@ -268,6 +268,7 @@ describe('WorkspacesSettingsUtils', () => { shouldShow: true, lastModified: '2024-11-05 11:10:38.688', }, + // eslint-disable-next-line @typescript-eslint/naming-convention '536505040772198026': { reportActionID: '536505040772198026', actionName: 'ADDCOMMENT', @@ -297,6 +298,7 @@ describe('WorkspacesSettingsUtils', () => { isOptimisticAction: true, lastModified: '2024-11-05 11:10:43.943', }, + // eslint-disable-next-line @typescript-eslint/naming-convention '5286007009323266706': { reportActionID: '5286007009323266706', actionName: 'ADDCOMMENT', @@ -326,6 +328,7 @@ describe('WorkspacesSettingsUtils', () => { isOptimisticAction: true, lastModified: '2024-11-05 11:10:43.944', }, + // eslint-disable-next-line @typescript-eslint/naming-convention '4422060638727390382': { actionName: 'ADDCOMMENT', actorAccountID: 8392101, @@ -351,6 +354,7 @@ describe('WorkspacesSettingsUtils', () => { reportActionID: '4422060638727390382', shouldShow: true, }, + // eslint-disable-next-line @typescript-eslint/naming-convention '2824688328360312239': { actionName: 'ADDCOMMENT', actorAccountID: 8392101, @@ -378,6 +382,7 @@ describe('WorkspacesSettingsUtils', () => { }, }, reportActions_6955627196303088: { + // eslint-disable-next-line @typescript-eslint/naming-convention '1493209744740418100': { reportActionID: '1493209744740418100', actionName: 'CREATED', @@ -406,6 +411,7 @@ describe('WorkspacesSettingsUtils', () => { created: '2024-11-05 11:19:18.285', shouldShow: true, }, + // eslint-disable-next-line @typescript-eslint/naming-convention '8722650843049927838': { actionName: 'IOU', actorAccountID: 18634488, @@ -449,6 +455,7 @@ describe('WorkspacesSettingsUtils', () => { timestamp: 1730805558, whisperedToAccountIDs: [], }, + // eslint-disable-next-line @typescript-eslint/naming-convention '1783566081350093529': { actionName: 'IOU', actorAccountID: 18634488, @@ -494,6 +501,7 @@ describe('WorkspacesSettingsUtils', () => { }, }, reportActions_4286515777714555: { + // eslint-disable-next-line @typescript-eslint/naming-convention '1995312838979534584': { reportActionID: '1995312838979534584', actionName: 'CREATED', @@ -524,6 +532,7 @@ describe('WorkspacesSettingsUtils', () => { }, }, reportActions_7900715127836904: { + // eslint-disable-next-line @typescript-eslint/naming-convention '3536855248086336861': { reportActionID: '3536855248086336861', actionName: 'CREATED', From 38dd38687977bc59d6aceab5389982776dbe65f6 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Tue, 5 Nov 2024 11:27:49 -0300 Subject: [PATCH 033/111] Stlye --- src/libs/TransactionUtils/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 8d78fb684522..7a6fc135dc30 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -828,16 +828,17 @@ function hasNoticeTypeViolation(transactionID: string, transactionViolations: On * Checks if any violations for the provided transaction are of type 'warning' */ function hasWarningTypeViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean | null): boolean { - const warningTypeViolations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === null || showInReview === (violation.showInReview ?? false)), - ); + const warningTypeViolations = + transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter( + (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === null || showInReview === (violation.showInReview ?? false)), + ) ?? []; const hasOnlyDupeDetectionViolation = warningTypeViolations?.every((violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION); if (!Permissions.canUseDupeDetection(allBetas ?? []) && hasOnlyDupeDetectionViolation) { return false; } - return !!warningTypeViolations && warningTypeViolations.length > 0; + return warningTypeViolations.length > 0; } /** From 454aef3cf1dad4c39465ff56287028cd52f4068c Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Tue, 5 Nov 2024 13:13:35 -0300 Subject: [PATCH 034/111] Fix tests --- src/libs/ReportUtils.ts | 6 +-- src/libs/SidebarUtils.ts | 13 ++--- src/libs/TransactionUtils/index.ts | 8 +-- tests/unit/DebugUtilsTest.ts | 79 ++++++------------------------ 4 files changed, 26 insertions(+), 80 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4044c03e7fa2..85bdd13a62fc 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6328,7 +6328,7 @@ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionV // - Belong to the same workspace // And if any have a violation, then it should have a RBR const allReports = Object.values(ReportConnection.getAllReports() ?? {}) as Report[]; - const potentialReports = allReports.filter((r) => r.ownerAccountID === currentUserAccountID && r.stateNum && r.stateNum <= 1 && r.policyID === report.policyID); + const potentialReports = allReports.filter((r) => r.ownerAccountID === currentUserAccountID && (r.stateNum ?? 0) <= 1 && r.policyID === report.policyID); return potentialReports.some( (potentialReport) => hasViolations(potentialReport.reportID, transactionViolations) || hasWarningTypeViolations(potentialReport.reportID, transactionViolations), ); @@ -6337,7 +6337,7 @@ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionV /** * Checks to see if a report contains a violation */ -function hasViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean | null): boolean { +function hasViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean): boolean { const transactions = reportsTransactions[reportID] ?? []; return transactions.some((transaction) => TransactionUtils.hasViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); } @@ -6345,7 +6345,7 @@ function hasViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean | null): boolean { +function hasWarningTypeViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean): boolean { const transactions = reportsTransactions[reportID] ?? []; return transactions.some((transaction) => TransactionUtils.hasWarningTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 5992dffd44b2..da046e684a9f 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -240,16 +240,11 @@ function getReasonAndReportActionThatHasRedBrickRoad( ): ReasonAndReportActionThatHasRedBrickRoad | null { const {errors, reportAction} = ReportUtils.getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions); const hasErrors = Object.keys(errors).length !== 0; - const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, ReportActionsUtils.getAllReportActions(report.reportID)); - if (oneTransactionThreadReportID) { - const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); - - if (ReportUtils.shouldDisplayViolationsRBRInLHN(oneTransactionThreadReport, transactionViolations)) { - return { - reason: CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS, - }; - } + if (ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations)) { + return { + reason: CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS, + }; } if (hasErrors) { diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 7a6fc135dc30..e715c82123fd 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -809,18 +809,18 @@ function isOnHoldByTransactionID(transactionID: string): boolean { /** * Checks if any violations for the provided transaction are of type 'violation' */ -function hasViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean | null): boolean { +function hasViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === null || showInReview === (violation.showInReview ?? false)), + (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), ); } /** * Checks if any violations for the provided transaction are of type 'notice' */ -function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean | null): boolean { +function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && (showInReview === null || showInReview === (violation.showInReview ?? false)), + (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), ); } diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index c5d84341deee..3f9f28160b0a 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -693,46 +693,6 @@ describe('DebugUtils', () => { }); expect(reason).toBe('debug.reasonVisibleInLHN.pinnedByUser'); }); - it('returns correct reason when report has IOU violations', async () => { - const threadReport = { - ...baseReport, - stateNum: CONST.REPORT.STATE_NUM.OPEN, - statusNum: CONST.REPORT.STATUS_NUM.OPEN, - parentReportID: '0', - parentReportActionID: '0', - }; - await Onyx.multiSet({ - [ONYXKEYS.SESSION]: { - accountID: 1234, - }, - [`${ONYXKEYS.COLLECTION.REPORT}0` as const]: { - reportID: '0', - type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: 1234, - }, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}0` as const]: { - // eslint-disable-next-line @typescript-eslint/naming-convention - '0': { - reportActionID: '0', - actionName: CONST.REPORT.ACTIONS.TYPE.IOU, - message: { - type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, - IOUTransactionID: '0', - IOUReportID: '0', - }, - }, - }, - [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: threadReport, - [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}0` as const]: [ - { - type: CONST.VIOLATION_TYPES.VIOLATION, - name: CONST.VIOLATIONS.MODIFIED_AMOUNT, - }, - ], - }); - const reason = DebugUtils.getReasonForShowingRowInLHN(threadReport); - expect(reason).toBe('debug.reasonVisibleInLHN.hasIOUViolations'); - }); it('returns correct reason when report has add workspace room errors', () => { const reason = DebugUtils.getReasonForShowingRowInLHN({ ...baseReport, @@ -1530,28 +1490,13 @@ describe('DebugUtils', () => { ) ?? {}; expect(reason).toBe('debug.reasonRBR.hasViolations'); }); - it('returns correct reason when there are transaction thread violations', async () => { + it('returns correct reason when there are reports on the workspace chat with violations', async () => { const report: Report = { reportID: '0', - type: CONST.REPORT.TYPE.EXPENSE, + type: CONST.REPORT.TYPE.CHAT, ownerAccountID: 1234, - }; - const reportActions: ReportActions = { - // eslint-disable-next-line @typescript-eslint/naming-convention - '0': { - reportActionID: '0', - actionName: CONST.REPORT.ACTIONS.TYPE.IOU, - message: { - type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, - IOUTransactionID: '0', - IOUReportID: '0', - amount: 10, - currency: CONST.CURRENCY.USD, - text: '', - }, - created: '2024-07-13 06:02:11.111', - childReportID: '1', - }, + policyID: '1', + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, }; await Onyx.multiSet({ [ONYXKEYS.SESSION]: { @@ -1562,17 +1507,23 @@ describe('DebugUtils', () => { reportID: '1', parentReportActionID: '0', stateNum: CONST.REPORT.STATE_NUM.OPEN, - statusNum: CONST.REPORT.STATE_NUM.SUBMITTED, + ownerAccountID: 1234, + policyID: '1', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}0` as const]: { + transactionID: '1', + amount: 10, + modifiedAmount: 10, + reportID: '0', }, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}0` as const]: reportActions, - [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}0` as const]: [ + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}1` as const]: [ { type: CONST.VIOLATION_TYPES.VIOLATION, - name: CONST.VIOLATIONS.MODIFIED_AMOUNT, + name: CONST.VIOLATIONS.MISSING_CATEGORY, }, ], }); - const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, reportActions, false) ?? {}; + const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, {}, false) ?? {}; expect(reason).toBe('debug.reasonRBR.hasTransactionThreadViolations'); }); }); From 976c78f0427a4fd355523dc8a648600ace9c5703 Mon Sep 17 00:00:00 2001 From: truph01 Date: Wed, 6 Nov 2024 00:44:01 +0700 Subject: [PATCH 035/111] fix: lint --- tests/unit/WorkspaceSettingsUtilsTest.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts index 427356c0ccc4..bd765dad8cab 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.ts +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -1,9 +1,9 @@ -import {OnyxCollection} from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import {getBrickRoadForPolicy} from '@libs/WorkspacesSettingsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import {Report, ReportActions} from '@src/types/onyx'; -import {ReportCollectionDataSet} from '@src/types/onyx/Report'; +import type {Report, ReportActions} from '@src/types/onyx'; +import type {ReportCollectionDataSet} from '@src/types/onyx/Report'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -60,6 +60,7 @@ describe('WorkspacesSettingsUtils', () => { }; const actions: OnyxCollection = { + // eslint-disable-next-line @typescript-eslint/naming-convention reportActions_1699789757771388: { // eslint-disable-next-line @typescript-eslint/naming-convention '4007735288062946397': { @@ -146,6 +147,7 @@ describe('WorkspacesSettingsUtils', () => { whisperedToAccountIDs: [], }, }, + // eslint-disable-next-line @typescript-eslint/naming-convention reportActions_4148694821839494: { // eslint-disable-next-line @typescript-eslint/naming-convention '2964625714811661556': { @@ -203,6 +205,7 @@ describe('WorkspacesSettingsUtils', () => { shouldShow: true, }, }, + // eslint-disable-next-line @typescript-eslint/naming-convention reportActions_4625283659773773: { // eslint-disable-next-line @typescript-eslint/naming-convention '7132923952865070123': { @@ -381,6 +384,7 @@ describe('WorkspacesSettingsUtils', () => { shouldShow: true, }, }, + // eslint-disable-next-line @typescript-eslint/naming-convention reportActions_6955627196303088: { // eslint-disable-next-line @typescript-eslint/naming-convention '1493209744740418100': { @@ -500,6 +504,7 @@ describe('WorkspacesSettingsUtils', () => { whisperedToAccountIDs: [], }, }, + // eslint-disable-next-line @typescript-eslint/naming-convention reportActions_4286515777714555: { // eslint-disable-next-line @typescript-eslint/naming-convention '1995312838979534584': { @@ -531,6 +536,7 @@ describe('WorkspacesSettingsUtils', () => { shouldShow: true, }, }, + // eslint-disable-next-line @typescript-eslint/naming-convention reportActions_7900715127836904: { // eslint-disable-next-line @typescript-eslint/naming-convention '3536855248086336861': { @@ -570,12 +576,14 @@ describe('WorkspacesSettingsUtils', () => { [ONYXKEYS.SESSION]: { accountID: 18634488, }, + // eslint-disable-next-line @typescript-eslint/naming-convention transactionViolations_3106135972713435169: [ { name: 'missingCategory', type: 'violation', }, ], + // eslint-disable-next-line @typescript-eslint/naming-convention transactionViolations_3690687111940510713: [ { name: 'missingCategory', From 2b630e9899816e835c7220b9f71b3574d390561c Mon Sep 17 00:00:00 2001 From: Sachin Chavda Date: Wed, 6 Nov 2024 00:17:24 +0530 Subject: [PATCH 036/111] close focus notification when clicking backdrop or back button --- src/components/FocusModeNotification.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/FocusModeNotification.tsx b/src/components/FocusModeNotification.tsx index 7b3f567d256b..fe63fb4b487b 100644 --- a/src/components/FocusModeNotification.tsx +++ b/src/components/FocusModeNotification.tsx @@ -24,6 +24,8 @@ function FocusModeNotification() { confirmText={translate('common.buttonConfirm')} onConfirm={User.clearFocusModeNotification} shouldShowCancelButton={false} + onBackdropPress={User.clearFocusModeNotification} + onCancel={User.clearFocusModeNotification} prompt={ {translate('focusModeUpdateModal.prompt')} From bb451f523afc55a4351bf85ba72407011f73b31b Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:33:35 +1300 Subject: [PATCH 037/111] Rename the patch --- ...patch => react-native+0.75.2+020+keyboard-avoiding-view.patch} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename patches/{react-native+0.75.2+018+keyboard-avoiding-view.patch => react-native+0.75.2+020+keyboard-avoiding-view.patch} (100%) diff --git a/patches/react-native+0.75.2+018+keyboard-avoiding-view.patch b/patches/react-native+0.75.2+020+keyboard-avoiding-view.patch similarity index 100% rename from patches/react-native+0.75.2+018+keyboard-avoiding-view.patch rename to patches/react-native+0.75.2+020+keyboard-avoiding-view.patch From 43012b5fc1429ca738f63bf27ade6533ca21ba20 Mon Sep 17 00:00:00 2001 From: war-in Date: Wed, 6 Nov 2024 12:00:27 +0100 Subject: [PATCH 038/111] fix the logic after merging main --- src/libs/actions/Session/index.ts | 39 ++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 2f30f2caaa1a..eda761b9637b 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -484,13 +484,24 @@ function signUpUser() { function signInAfterTransitionFromOldDot(transitionURL: string) { const [route, queryParams] = transitionURL.split('?'); - const {email, authToken, encryptedAuthToken, accountID, autoGeneratedLogin, autoGeneratedPassword, clearOnyxOnStart, completedHybridAppOnboarding, isSingleNewDotEntry, primaryLogin, shouldRemoveDelegatedAccess} = - Object.fromEntries( - queryParams.split('&').map((param) => { - const [key, value] = param.split('='); - return [key, value]; - }), - ); + const { + email, + authToken, + encryptedAuthToken, + accountID, + autoGeneratedLogin, + autoGeneratedPassword, + clearOnyxOnStart, + completedHybridAppOnboarding, + isSingleNewDotEntry, + primaryLogin, + shouldRemoveDelegatedAccess, + } = Object.fromEntries( + queryParams.split('&').map((param) => { + const [key, value] = param.split('='); + return [key, value]; + }), + ); const clearOnyxForNewAccount = () => { if (clearOnyxOnStart !== 'true') { @@ -502,6 +513,12 @@ function signInAfterTransitionFromOldDot(transitionURL: string) { const setSessionDataAndOpenApp = new Promise((resolve) => { clearOnyxForNewAccount() + .then(() => { + if (!shouldRemoveDelegatedAccess) { + return; + } + return Onyx.clear(KEYS_TO_PRESERVE_DELEGATE_ACCESS); + }) .then(() => Onyx.multiSet({ [ONYXKEYS.SESSION]: {email, authToken, encryptedAuthToken: decodeURIComponent(encryptedAuthToken), accountID: Number(accountID)}, @@ -511,13 +528,7 @@ function signInAfterTransitionFromOldDot(transitionURL: string) { [ONYXKEYS.NVP_TRYNEWDOT]: {classicRedirect: {completedHybridAppOnboarding: completedHybridAppOnboarding === 'true'}}, }), ) - .then(() => { - if (shouldRemoveDelegatedAccess) { - Onyx.clear(KEYS_TO_PRESERVE_DELEGATE_ACCESS).then(App.openApp); - } else { - App.openApp(); - } - }) + .then(App.openApp) .catch((error) => { Log.hmmm('[HybridApp] Initialization of HybridApp has failed. Forcing transition', {error}); }) From e156dca948e0cd0392146e31736afde3e6782a38 Mon Sep 17 00:00:00 2001 From: truph01 Date: Thu, 7 Nov 2024 09:46:18 +0700 Subject: [PATCH 039/111] fix: add comment to test and create json file --- tests/unit/WorkspaceSettingsUtilsTest.json | 542 +++++++++++++++++++ tests/unit/WorkspaceSettingsUtilsTest.ts | 586 +-------------------- 2 files changed, 558 insertions(+), 570 deletions(-) create mode 100644 tests/unit/WorkspaceSettingsUtilsTest.json diff --git a/tests/unit/WorkspaceSettingsUtilsTest.json b/tests/unit/WorkspaceSettingsUtilsTest.json new file mode 100644 index 000000000000..7408429a5c1f --- /dev/null +++ b/tests/unit/WorkspaceSettingsUtilsTest.json @@ -0,0 +1,542 @@ +{ + "session": { + "accountID": 18634488 + }, + "reports": { + "report_4286515777714555": { + "isOptimisticReport": false, + "type": "chat", + "isOwnPolicyExpenseChat": false, + "isPinned": false, + "lastActorAccountID": 0, + "lastMessageTranslationKey": "", + "lastMessageHtml": "", + "lastReadTime": "2024-11-05 11:19:18.288", + "lastVisibleActionCreated": "2024-11-05 11:19:18.288", + "oldPolicyName": "", + "ownerAccountID": 0, + "parentReportActionID": "8722650843049927838", + "parentReportID": "6955627196303088", + "policyID": "57D0F454E0BCE54B", + "reportID": "4286515777714555", + "reportName": "Expense", + "stateNum": 0, + "statusNum": 0, + "description": "", + "avatarUrl": "", + "avatarFileName": "" + }, + "report_6955627196303088": { + "reportID": "6955627196303088", + "chatReportID": "1699789757771388", + "policyID": "57D0F454E0BCE54B", + "type": "expense", + "ownerAccountID": 18634488, + "stateNum": 1, + "statusNum": 1, + "parentReportID": "1699789757771388", + "parentReportActionID": "7978085421707288417" + } + }, + "transactionViolations": { + "transactionViolations_3106135972713435169": [ + { + "name": "missingCategory", + "type": "violation" + } + ], + "transactionViolations_3690687111940510713": [ + { + "name": "missingCategory", + "type": "violation" + } + ] + }, + "reportActions": { + "reportActions_1699789757771388": { + "4007735288062946397": { + "reportActionID": "4007735288062946397", + "actionName": "CREATED", + "actorAccountID": 18634488, + "message": [ + { + "type": "TEXT", + "style": "strong", + "text": "You" + }, + { + "type": "TEXT", + "style": "normal", + "text": " created this report" + } + ], + "person": [ + { + "type": "TEXT", + "style": "strong", + "text": "adasdasd" + } + ], + "automatic": false, + "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", + "created": "2024-11-05 11: 10: 47.852", + "shouldShow": true, + "reportActionTimestamp": 1730805047852, + "sequenceNumber": 0, + "lastModified": "2024-11-05 11: 10: 47.852" + }, + "7978085421707288417": { + "reportActionID": "7978085421707288417", + "reportID": "1699789757771388", + "actionName": "REPORTPREVIEW", + "originalMessage": {}, + "message": [ + { + "deleted": "", + "html": "Adasdasd's Workspace owes ₫579", + "isDeletedParentAction": false, + "isEdited": false, + "text": "Adasdasd's Workspace owes ₫579", + "type": "COMMENT", + "whisperedTo": [] + } + ], + "created": "2024-11-05 11: 19: 18.710", + "accountID": 18634488, + "actorAccountID": 18634488, + "childReportID": "6955627196303088", + "childMoneyRequestCount": 2, + "childLastMoneyRequestComment": "", + "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", + "childCommenterCount": 0, + "childLastActorAccountID": 18634488, + "childLastVisibleActionCreated": "", + "childOldestFourAccountIDs": "", + "childReportNotificationPreference": "hidden", + "childType": "expense", + "childVisibleActionCount": 0, + "lastModified": "2024-11-05 11: 19: 18.710", + "person": [ + { + "style": "strong", + "text": "adasdasd", + "type": "TEXT" + } + ], + "shouldShow": true, + "automatic": false, + "childManagerAccountID": 18634488, + "childOwnerAccountID": 18634488, + "childReportName": "Expense Report #6955627196303088", + "childStateNum": 1, + "childStatusNum": 1, + "reportActionTimestamp": 1730805558710, + "timestamp": 1730805558, + "whisperedToAccountIDs": [] + } + }, + "reportActions_4148694821839494": { + "2964625714811661556": { + "reportActionID": "2964625714811661556", + "actionName": "CREATED", + "actorAccountID": 18634488, + "message": [ + { + "type": "TEXT", + "style": "strong", + "text": "_FAKE_" + }, + { + "type": "TEXT", + "style": "normal", + "text": " created this report" + } + ], + "person": [ + { + "type": "TEXT", + "style": "strong", + "text": "adasdasd" + } + ], + "automatic": false, + "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", + "created": "2024-11-05 11: 10: 47.077", + "shouldShow": true + }, + "5971844472086652538": { + "actionName": "ADDCOMMENT", + "actorAccountID": 10288574, + "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/26271df52c9c9db57fa0221feaef04d18a045f2b_128.jpeg", + "created": "2024-11-05 11: 11: 47.410", + "lastModified": "2024-11-05 11: 11: 47.410", + "message": [ + { + "html": "

Let's get you set up on Expensify

Hi there 👋 , I'm your dedicated setup specialist. I look forward to helping you explore and configure Expensify. A few important things to mention:
  • Your workspace has a ton of custom settings, just select your workspace settings to set it up.
  • You've got more functionality to enable, like custom categories, tags, etc. Just ask me how.
Chat with me here in your #admins room or just reply to this message. You can also schedule a call if you have more in-depth questions. Talk soon!

\"Setup", + "text": "Let's get you set up on Expensify\nHi there 👋 , I'm your dedicated setup specialist. I look forward to helping you explore and configure Expensify. A few important things to mention: \nYour workspace has a ton of custom settings, just select your workspace settings to set it up.You've got more functionality to enable, like custom categories, tags, etc. Just ask me how. Chat with me here in your #admins room or just reply to this message. You can also schedule a call if you have more in-depth questions. Talk soon!\n\n[Attachment]", + "type": "COMMENT", + "whisperedTo": [] + } + ], + "originalMessage": {}, + "person": [ + { + "style": "strong", + "text": "Setup Specialist - BreAnna Sumpter Mon-Friday GMT & EST", + "type": "TEXT" + } + ], + "reportActionID": "5971844472086652538", + "shouldShow": true + } + }, + "reportActions_4625283659773773": { + "7132923952865070123": { + "actionName": "ADDCOMMENT", + "actorAccountID": 8392101, + "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png", + "created": "2024-11-05 11: 10: 38.956", + "lastModified": "2024-11-05 11: 10: 38.956", + "message": [ + { + "type": "COMMENT", + "html": "

Welcome to Expensify

👋 Hey there, I'm Concierge! If you have any questions about Expensify, you can always chat with me here 24-7 for fast and reliable support. I'm happy to help!", + "text": "Welcome to Expensify\n👋 Hey there, I'm Concierge! If you have any questions about Expensify, you can always chat with me here 24-7 for fast and reliable support. I'm happy to help!", + "isEdited": false, + "whisperedTo": [], + "isDeletedParentAction": false, + "deleted": "" + } + ], + "originalMessage": {}, + "person": [ + { + "type": "TEXT", + "style": "strong", + "text": "Expensify Concierge" + } + ], + "reportActionID": "7132923952865070123", + "shouldShow": true, + "timestamp": 1730805038, + "reportActionTimestamp": 1730805038956, + "automatic": false, + "whisperedToAccountIDs": [] + }, + "5686837203726341682": { + "reportActionID": "5686837203726341682", + "actionName": "CREATED", + "created": "2024-11-05 11: 10: 38.688", + "reportActionTimestamp": 1730805038688, + "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", + "message": [ + { + "type": "TEXT", + "style": "strong", + "text": "__fake__" + }, + { + "type": "TEXT", + "style": "normal", + "text": " created this report" + } + ], + "person": [ + { + "type": "TEXT", + "style": "strong", + "text": "__fake__" + } + ], + "automatic": false, + "sequenceNumber": 0, + "shouldShow": true, + "lastModified": "2024-11-05 11: 10: 38.688" + }, + "536505040772198026": { + "reportActionID": "536505040772198026", + "actionName": "ADDCOMMENT", + "actorAccountID": 8392101, + "person": [ + { + "style": "strong", + "text": "Expensify Concierge", + "type": "TEXT" + } + ], + "automatic": false, + "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png", + "created": "2024-11-05 11: 10: 43.943", + "message": [ + { + "html": "Let’s get you set up 🔧", + "text": "Let’s get you set up 🔧", + "type": "COMMENT", + "whisperedTo": [] + } + ], + "originalMessage": {}, + "isFirstItem": false, + "isAttachmentWithText": false, + "shouldShow": true, + "isOptimisticAction": true, + "lastModified": "2024-11-05 11: 10: 43.943" + }, + "5286007009323266706": { + "reportActionID": "5286007009323266706", + "actionName": "ADDCOMMENT", + "actorAccountID": 8392101, + "person": [ + { + "style": "strong", + "text": "Expensify Concierge", + "type": "TEXT" + } + ], + "automatic": false, + "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png", + "created": "2024-11-05 11: 10: 43.944", + "message": [ + { + "html": "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", + "text": "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", + "type": "COMMENT", + "whisperedTo": [] + } + ], + "originalMessage": {}, + "isFirstItem": false, + "isAttachmentWithText": false, + "shouldShow": true, + "isOptimisticAction": true, + "lastModified": "2024-11-05 11: 10: 43.944" + }, + "4422060638727390382": { + "actionName": "ADDCOMMENT", + "actorAccountID": 8392101, + "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png", + "created": "2024-11-05 11: 11: 47.086", + "lastModified": "2024-11-05 11: 11: 47.086", + "message": [ + { + "html": "Let's get your company set up! Setup Specialist - BreAnna, your dedicated specialist, is online now and can answer your initial questions or provide a demo.

💬 CHAT WITH YOUR SETUP SPECIALIST", + "text": "Let's get your company set up! Setup Specialist - BreAnna, your dedicated specialist, is online now and can answer your initial questions or provide a demo.\n\n💬 CHAT WITH YOUR SETUP SPECIALIST", + "type": "COMMENT", + "whisperedTo": [] + } + ], + "originalMessage": {}, + "person": [ + { + "style": "strong", + "text": "Expensify Concierge", + "type": "TEXT" + } + ], + "reportActionID": "4422060638727390382", + "shouldShow": true + }, + "2824688328360312239": { + "actionName": "ADDCOMMENT", + "actorAccountID": 8392101, + "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png", + "created": "2024-11-05 11: 19: 18.703", + "lastModified": "2024-11-05 11: 19: 18.703", + "message": [ + { + "html": "

You’ve started a free trial!


Welcome to your free 7-day trial of Expensify 🎉 Use it to continue exploring your workspace's benefits, including tracking expenses, reimbursing employees, managing company spend, and more.

If you have any questions, chat with your dedicated Setup Specialist in #admins. Enjoy!", + "text": "You’ve started a free trial!\n\nWelcome to your free 7-day trial of Expensify 🎉 Use it to continue exploring your workspace's benefits, including tracking expenses, reimbursing employees, managing company spend, and more.\n\nIf you have any questions, chat with your dedicated Setup Specialist in #admins. Enjoy!", + "type": "COMMENT", + "whisperedTo": [] + } + ], + "originalMessage": {}, + "person": [ + { + "style": "strong", + "text": "Expensify Concierge", + "type": "TEXT" + } + ], + "reportActionID": "2824688328360312239", + "shouldShow": true + } + }, + "reportActions_6955627196303088": { + "1493209744740418100": { + "reportActionID": "1493209744740418100", + "actionName": "CREATED", + "actorAccountID": 18634488, + "message": [ + { + "type": "TEXT", + "style": "strong", + "text": "ajsdjajdjadjajsjajdsj123@gmail.com" + }, + { + "type": "TEXT", + "style": "normal", + "text": " created this report" + } + ], + "person": [ + { + "type": "TEXT", + "style": "strong", + "text": "adasdasd" + } + ], + "automatic": false, + "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", + "created": "2024-11-05 11: 19: 18.285", + "shouldShow": true + }, + "8722650843049927838": { + "actionName": "IOU", + "actorAccountID": 18634488, + "automatic": false, + "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", + "isAttachmentOnly": false, + "originalMessage": { + "amount": 12300, + "comment": "", + "currency": "VND", + "IOUTransactionID": "3106135972713435169", + "IOUReportID": "6955627196303088" + }, + "message": [ + { + "deleted": "", + "html": "₫123 expense", + "isDeletedParentAction": false, + "isEdited": false, + "text": "₫123 expense", + "type": "COMMENT", + "whisperedTo": [] + } + ], + "person": [ + { + "style": "strong", + "text": "adasdasd", + "type": "TEXT" + } + ], + "reportActionID": "8722650843049927838", + "shouldShow": true, + "created": "2024-11-05 11: 19: 18.706", + "childReportID": "4286515777714555", + "lastModified": "2024-11-05 11: 19: 18.706", + "childReportNotificationPreference": "hidden", + "childType": "chat", + "reportActionTimestamp": 1730805558706, + "sequenceNumber": 1, + "timestamp": 1730805558, + "whisperedToAccountIDs": [] + }, + "1783566081350093529": { + "actionName": "IOU", + "actorAccountID": 18634488, + "automatic": false, + "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", + "isAttachmentOnly": false, + "originalMessage": { + "amount": 45600, + "comment": "", + "currency": "VND", + "IOUTransactionID": "3690687111940510713", + "IOUReportID": "6955627196303088" + }, + "message": [ + { + "deleted": "", + "html": "₫456 expense", + "isDeletedParentAction": false, + "isEdited": false, + "text": "₫456 expense", + "type": "COMMENT", + "whisperedTo": [] + } + ], + "person": [ + { + "style": "strong", + "text": "adasdasd", + "type": "TEXT" + } + ], + "reportActionID": "1783566081350093529", + "shouldShow": true, + "created": "2024-11-05 11: 20: 22.065", + "childReportID": "7900715127836904", + "lastModified": "2024-11-05 11: 20: 22.065", + "childReportNotificationPreference": "hidden", + "childType": "chat", + "reportActionTimestamp": 1730805622065, + "sequenceNumber": 2, + "timestamp": 1730805622, + "whisperedToAccountIDs": [] + } + }, + "reportActions_4286515777714555": { + "1995312838979534584": { + "reportActionID": "1995312838979534584", + "actionName": "CREATED", + "actorAccountID": 18634488, + "message": [ + { + "type": "TEXT", + "style": "strong", + "text": "ajsdjajdjadjajsjajdsj123@gmail.com" + }, + { + "type": "TEXT", + "style": "normal", + "text": " created this report" + } + ], + "person": [ + { + "type": "TEXT", + "style": "strong", + "text": "adasdasd" + } + ], + "automatic": false, + "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", + "created": "2024-11-05 11: 19: 18.288", + "shouldShow": true + } + }, + "reportActions_7900715127836904": { + "3536855248086336861": { + "reportActionID": "3536855248086336861", + "actionName": "CREATED", + "actorAccountID": 18634488, + "message": [ + { + "type": "TEXT", + "style": "strong", + "text": "ajsdjajdjadjajsjajdsj123@gmail.com" + }, + { + "type": "TEXT", + "style": "normal", + "text": " created this report" + } + ], + "person": [ + { + "type": "TEXT", + "style": "strong", + "text": "adasdasd" + } + ], + "automatic": false, + "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", + "created": "2024-11-05 11: 20: 21.589", + "shouldShow": true + } + } + } +} \ No newline at end of file diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts index bd765dad8cab..2e7d82263e41 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.ts +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -2,10 +2,11 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import {getBrickRoadForPolicy} from '@libs/WorkspacesSettingsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report, ReportActions} from '@src/types/onyx'; +import type {Report, ReportActions, TransactionViolations} from '@src/types/onyx'; import type {ReportCollectionDataSet} from '@src/types/onyx/Report'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import mockData from './WorkspaceSettingsUtilsTest.json'; describe('WorkspacesSettingsUtils', () => { beforeAll(() => { @@ -20,581 +21,26 @@ describe('WorkspacesSettingsUtils', () => { }); describe('getBrickRoadForPolicy', () => { it('Should return "error"', async () => { - const report: Report = { - isOptimisticReport: false, - type: 'chat', - isOwnPolicyExpenseChat: false, - isPinned: false, - lastActorAccountID: 0, - lastMessageTranslationKey: '', - lastMessageHtml: '', - lastReadTime: '2024-11-05 11:19:18.288', - lastVisibleActionCreated: '2024-11-05 11:19:18.288', - oldPolicyName: '', - ownerAccountID: 0, - parentReportActionID: '8722650843049927838', - parentReportID: '6955627196303088', - policyID: '57D0F454E0BCE54B', - reportID: '4286515777714555', - reportName: 'Expense', - stateNum: 0, - statusNum: 0, - description: '', - avatarUrl: '', - avatarFileName: '', - }; - - const MOCK_REPORTS: ReportCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.REPORT}4286515777714555` as const]: report, - [`${ONYXKEYS.COLLECTION.REPORT}6955627196303088` as const]: { - reportID: '6955627196303088', - chatReportID: '1699789757771388', - policyID: '57D0F454E0BCE54B', - type: 'expense', - ownerAccountID: 18634488, - stateNum: 1, - statusNum: 1, - parentReportID: '1699789757771388', - parentReportActionID: '7978085421707288417', - }, - }; - - const actions: OnyxCollection = { - // eslint-disable-next-line @typescript-eslint/naming-convention - reportActions_1699789757771388: { - // eslint-disable-next-line @typescript-eslint/naming-convention - '4007735288062946397': { - reportActionID: '4007735288062946397', - actionName: 'CREATED', - actorAccountID: 18634488, - message: [ - { - type: 'TEXT', - style: 'strong', - text: 'You', - }, - { - type: 'TEXT', - style: 'normal', - text: ' created this report', - }, - ], - person: [ - { - type: 'TEXT', - style: 'strong', - text: 'adasdasd', - }, - ], - automatic: false, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', - created: '2024-11-05 11:10:47.852', - shouldShow: true, - reportActionTimestamp: 1730805047852, - sequenceNumber: 0, - lastModified: '2024-11-05 11:10:47.852', - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - '7978085421707288417': { - reportActionID: '7978085421707288417', - reportID: '1699789757771388', - actionName: 'REPORTPREVIEW', - originalMessage: {}, - message: [ - { - deleted: '', - html: "Adasdasd's Workspace owes ₫579", - isDeletedParentAction: false, - isEdited: false, - text: "Adasdasd's Workspace owes ₫579", - type: 'COMMENT', - whisperedTo: [], - }, - ], - created: '2024-11-05 11:19:18.710', - accountID: 18634488, - actorAccountID: 18634488, - childReportID: '6955627196303088', - childMoneyRequestCount: 2, - childLastMoneyRequestComment: '', - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', - childCommenterCount: 0, - childLastActorAccountID: 18634488, - - childLastVisibleActionCreated: '', - childOldestFourAccountIDs: '', - childReportNotificationPreference: 'hidden', - childType: 'expense', - childVisibleActionCount: 0, - lastModified: '2024-11-05 11:19:18.710', - person: [ - { - style: 'strong', - text: 'adasdasd', - type: 'TEXT', - }, - ], - shouldShow: true, - automatic: false, - childManagerAccountID: 18634488, - childOwnerAccountID: 18634488, - childReportName: 'Expense Report #6955627196303088', - childStateNum: 1, - childStatusNum: 1, - - reportActionTimestamp: 1730805558710, - timestamp: 1730805558, - whisperedToAccountIDs: [], - }, - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - reportActions_4148694821839494: { - // eslint-disable-next-line @typescript-eslint/naming-convention - '2964625714811661556': { - reportActionID: '2964625714811661556', - actionName: 'CREATED', - actorAccountID: 18634488, - message: [ - { - type: 'TEXT', - style: 'strong', - text: '_FAKE_', - }, - { - type: 'TEXT', - style: 'normal', - text: ' created this report', - }, - ], - person: [ - { - type: 'TEXT', - style: 'strong', - text: 'adasdasd', - }, - ], - automatic: false, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', - created: '2024-11-05 11:10:47.077', - shouldShow: true, - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - '5971844472086652538': { - actionName: 'ADDCOMMENT', - actorAccountID: 10288574, - avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/26271df52c9c9db57fa0221feaef04d18a045f2b_128.jpeg', - created: '2024-11-05 11:11:47.410', - lastModified: '2024-11-05 11:11:47.410', - message: [ - { - html: '

Let\'s get you set up on Expensify

Hi there 👋 , I\'m your dedicated setup specialist. I look forward to helping you explore and configure Expensify. A few important things to mention:
  • Your workspace has a ton of custom settings, just select your workspace settings to set it up.
  • You\'ve got more functionality to enable, like custom categories, tags, etc. Just ask me how.
Chat with me here in your #admins room or just reply to this message. You can also schedule a call if you have more in-depth questions. Talk soon!

Setup Guide GIF', - text: "Let's get you set up on Expensify\nHi there 👋 , I'm your dedicated setup specialist. I look forward to helping you explore and configure Expensify. A few important things to mention: \nYour workspace has a ton of custom settings, just select your workspace settings to set it up.You've got more functionality to enable, like custom categories, tags, etc. Just ask me how. Chat with me here in your #admins room or just reply to this message. You can also schedule a call if you have more in-depth questions. Talk soon!\n\n[Attachment]", - type: 'COMMENT', - whisperedTo: [], - }, - ], - originalMessage: {}, - person: [ - { - style: 'strong', - text: 'Setup Specialist - BreAnna Sumpter Mon-Friday GMT & EST', - type: 'TEXT', - }, - ], - reportActionID: '5971844472086652538', - shouldShow: true, - }, - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - reportActions_4625283659773773: { - // eslint-disable-next-line @typescript-eslint/naming-convention - '7132923952865070123': { - actionName: 'ADDCOMMENT', - actorAccountID: 8392101, - avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png', - created: '2024-11-05 11:10:38.956', - lastModified: '2024-11-05 11:10:38.956', - message: [ - { - type: 'COMMENT', - html: "

Welcome to Expensify

👋 Hey there, I'm Concierge! If you have any questions about Expensify, you can always chat with me here 24-7 for fast and reliable support. I'm happy to help!", - text: "Welcome to Expensify\n👋 Hey there, I'm Concierge! If you have any questions about Expensify, you can always chat with me here 24-7 for fast and reliable support. I'm happy to help!", - isEdited: false, - whisperedTo: [], - isDeletedParentAction: false, - deleted: '', - }, - ], - originalMessage: {}, - person: [ - { - type: 'TEXT', - style: 'strong', - text: 'Expensify Concierge', - }, - ], - reportActionID: '7132923952865070123', - shouldShow: true, - timestamp: 1730805038, - reportActionTimestamp: 1730805038956, - automatic: false, - whisperedToAccountIDs: [], - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - '5686837203726341682': { - reportActionID: '5686837203726341682', - actionName: 'CREATED', - created: '2024-11-05 11:10:38.688', - reportActionTimestamp: 1730805038688, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', - message: [ - { - type: 'TEXT', - style: 'strong', - text: '__fake__', - }, - { - type: 'TEXT', - style: 'normal', - text: ' created this report', - }, - ], - person: [ - { - type: 'TEXT', - style: 'strong', - text: '__fake__', - }, - ], - automatic: false, - sequenceNumber: 0, - shouldShow: true, - lastModified: '2024-11-05 11:10:38.688', - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - '536505040772198026': { - reportActionID: '536505040772198026', - actionName: 'ADDCOMMENT', - actorAccountID: 8392101, - person: [ - { - style: 'strong', - text: 'Expensify Concierge', - type: 'TEXT', - }, - ], - automatic: false, - avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png', - created: '2024-11-05 11:10:43.943', - message: [ - { - html: 'Let’s get you set up 🔧', - text: 'Let’s get you set up 🔧', - type: 'COMMENT', - whisperedTo: [], - }, - ], - originalMessage: {}, - isFirstItem: false, - isAttachmentWithText: false, - shouldShow: true, - isOptimisticAction: true, - lastModified: '2024-11-05 11:10:43.943', - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - '5286007009323266706': { - reportActionID: '5286007009323266706', - actionName: 'ADDCOMMENT', - actorAccountID: 8392101, - person: [ - { - style: 'strong', - text: 'Expensify Concierge', - type: 'TEXT', - }, - ], - automatic: false, - avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png', - created: '2024-11-05 11:10:43.944', - message: [ - { - html: "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", - text: "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", - type: 'COMMENT', - whisperedTo: [], - }, - ], - originalMessage: {}, - isFirstItem: false, - isAttachmentWithText: false, - shouldShow: true, - isOptimisticAction: true, - lastModified: '2024-11-05 11:10:43.944', - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - '4422060638727390382': { - actionName: 'ADDCOMMENT', - actorAccountID: 8392101, - avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png', - created: '2024-11-05 11:11:47.086', - lastModified: '2024-11-05 11:11:47.086', - message: [ - { - html: 'Let\'s get your company set up! Setup Specialist - BreAnna, your dedicated specialist, is online now and can answer your initial questions or provide a demo.

💬 CHAT WITH YOUR SETUP SPECIALIST', - text: "Let's get your company set up! Setup Specialist - BreAnna, your dedicated specialist, is online now and can answer your initial questions or provide a demo.\n\n💬 CHAT WITH YOUR SETUP SPECIALIST", - type: 'COMMENT', - whisperedTo: [], - }, - ], - originalMessage: {}, - person: [ - { - style: 'strong', - text: 'Expensify Concierge', - type: 'TEXT', - }, - ], - reportActionID: '4422060638727390382', - shouldShow: true, - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - '2824688328360312239': { - actionName: 'ADDCOMMENT', - actorAccountID: 8392101, - avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png', - created: '2024-11-05 11:19:18.703', - lastModified: '2024-11-05 11:19:18.703', - message: [ - { - html: '

You’ve started a free trial!


Welcome to your free 7-day trial of Expensify 🎉 Use it to continue exploring your workspace\'s benefits, including tracking expenses, reimbursing employees, managing company spend, and more.

If you have any questions, chat with your dedicated Setup Specialist in #admins. Enjoy!', - text: "You’ve started a free trial!\n\nWelcome to your free 7-day trial of Expensify 🎉 Use it to continue exploring your workspace's benefits, including tracking expenses, reimbursing employees, managing company spend, and more.\n\nIf you have any questions, chat with your dedicated Setup Specialist in #admins. Enjoy!", - type: 'COMMENT', - whisperedTo: [], - }, - ], - originalMessage: {}, - person: [ - { - style: 'strong', - text: 'Expensify Concierge', - type: 'TEXT', - }, - ], - reportActionID: '2824688328360312239', - shouldShow: true, - }, - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - reportActions_6955627196303088: { - // eslint-disable-next-line @typescript-eslint/naming-convention - '1493209744740418100': { - reportActionID: '1493209744740418100', - actionName: 'CREATED', - actorAccountID: 18634488, - message: [ - { - type: 'TEXT', - style: 'strong', - text: 'ajsdjajdjadjajsjajdsj123@gmail.com', - }, - { - type: 'TEXT', - style: 'normal', - text: ' created this report', - }, - ], - person: [ - { - type: 'TEXT', - style: 'strong', - text: 'adasdasd', - }, - ], - automatic: false, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', - created: '2024-11-05 11:19:18.285', - shouldShow: true, - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - '8722650843049927838': { - actionName: 'IOU', - actorAccountID: 18634488, - automatic: false, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', - isAttachmentOnly: false, - originalMessage: { - amount: 12300, - comment: '', - currency: 'VND', - IOUTransactionID: '3106135972713435169', - IOUReportID: '6955627196303088', - }, - message: [ - { - deleted: '', - html: '₫123 expense', - isDeletedParentAction: false, - isEdited: false, - text: '₫123 expense', - type: 'COMMENT', - whisperedTo: [], - }, - ], - person: [ - { - style: 'strong', - text: 'adasdasd', - type: 'TEXT', - }, - ], - reportActionID: '8722650843049927838', - shouldShow: true, - created: '2024-11-05 11:19:18.706', - childReportID: '4286515777714555', - lastModified: '2024-11-05 11:19:18.706', - childReportNotificationPreference: 'hidden', - childType: 'chat', - reportActionTimestamp: 1730805558706, - sequenceNumber: 1, - timestamp: 1730805558, - whisperedToAccountIDs: [], - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - '1783566081350093529': { - actionName: 'IOU', - actorAccountID: 18634488, - automatic: false, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', - isAttachmentOnly: false, - originalMessage: { - amount: 45600, - comment: '', - currency: 'VND', - IOUTransactionID: '3690687111940510713', - IOUReportID: '6955627196303088', - }, - message: [ - { - deleted: '', - html: '₫456 expense', - isDeletedParentAction: false, - isEdited: false, - text: '₫456 expense', - type: 'COMMENT', - whisperedTo: [], - }, - ], - person: [ - { - style: 'strong', - text: 'adasdasd', - type: 'TEXT', - }, - ], - reportActionID: '1783566081350093529', - shouldShow: true, - created: '2024-11-05 11:20:22.065', - childReportID: '7900715127836904', - lastModified: '2024-11-05 11:20:22.065', - childReportNotificationPreference: 'hidden', - childType: 'chat', - reportActionTimestamp: 1730805622065, - sequenceNumber: 2, - timestamp: 1730805622, - whisperedToAccountIDs: [], - }, - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - reportActions_4286515777714555: { - // eslint-disable-next-line @typescript-eslint/naming-convention - '1995312838979534584': { - reportActionID: '1995312838979534584', - actionName: 'CREATED', - actorAccountID: 18634488, - message: [ - { - type: 'TEXT', - style: 'strong', - text: 'ajsdjajdjadjajsjajdsj123@gmail.com', - }, - { - type: 'TEXT', - style: 'normal', - text: ' created this report', - }, - ], - person: [ - { - type: 'TEXT', - style: 'strong', - text: 'adasdasd', - }, - ], - automatic: false, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', - created: '2024-11-05 11:19:18.288', - shouldShow: true, - }, - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - reportActions_7900715127836904: { - // eslint-disable-next-line @typescript-eslint/naming-convention - '3536855248086336861': { - reportActionID: '3536855248086336861', - actionName: 'CREATED', - actorAccountID: 18634488, - message: [ - { - type: 'TEXT', - style: 'strong', - text: 'ajsdjajdjadjajsjajdsj123@gmail.com', - }, - { - type: 'TEXT', - style: 'normal', - text: ' created this report', - }, - ], - person: [ - { - type: 'TEXT', - style: 'strong', - text: 'adasdasd', - }, - ], - automatic: false, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png', - created: '2024-11-05 11:20:21.589', - shouldShow: true, - }, - }, - }; + // Given mock data for reports, transaction violations, sessions, and report actions + const report = Object.values(mockData.reports)?.at(0); + const transactionViolations = mockData.transactionViolations; + const reports = mockData.reports; + const session = mockData.session; + const reportActions = mockData.reportActions; await Onyx.multiSet({ - ...MOCK_REPORTS, - ...actions, - [ONYXKEYS.SESSION]: { - accountID: 18634488, - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - transactionViolations_3106135972713435169: [ - { - name: 'missingCategory', - type: 'violation', - }, - ], - // eslint-disable-next-line @typescript-eslint/naming-convention - transactionViolations_3690687111940510713: [ - { - name: 'missingCategory', - type: 'violation', - }, - ], + ...(reports as ReportCollectionDataSet), + ...(reportActions as OnyxCollection), + ...(transactionViolations as OnyxCollection), + session, }); await waitForBatchedUpdates(); - const result = getBrickRoadForPolicy(report, actions); + // When calling getBrickRoadForPolicy with a report and report actions + const result = getBrickRoadForPolicy(report as Report, reportActions as OnyxCollection); + + // Then the result should be 'error' expect(result).toBe('error'); }); }); From e82e777b8cc38057477239852328ce7015b52b8f Mon Sep 17 00:00:00 2001 From: truph01 Date: Thu, 7 Nov 2024 09:50:14 +0700 Subject: [PATCH 040/111] fix: prettier --- tests/unit/WorkspaceSettingsUtilsTest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/WorkspaceSettingsUtilsTest.json b/tests/unit/WorkspaceSettingsUtilsTest.json index 7408429a5c1f..08b40bfacbdc 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.json +++ b/tests/unit/WorkspaceSettingsUtilsTest.json @@ -539,4 +539,4 @@ } } } -} \ No newline at end of file +} From 206cd0f63b8358893d07c3dda9c142ea6879bb5b Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 7 Nov 2024 16:05:08 +0700 Subject: [PATCH 041/111] fix: dup create workspace --- .../OnboardingAccounting/BaseOnboardingAccounting.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index 63e4b435bcd3..649f9cb74340 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -1,4 +1,5 @@ import React, {useEffect, useMemo, useState} from 'react'; +import {InteractionManager} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; @@ -60,7 +61,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding if (!isVsb || !!onboardingPolicyID) { return; } - + const {adminsChatReportID, policyID} = Policy.createWorkspace(undefined, true, '', Policy.generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM); Welcome.setOnboardingAdminsChatReportID(adminsChatReportID); Welcome.setOnboardingPolicyID(policyID); @@ -162,10 +163,10 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding onboardingCompanySize, userReportedIntegration, ); - - Welcome.setOnboardingAdminsChatReportID(); - Welcome.setOnboardingPolicyID(); - + InteractionManager.runAfterInteractions(() => { + Welcome.setOnboardingAdminsChatReportID(); + Welcome.setOnboardingPolicyID(); + }); navigateAfterOnboarding(isSmallScreenWidth, shouldUseNarrowLayout, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, route.params?.backTo); }} pressOnEnter From 9a9600f7c5f054aea867c886f0ea2a212d935af2 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 7 Nov 2024 16:12:53 +0700 Subject: [PATCH 042/111] fix lint --- src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index 649f9cb74340..f9f456daf541 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -61,7 +61,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding if (!isVsb || !!onboardingPolicyID) { return; } - + const {adminsChatReportID, policyID} = Policy.createWorkspace(undefined, true, '', Policy.generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM); Welcome.setOnboardingAdminsChatReportID(adminsChatReportID); Welcome.setOnboardingPolicyID(policyID); From 6207c96523a447817bcb84681603c3b2fa74c14a Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Thu, 7 Nov 2024 16:14:56 +0100 Subject: [PATCH 043/111] report of fix-49974-attachment-infinite-loading changes in new branch to avoid force pushing --- assets/images/attachment-not-found.svg | 18 +++++++++++++ .../AttachmentCarousel/CarouselItem.tsx | 2 ++ .../Attachments/AttachmentView/index.tsx | 25 +++++++++++++------ .../HTMLRenderers/ImageRenderer.tsx | 6 +---- src/components/Icon/Expensicons.ts | 2 ++ src/components/ImageView/index.tsx | 12 ++++++--- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 8 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 assets/images/attachment-not-found.svg diff --git a/assets/images/attachment-not-found.svg b/assets/images/attachment-not-found.svg new file mode 100644 index 000000000000..25da973ce9cb --- /dev/null +++ b/assets/images/attachment-not-found.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx b/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx index 4de43a763231..5800e92cc4f4 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx @@ -4,6 +4,7 @@ import {View} from 'react-native'; import AttachmentView from '@components/Attachments/AttachmentView'; import type {Attachment} from '@components/Attachments/types'; import Button from '@components/Button'; +import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import SafeAreaConsumer from '@components/SafeAreaConsumer'; import Text from '@components/Text'; @@ -83,6 +84,7 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}: CarouselItemPr isHovered={isModalHovered} isFocused={isFocused} duration={item.duration} + fallbackSource={Expensicons.AttachmentNotFound} /> diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 0af1a86992e7..1281c017308d 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -10,6 +10,7 @@ import EReceipt from '@components/EReceipt'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -127,7 +128,7 @@ function AttachmentView({ const [imageError, setImageError] = useState(false); - useNetwork({onReconnect: () => setImageError(false)}); + const {isOffline} = useNetwork({onReconnect: () => setImageError(false)}); useEffect(() => { FileUtils.getFileResolution(file).then((resolution) => { @@ -226,15 +227,20 @@ function AttachmentView({ if (isFileImage) { if (imageError && (typeof fallbackSource === 'number' || typeof fallbackSource === 'function')) { return ( - + + + + {translate('attachmentView.attachmentNotFound')} + + ); } + let imageSource = imageError && fallbackSource ? (fallbackSource as string) : (source as string); if (isHighResolution) { @@ -268,6 +274,9 @@ function AttachmentView({ isImage={isFileImage} onPress={onPress} onError={() => { + if (isOffline) { + return; + } setImageError(true); }} /> diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 17fbe1656020..e08d3e9ce524 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -1,4 +1,4 @@ -import React, {memo, useState} from 'react'; +import React, {memo} from 'react'; import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import type {CustomRendererProps, TBlock} from 'react-native-render-html'; @@ -67,7 +67,6 @@ function ImageRenderer({tnode}: ImageRendererProps) { const fileType = FileUtils.getFileType(attachmentSourceAttribute); const fallbackIcon = fileType === CONST.ATTACHMENT_FILE_TYPE.FILE ? Expensicons.Document : Expensicons.GalleryNotFound; - const [hasLoadFailed, setHasLoadFailed] = useState(true); const theme = useTheme(); const thumbnailImageComponent = ( @@ -80,8 +79,6 @@ function ImageRenderer({tnode}: ImageRendererProps) { imageHeight={imageHeight} isDeleted={isDeleted} altText={alt} - onLoadFailure={() => setHasLoadFailed(true)} - onMeasure={() => setHasLoadFailed(false)} fallbackIconBackground={theme.highlightBG} fallbackIconColor={theme.border} /> @@ -113,7 +110,6 @@ function ImageRenderer({tnode}: ImageRendererProps) { shouldUseHapticsOnLongPress accessibilityRole={CONST.ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.viewAttachment')} - disabled={hasLoadFailed} > {thumbnailImageComponent} diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index fa531ce34adf..bd4bb64da050 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -8,6 +8,7 @@ import ArrowRight from '@assets/images/arrow-right.svg'; import ArrowUpLong from '@assets/images/arrow-up-long.svg'; import UpArrow from '@assets/images/arrow-up.svg'; import ArrowsUpDown from '@assets/images/arrows-updown.svg'; +import AttachmentNotFound from '@assets/images/attachment-not-found.svg'; import AdminRoomAvatar from '@assets/images/avatars/admin-room.svg'; import AnnounceRoomAvatar from '@assets/images/avatars/announce-room.svg'; import ConciergeAvatar from '@assets/images/avatars/concierge-avatar.svg'; @@ -217,6 +218,7 @@ export { ArrowsUpDown, ArrowUpLong, ArrowDownLong, + AttachmentNotFound, Wrench, BackArrow, Bank, diff --git a/src/components/ImageView/index.tsx b/src/components/ImageView/index.tsx index 266ed2eed16a..0bce2fd38432 100644 --- a/src/components/ImageView/index.tsx +++ b/src/components/ImageView/index.tsx @@ -196,8 +196,12 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV document.removeEventListener('mouseup', trackPointerPosition); }; }, [canUseTouchScreen, trackMovement, trackPointerPosition]); - - const isLocalFile = FileUtils.isLocalFile(url); + // isLocalToUserDeviceFile means the file is located on the user device, + // not loaded on the server yet (the user is offline when loading this file in fact) + let isLocalToUserDeviceFile = FileUtils.isLocalFile(url); + if (isLocalToUserDeviceFile && typeof url === 'string' && url.startsWith('/chat-attachments')) { + isLocalToUserDeviceFile = false; + } if (canUseTouchScreen) { return ( @@ -238,8 +242,8 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV /> - {isLoading && (!isOffline || isLocalFile) && } - {isLoading && !isLocalFile && } + {isLoading && (!isOffline || isLocalToUserDeviceFile) && } + {isLoading && !isLocalToUserDeviceFile && } ); } diff --git a/src/languages/en.ts b/src/languages/en.ts index 92bed5ecf1f0..822e2c55a89f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1964,6 +1964,7 @@ const translations = { afterLinkText: 'to view it.', formLabel: 'View PDF', }, + attachmentNotFound: 'Attachment not found', }, messages: { errorMessageInvalidPhone: `Please enter a valid phone number without brackets or dashes. If you're outside the US, please include your country code (e.g. ${CONST.EXAMPLE_PHONE_NUMBER}).`, diff --git a/src/languages/es.ts b/src/languages/es.ts index eefbb16870b5..6c269d42d572 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1984,6 +1984,7 @@ const translations = { afterLinkText: 'para verlo.', formLabel: 'Ver PDF', }, + attachmentNotFound: 'Archivo adjunto no encontrado', }, messages: { errorMessageInvalidPhone: `Por favor, introduce un número de teléfono válido sin paréntesis o guiones. Si reside fuera de Estados Unidos, por favor incluye el prefijo internacional (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`, From ee38879cc2d8112576db9a597e28a9fe0ee6be33 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Thu, 7 Nov 2024 11:44:06 -0500 Subject: [PATCH 044/111] set account.isLoading --- src/libs/actions/User.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index eaccbb8497ac..f3b8b1a15c28 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -555,6 +555,16 @@ function validateLogin(accountID: number, validateCode: string) { Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, isLoading: true}); const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.ACCOUNT, + value: { + isLoading: true, + }, + }, + ]; + + const finallyData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.ACCOUNT, @@ -566,7 +576,7 @@ function validateLogin(accountID: number, validateCode: string) { const parameters: ValidateLoginParams = {accountID, validateCode}; - API.write(WRITE_COMMANDS.VALIDATE_LOGIN, parameters, {optimisticData}); + API.write(WRITE_COMMANDS.VALIDATE_LOGIN, parameters, {optimisticData, finallyData}); Navigation.navigate(ROUTES.HOME); } From 8d0da0016de99bf79d2e181e8f17930b0b198984 Mon Sep 17 00:00:00 2001 From: truph01 Date: Fri, 8 Nov 2024 16:28:41 +0700 Subject: [PATCH 045/111] fix: remove unused data in json --- tests/unit/WorkspaceSettingsUtilsTest.json | 460 +-------------------- 1 file changed, 2 insertions(+), 458 deletions(-) diff --git a/tests/unit/WorkspaceSettingsUtilsTest.json b/tests/unit/WorkspaceSettingsUtilsTest.json index 08b40bfacbdc..ff83fe078adf 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.json +++ b/tests/unit/WorkspaceSettingsUtilsTest.json @@ -4,27 +4,15 @@ }, "reports": { "report_4286515777714555": { - "isOptimisticReport": false, "type": "chat", "isOwnPolicyExpenseChat": false, - "isPinned": false, - "lastActorAccountID": 0, - "lastMessageTranslationKey": "", - "lastMessageHtml": "", - "lastReadTime": "2024-11-05 11:19:18.288", - "lastVisibleActionCreated": "2024-11-05 11:19:18.288", - "oldPolicyName": "", "ownerAccountID": 0, "parentReportActionID": "8722650843049927838", "parentReportID": "6955627196303088", "policyID": "57D0F454E0BCE54B", "reportID": "4286515777714555", - "reportName": "Expense", "stateNum": 0, - "statusNum": 0, - "description": "", - "avatarUrl": "", - "avatarFileName": "" + "statusNum": 0 }, "report_6955627196303088": { "reportID": "6955627196303088", @@ -53,344 +41,7 @@ ] }, "reportActions": { - "reportActions_1699789757771388": { - "4007735288062946397": { - "reportActionID": "4007735288062946397", - "actionName": "CREATED", - "actorAccountID": 18634488, - "message": [ - { - "type": "TEXT", - "style": "strong", - "text": "You" - }, - { - "type": "TEXT", - "style": "normal", - "text": " created this report" - } - ], - "person": [ - { - "type": "TEXT", - "style": "strong", - "text": "adasdasd" - } - ], - "automatic": false, - "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", - "created": "2024-11-05 11: 10: 47.852", - "shouldShow": true, - "reportActionTimestamp": 1730805047852, - "sequenceNumber": 0, - "lastModified": "2024-11-05 11: 10: 47.852" - }, - "7978085421707288417": { - "reportActionID": "7978085421707288417", - "reportID": "1699789757771388", - "actionName": "REPORTPREVIEW", - "originalMessage": {}, - "message": [ - { - "deleted": "", - "html": "Adasdasd's Workspace owes ₫579", - "isDeletedParentAction": false, - "isEdited": false, - "text": "Adasdasd's Workspace owes ₫579", - "type": "COMMENT", - "whisperedTo": [] - } - ], - "created": "2024-11-05 11: 19: 18.710", - "accountID": 18634488, - "actorAccountID": 18634488, - "childReportID": "6955627196303088", - "childMoneyRequestCount": 2, - "childLastMoneyRequestComment": "", - "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", - "childCommenterCount": 0, - "childLastActorAccountID": 18634488, - "childLastVisibleActionCreated": "", - "childOldestFourAccountIDs": "", - "childReportNotificationPreference": "hidden", - "childType": "expense", - "childVisibleActionCount": 0, - "lastModified": "2024-11-05 11: 19: 18.710", - "person": [ - { - "style": "strong", - "text": "adasdasd", - "type": "TEXT" - } - ], - "shouldShow": true, - "automatic": false, - "childManagerAccountID": 18634488, - "childOwnerAccountID": 18634488, - "childReportName": "Expense Report #6955627196303088", - "childStateNum": 1, - "childStatusNum": 1, - "reportActionTimestamp": 1730805558710, - "timestamp": 1730805558, - "whisperedToAccountIDs": [] - } - }, - "reportActions_4148694821839494": { - "2964625714811661556": { - "reportActionID": "2964625714811661556", - "actionName": "CREATED", - "actorAccountID": 18634488, - "message": [ - { - "type": "TEXT", - "style": "strong", - "text": "_FAKE_" - }, - { - "type": "TEXT", - "style": "normal", - "text": " created this report" - } - ], - "person": [ - { - "type": "TEXT", - "style": "strong", - "text": "adasdasd" - } - ], - "automatic": false, - "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", - "created": "2024-11-05 11: 10: 47.077", - "shouldShow": true - }, - "5971844472086652538": { - "actionName": "ADDCOMMENT", - "actorAccountID": 10288574, - "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/26271df52c9c9db57fa0221feaef04d18a045f2b_128.jpeg", - "created": "2024-11-05 11: 11: 47.410", - "lastModified": "2024-11-05 11: 11: 47.410", - "message": [ - { - "html": "

Let's get you set up on Expensify

Hi there 👋 , I'm your dedicated setup specialist. I look forward to helping you explore and configure Expensify. A few important things to mention:
  • Your workspace has a ton of custom settings, just select your workspace settings to set it up.
  • You've got more functionality to enable, like custom categories, tags, etc. Just ask me how.
Chat with me here in your #admins room or just reply to this message. You can also schedule a call if you have more in-depth questions. Talk soon!

\"Setup", - "text": "Let's get you set up on Expensify\nHi there 👋 , I'm your dedicated setup specialist. I look forward to helping you explore and configure Expensify. A few important things to mention: \nYour workspace has a ton of custom settings, just select your workspace settings to set it up.You've got more functionality to enable, like custom categories, tags, etc. Just ask me how. Chat with me here in your #admins room or just reply to this message. You can also schedule a call if you have more in-depth questions. Talk soon!\n\n[Attachment]", - "type": "COMMENT", - "whisperedTo": [] - } - ], - "originalMessage": {}, - "person": [ - { - "style": "strong", - "text": "Setup Specialist - BreAnna Sumpter Mon-Friday GMT & EST", - "type": "TEXT" - } - ], - "reportActionID": "5971844472086652538", - "shouldShow": true - } - }, - "reportActions_4625283659773773": { - "7132923952865070123": { - "actionName": "ADDCOMMENT", - "actorAccountID": 8392101, - "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png", - "created": "2024-11-05 11: 10: 38.956", - "lastModified": "2024-11-05 11: 10: 38.956", - "message": [ - { - "type": "COMMENT", - "html": "

Welcome to Expensify

👋 Hey there, I'm Concierge! If you have any questions about Expensify, you can always chat with me here 24-7 for fast and reliable support. I'm happy to help!", - "text": "Welcome to Expensify\n👋 Hey there, I'm Concierge! If you have any questions about Expensify, you can always chat with me here 24-7 for fast and reliable support. I'm happy to help!", - "isEdited": false, - "whisperedTo": [], - "isDeletedParentAction": false, - "deleted": "" - } - ], - "originalMessage": {}, - "person": [ - { - "type": "TEXT", - "style": "strong", - "text": "Expensify Concierge" - } - ], - "reportActionID": "7132923952865070123", - "shouldShow": true, - "timestamp": 1730805038, - "reportActionTimestamp": 1730805038956, - "automatic": false, - "whisperedToAccountIDs": [] - }, - "5686837203726341682": { - "reportActionID": "5686837203726341682", - "actionName": "CREATED", - "created": "2024-11-05 11: 10: 38.688", - "reportActionTimestamp": 1730805038688, - "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", - "message": [ - { - "type": "TEXT", - "style": "strong", - "text": "__fake__" - }, - { - "type": "TEXT", - "style": "normal", - "text": " created this report" - } - ], - "person": [ - { - "type": "TEXT", - "style": "strong", - "text": "__fake__" - } - ], - "automatic": false, - "sequenceNumber": 0, - "shouldShow": true, - "lastModified": "2024-11-05 11: 10: 38.688" - }, - "536505040772198026": { - "reportActionID": "536505040772198026", - "actionName": "ADDCOMMENT", - "actorAccountID": 8392101, - "person": [ - { - "style": "strong", - "text": "Expensify Concierge", - "type": "TEXT" - } - ], - "automatic": false, - "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png", - "created": "2024-11-05 11: 10: 43.943", - "message": [ - { - "html": "Let’s get you set up 🔧", - "text": "Let’s get you set up 🔧", - "type": "COMMENT", - "whisperedTo": [] - } - ], - "originalMessage": {}, - "isFirstItem": false, - "isAttachmentWithText": false, - "shouldShow": true, - "isOptimisticAction": true, - "lastModified": "2024-11-05 11: 10: 43.943" - }, - "5286007009323266706": { - "reportActionID": "5286007009323266706", - "actionName": "ADDCOMMENT", - "actorAccountID": 8392101, - "person": [ - { - "style": "strong", - "text": "Expensify Concierge", - "type": "TEXT" - } - ], - "automatic": false, - "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png", - "created": "2024-11-05 11: 10: 43.944", - "message": [ - { - "html": "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", - "text": "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", - "type": "COMMENT", - "whisperedTo": [] - } - ], - "originalMessage": {}, - "isFirstItem": false, - "isAttachmentWithText": false, - "shouldShow": true, - "isOptimisticAction": true, - "lastModified": "2024-11-05 11: 10: 43.944" - }, - "4422060638727390382": { - "actionName": "ADDCOMMENT", - "actorAccountID": 8392101, - "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png", - "created": "2024-11-05 11: 11: 47.086", - "lastModified": "2024-11-05 11: 11: 47.086", - "message": [ - { - "html": "Let's get your company set up! Setup Specialist - BreAnna, your dedicated specialist, is online now and can answer your initial questions or provide a demo.

💬 CHAT WITH YOUR SETUP SPECIALIST", - "text": "Let's get your company set up! Setup Specialist - BreAnna, your dedicated specialist, is online now and can answer your initial questions or provide a demo.\n\n💬 CHAT WITH YOUR SETUP SPECIALIST", - "type": "COMMENT", - "whisperedTo": [] - } - ], - "originalMessage": {}, - "person": [ - { - "style": "strong", - "text": "Expensify Concierge", - "type": "TEXT" - } - ], - "reportActionID": "4422060638727390382", - "shouldShow": true - }, - "2824688328360312239": { - "actionName": "ADDCOMMENT", - "actorAccountID": 8392101, - "avatar": "https: //d1wpcgnaa73g0y.cloudfront.net/894b50e60056c966d12216005fbcacec8ce5a2c0.png", - "created": "2024-11-05 11: 19: 18.703", - "lastModified": "2024-11-05 11: 19: 18.703", - "message": [ - { - "html": "

You’ve started a free trial!


Welcome to your free 7-day trial of Expensify 🎉 Use it to continue exploring your workspace's benefits, including tracking expenses, reimbursing employees, managing company spend, and more.

If you have any questions, chat with your dedicated Setup Specialist in #admins. Enjoy!", - "text": "You’ve started a free trial!\n\nWelcome to your free 7-day trial of Expensify 🎉 Use it to continue exploring your workspace's benefits, including tracking expenses, reimbursing employees, managing company spend, and more.\n\nIf you have any questions, chat with your dedicated Setup Specialist in #admins. Enjoy!", - "type": "COMMENT", - "whisperedTo": [] - } - ], - "originalMessage": {}, - "person": [ - { - "style": "strong", - "text": "Expensify Concierge", - "type": "TEXT" - } - ], - "reportActionID": "2824688328360312239", - "shouldShow": true - } - }, "reportActions_6955627196303088": { - "1493209744740418100": { - "reportActionID": "1493209744740418100", - "actionName": "CREATED", - "actorAccountID": 18634488, - "message": [ - { - "type": "TEXT", - "style": "strong", - "text": "ajsdjajdjadjajsjajdsj123@gmail.com" - }, - { - "type": "TEXT", - "style": "normal", - "text": " created this report" - } - ], - "person": [ - { - "type": "TEXT", - "style": "strong", - "text": "adasdasd" - } - ], - "automatic": false, - "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", - "created": "2024-11-05 11: 19: 18.285", - "shouldShow": true - }, "8722650843049927838": { "actionName": "IOU", "actorAccountID": 18634488, @@ -428,114 +79,7 @@ "childReportID": "4286515777714555", "lastModified": "2024-11-05 11: 19: 18.706", "childReportNotificationPreference": "hidden", - "childType": "chat", - "reportActionTimestamp": 1730805558706, - "sequenceNumber": 1, - "timestamp": 1730805558, - "whisperedToAccountIDs": [] - }, - "1783566081350093529": { - "actionName": "IOU", - "actorAccountID": 18634488, - "automatic": false, - "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", - "isAttachmentOnly": false, - "originalMessage": { - "amount": 45600, - "comment": "", - "currency": "VND", - "IOUTransactionID": "3690687111940510713", - "IOUReportID": "6955627196303088" - }, - "message": [ - { - "deleted": "", - "html": "₫456 expense", - "isDeletedParentAction": false, - "isEdited": false, - "text": "₫456 expense", - "type": "COMMENT", - "whisperedTo": [] - } - ], - "person": [ - { - "style": "strong", - "text": "adasdasd", - "type": "TEXT" - } - ], - "reportActionID": "1783566081350093529", - "shouldShow": true, - "created": "2024-11-05 11: 20: 22.065", - "childReportID": "7900715127836904", - "lastModified": "2024-11-05 11: 20: 22.065", - "childReportNotificationPreference": "hidden", - "childType": "chat", - "reportActionTimestamp": 1730805622065, - "sequenceNumber": 2, - "timestamp": 1730805622, - "whisperedToAccountIDs": [] - } - }, - "reportActions_4286515777714555": { - "1995312838979534584": { - "reportActionID": "1995312838979534584", - "actionName": "CREATED", - "actorAccountID": 18634488, - "message": [ - { - "type": "TEXT", - "style": "strong", - "text": "ajsdjajdjadjajsjajdsj123@gmail.com" - }, - { - "type": "TEXT", - "style": "normal", - "text": " created this report" - } - ], - "person": [ - { - "type": "TEXT", - "style": "strong", - "text": "adasdasd" - } - ], - "automatic": false, - "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", - "created": "2024-11-05 11: 19: 18.288", - "shouldShow": true - } - }, - "reportActions_7900715127836904": { - "3536855248086336861": { - "reportActionID": "3536855248086336861", - "actionName": "CREATED", - "actorAccountID": 18634488, - "message": [ - { - "type": "TEXT", - "style": "strong", - "text": "ajsdjajdjadjajsjajdsj123@gmail.com" - }, - { - "type": "TEXT", - "style": "normal", - "text": " created this report" - } - ], - "person": [ - { - "type": "TEXT", - "style": "strong", - "text": "adasdasd" - } - ], - "automatic": false, - "avatar": "https: //d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_1.png", - "created": "2024-11-05 11: 20: 21.589", - "shouldShow": true + "childType": "chat" } } } From d42e264564a476786262ac6e29957d50912d5821 Mon Sep 17 00:00:00 2001 From: truph01 Date: Fri, 8 Nov 2024 16:32:21 +0700 Subject: [PATCH 046/111] fix: add 2nd test case --- tests/unit/WorkspaceSettingsUtilsTest.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts index 2e7d82263e41..239fd8e6ddfa 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.ts +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -43,5 +43,28 @@ describe('WorkspacesSettingsUtils', () => { // Then the result should be 'error' expect(result).toBe('error'); }); + + it('Should return "undefined"', async () => { + // Given mock data for reports, transaction violations, sessions, and report actions + const report = Object.values(mockData.reports)?.at(0); + const transactionViolations = mockData.transactionViolations; + const reports = mockData.reports; + const session = mockData.session; + const reportActions = mockData.reportActions; + + await Onyx.multiSet({ + ...(reports as ReportCollectionDataSet), + ...(reportActions as OnyxCollection), + session, + }); + + await waitForBatchedUpdates(); + + // When calling getBrickRoadForPolicy with a report and report actions + const result = getBrickRoadForPolicy(report as Report, reportActions as OnyxCollection); + + // Then the result should be 'error' + expect(result).toBe(undefined); + }); }); }); From ef77bb997ca8f232423d0627c98f361a988d94e9 Mon Sep 17 00:00:00 2001 From: truph01 Date: Fri, 8 Nov 2024 16:37:32 +0700 Subject: [PATCH 047/111] fix: lint --- tests/unit/WorkspaceSettingsUtilsTest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts index 239fd8e6ddfa..934c003ccda6 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.ts +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -47,7 +47,6 @@ describe('WorkspacesSettingsUtils', () => { it('Should return "undefined"', async () => { // Given mock data for reports, transaction violations, sessions, and report actions const report = Object.values(mockData.reports)?.at(0); - const transactionViolations = mockData.transactionViolations; const reports = mockData.reports; const session = mockData.session; const reportActions = mockData.reportActions; From bd9c7acd3d217373c98ee085e6a54177872e7a91 Mon Sep 17 00:00:00 2001 From: truph01 Date: Fri, 8 Nov 2024 23:07:23 +0700 Subject: [PATCH 048/111] fix: update comment --- tests/unit/WorkspaceSettingsUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts index 934c003ccda6..f804356d3706 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.ts +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -62,7 +62,7 @@ describe('WorkspacesSettingsUtils', () => { // When calling getBrickRoadForPolicy with a report and report actions const result = getBrickRoadForPolicy(report as Report, reportActions as OnyxCollection); - // Then the result should be 'error' + // Then the result should be 'undefined' expect(result).toBe(undefined); }); }); From f36e9f6e772f57c22e6e7e2ce88a51a71ddb1894 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Fri, 8 Nov 2024 18:52:35 +0100 Subject: [PATCH 049/111] Update tests/unit/DebugUtilsTest.ts Co-authored-by: Carlos Alvarez --- tests/unit/DebugUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index 3f9f28160b0a..abeaff971194 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -1510,7 +1510,7 @@ describe('DebugUtils', () => { ownerAccountID: 1234, policyID: '1', }, - [`${ONYXKEYS.COLLECTION.TRANSACTION}0` as const]: { + [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { transactionID: '1', amount: 10, modifiedAmount: 10, From 3e07767a46977f3d83ef164b4b0d30724ec8334a Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Nov 2024 20:00:18 +0530 Subject: [PATCH 050/111] fixes offline delete --- src/libs/actions/Policy/Policy.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index d87f0321bab0..905226428da0 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -198,6 +198,12 @@ Onyx.connect({ callback: (val) => (allRecentlyUsedCurrencies = val ?? []), }); +let activePolicyID: OnyxEntry; +Onyx.connect({ + key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, + callback: (value) => (activePolicyID = value), +}); + /** * Stores in Onyx the policy ID of the last workspace that was accessed by the user */ @@ -1620,6 +1626,8 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName const optimisticCategoriesData = buildOptimisticPolicyCategories(policyID, CONST.POLICY.DEFAULT_CATEGORIES); + const shouldSetCreatedWorkspaceAsActivePolicy = !!activePolicyID && allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`]?.type === CONST.POLICY.TYPE.PERSONAL; + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.SET, @@ -1705,8 +1713,21 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName key: `${ONYXKEYS.COLLECTION.REPORT_DRAFT}${expenseChatReportID}`, value: null, }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_DRAFT}${adminsChatReportID}`, + value: null, + }, ]; + if (shouldSetCreatedWorkspaceAsActivePolicy) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, + value: policyID, + }); + } + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -1795,6 +1816,14 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName }, ]; + if (shouldSetCreatedWorkspaceAsActivePolicy) { + failureData.push({ + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, + value: activePolicyID ?? '', + }); + } + if (optimisticCategoriesData.optimisticData) { optimisticData.push(...optimisticCategoriesData.optimisticData); } From 63f9e48737eb140a4723cf4bdde140ed7e86db9f Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Nov 2024 20:13:47 +0530 Subject: [PATCH 051/111] fix es lint --- src/libs/actions/Policy/Policy.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 905226428da0..14028d764171 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -198,10 +198,10 @@ Onyx.connect({ callback: (val) => (allRecentlyUsedCurrencies = val ?? []), }); -let activePolicyID: OnyxEntry; +let nvpActivePolicyID: OnyxEntry; Onyx.connect({ key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, - callback: (value) => (activePolicyID = value), + callback: (value) => (nvpActivePolicyID = value), }); /** @@ -1626,7 +1626,7 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName const optimisticCategoriesData = buildOptimisticPolicyCategories(policyID, CONST.POLICY.DEFAULT_CATEGORIES); - const shouldSetCreatedWorkspaceAsActivePolicy = !!activePolicyID && allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`]?.type === CONST.POLICY.TYPE.PERSONAL; + const shouldSetCreatedWorkspaceAsActivePolicy = !!nvpActivePolicyID && allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${nvpActivePolicyID}`]?.type === CONST.POLICY.TYPE.PERSONAL; const optimisticData: OnyxUpdate[] = [ { @@ -1820,7 +1820,7 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName failureData.push({ onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, - value: activePolicyID ?? '', + value: nvpActivePolicyID ?? '', }); } From d979c4a39d0cf6934bda89b8e920d2e07b6a7d55 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 11 Nov 2024 09:36:36 +0700 Subject: [PATCH 052/111] add comment --- src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index f9f456daf541..6e3e4c62aeda 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -163,6 +163,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding onboardingCompanySize, userReportedIntegration, ); + // Avoid creating new WS because onboardingPolicyID is cleared before unmounting InteractionManager.runAfterInteractions(() => { Welcome.setOnboardingAdminsChatReportID(); Welcome.setOnboardingPolicyID(); From 82ebccfc56fea0ddb804043c886c983d98ff4e24 Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:09:16 +0700 Subject: [PATCH 053/111] fix display of undefined on card name after page refresh --- ...orkspaceCompanyCardsSettingsFeedNamePage.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx index 3a922728b3ce..0b09e06319ee 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback} from 'react'; +import React, {useCallback, useEffect} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -30,8 +30,9 @@ type WorkspaceCompanyCardsSettingsFeedNamePageProps = StackScreenProps { + if (!feedName) { + return; + } + + navigation.setParams({currentFeedName: feedName}); + }, [feedName, navigation]); const validate = useCallback( (values: FormOnyxValues) => { From 487bc09b2bdbc20d23d54a11aa2bc2614238dfda Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:30:23 +0700 Subject: [PATCH 054/111] Add type parameter for company card setting feed name --- src/libs/Navigation/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index ba859efff944..33bace5b7ba3 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -854,6 +854,7 @@ type SettingsNavigatorParamList = { }; [SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS_FEED_NAME]: { policyID: string; + currentFeedName?: string; }; [SCREENS.WORKSPACE.EXPENSIFY_CARD_DETAILS]: { policyID: string; From ae40ea5843e18a05a42222fe78a29aacf06c5b46 Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:00:58 +0700 Subject: [PATCH 055/111] pass selected feed name when navigating to Settings Page --- src/ROUTES.ts | 4 ++-- src/libs/Navigation/types.ts | 2 +- .../WorkspaceCompanyCardsSettingsFeedNamePage.tsx | 15 +++------------ .../WorkspaceCompanyCardsSettingsPage.tsx | 2 +- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index cd94035e0fff..336c4b0cfa68 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1238,8 +1238,8 @@ const ROUTES = { getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards/settings` as const, }, WORKSPACE_COMPANY_CARDS_SETTINGS_FEED_NAME: { - route: 'settings/workspaces/:policyID/company-cards/settings/feed-name', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards/settings/feed-name` as const, + route: 'settings/workspaces/:policyID/company-cards/settings/feed-name/:feedName', + getRoute: (policyID: string, feedName: string) => `settings/workspaces/${policyID}/company-cards/settings/feed-name/${encodeURIComponent(feedName)}` as const, }, WORKSPACE_RULES: { route: 'settings/workspaces/:policyID/rules', diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 33bace5b7ba3..1630954c666a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -854,7 +854,7 @@ type SettingsNavigatorParamList = { }; [SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS_FEED_NAME]: { policyID: string; - currentFeedName?: string; + feedName?: string; }; [SCREENS.WORKSPACE.EXPENSIFY_CARD_DETAILS]: { policyID: string; diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx index 0b09e06319ee..33ee035f133c 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback, useEffect} from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -30,9 +30,8 @@ type WorkspaceCompanyCardsSettingsFeedNamePageProps = StackScreenProps { - if (!feedName) { - return; - } - - navigation.setParams({currentFeedName: feedName}); - }, [feedName, navigation]); + : decodeURIComponent(selectedFeedName ?? ''); const validate = useCallback( (values: FormOnyxValues) => { diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsPage.tsx index a1dfd7dea3eb..3f118551b602 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsPage.tsx @@ -45,7 +45,7 @@ function WorkspaceCompanyCardsSettingsPage({ const isPersonal = liabilityType === CONST.COMPANY_CARDS.DELETE_TRANSACTIONS.ALLOW; const navigateToChangeFeedName = () => { - Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS_FEED_NAME.getRoute(policyID)); + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS_FEED_NAME.getRoute(policyID, feedName)); }; const deleteCompanyCardFeed = () => { From dd2056dfca46ca677a95b35cbe3c537109b381e0 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 11 Nov 2024 13:30:19 +0100 Subject: [PATCH 056/111] fix bug with default exporter --- .../accounting/qbd/export/QuickbooksDesktopExportPage.tsx | 3 ++- .../QuickbooksDesktopPreferredExporterConfigurationPage.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx index 8555be0d3d83..d6fc05526c1d 100644 --- a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx +++ b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx @@ -31,7 +31,8 @@ function QuickbooksDesktopExportPage({policy}: WithPolicyConnectionsProps) { { description: translate('workspace.accounting.preferredExporter'), onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_PREFERRED_EXPORTER.getRoute(policyID)), - title: qbdConfig?.export?.exporter ?? policyOwner, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + title: qbdConfig?.export?.exporter || policyOwner, subscribedSettings: [CONST.QUICKBOOKS_DESKTOP_CONFIG.EXPORTER], }, { diff --git a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopPreferredExporterConfigurationPage.tsx b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopPreferredExporterConfigurationPage.tsx index eef48ee04dcf..2a227d7df344 100644 --- a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopPreferredExporterConfigurationPage.tsx +++ b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopPreferredExporterConfigurationPage.tsx @@ -45,7 +45,8 @@ function QuickbooksDesktopPreferredExporterConfigurationPage({policy}: WithPolic value: exporter.email, text: exporter.email, keyForList: exporter.email, - isSelected: (currentExporter ?? policy?.owner) === exporter.email, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + isSelected: (currentExporter || policy?.owner) === exporter.email, }); return options; }, []), From 46e89b0bae54b3108dcdaaa5a896593cc631c36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 11 Nov 2024 15:07:10 +0100 Subject: [PATCH 057/111] cleanup: moved report field options to their own file --- src/libs/OptionsListUtils.ts | 108 ------------------------ src/libs/ReportFieldOptionsListUtils.ts | 90 ++++++++++++++++++++ src/pages/EditReportFieldDropdown.tsx | 15 ++-- 3 files changed, 96 insertions(+), 117 deletions(-) create mode 100644 src/libs/ReportFieldOptionsListUtils.ts diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index f6decd6fb2f4..07fc9d26a8ef 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -171,9 +171,6 @@ type GetOptionsConfig = { taxRates?: TaxRatesWithDefault; policy?: OnyxEntry; transaction?: OnyxEntry; - includePolicyReportFieldOptions?: boolean; - policyReportFieldOptions?: string[]; - recentlyUsedPolicyReportFieldOptions?: string[]; transactionViolations?: OnyxCollection; includeInvoiceRooms?: boolean; includeDomainEmail?: boolean; @@ -217,7 +214,6 @@ type Options = { categoryOptions: CategoryTreeSection[]; tagOptions: CategorySection[]; taxRatesOptions: CategorySection[]; - policyReportFieldOptions?: CategorySection[] | null; }; type PreviewConfig = {showChatPreviewLine?: boolean; forcePolicyNamePreview?: boolean; showPersonalDetails?: boolean}; @@ -1309,81 +1305,6 @@ function hasEnabledTags(policyTagList: Array ({ - text: name, - keyForList: name, - searchText: name, - tooltipText: name, - isDisabled: false, - })); -} - -/** - * Build the section list for report field options - */ -function getReportFieldOptionsSection(options: string[], recentlyUsedOptions: string[], selectedOptions: Array>, searchInputValue: string) { - const reportFieldOptionsSections = []; - const selectedOptionKeys = selectedOptions.map(({text, keyForList, name}) => text ?? keyForList ?? name ?? '').filter((o) => !!o); - let indexOffset = 0; - - if (searchInputValue) { - const searchOptions = options.filter((option) => option.toLowerCase().includes(searchInputValue.toLowerCase())); - - reportFieldOptionsSections.push({ - // "Search" section - title: '', - shouldShow: true, - indexOffset, - data: getReportFieldOptions(searchOptions), - }); - - return reportFieldOptionsSections; - } - - const filteredRecentlyUsedOptions = recentlyUsedOptions.filter((recentlyUsedOption) => !selectedOptionKeys.includes(recentlyUsedOption)); - const filteredOptions = options.filter((option) => !selectedOptionKeys.includes(option)); - - if (selectedOptionKeys.length) { - reportFieldOptionsSections.push({ - // "Selected" section - title: '', - shouldShow: true, - indexOffset, - data: getReportFieldOptions(selectedOptionKeys), - }); - - indexOffset += selectedOptionKeys.length; - } - - if (filteredRecentlyUsedOptions.length > 0) { - reportFieldOptionsSections.push({ - // "Recent" section - title: Localize.translateLocal('common.recent'), - shouldShow: true, - indexOffset, - data: getReportFieldOptions(filteredRecentlyUsedOptions), - }); - - indexOffset += filteredRecentlyUsedOptions.length; - } - - reportFieldOptionsSections.push({ - // "All" section when items amount more than the threshold - title: Localize.translateLocal('common.all'), - shouldShow: true, - indexOffset, - data: getReportFieldOptions(filteredOptions), - }); - - return reportFieldOptionsSections; -} - /** * Sorts tax rates alphabetically by name. */ @@ -1727,9 +1648,6 @@ function getOptions( policy, transaction, includeSelfDM = false, - includePolicyReportFieldOptions = false, - policyReportFieldOptions = [], - recentlyUsedPolicyReportFieldOptions = [], includeInvoiceRooms = false, includeDomainEmail = false, action, @@ -1779,20 +1697,6 @@ function getOptions( }; } - if (includePolicyReportFieldOptions) { - const transformedPolicyReportFieldOptions = getReportFieldOptionsSection(policyReportFieldOptions, recentlyUsedPolicyReportFieldOptions, selectedOptions, searchInputValue); - return { - recentReports: [], - personalDetails: [], - userToInvite: null, - currentUserOption: null, - categoryOptions: [], - tagOptions: [], - taxRatesOptions: [], - policyReportFieldOptions: transformedPolicyReportFieldOptions, - }; - } - const parsedPhoneNumber = PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(searchInputValue))); const searchValue = parsedPhoneNumber.possible ? parsedPhoneNumber.number?.e164 ?? '' : searchInputValue.toLowerCase(); const topmostReportId = Navigation.getTopmostReportId() ?? '-1'; @@ -2145,9 +2049,6 @@ type FilteredOptionsParams = { taxRates?: TaxRatesWithDefault; maxRecentReportsToShow?: number; includeSelfDM?: boolean; - includePolicyReportFieldOptions?: boolean; - policyReportFieldOptions?: string[]; - recentlyUsedPolicyReportFieldOptions?: string[]; includeInvoiceRooms?: boolean; action?: IOUAction; sortByReportTypeInSearch?: boolean; @@ -2186,9 +2087,6 @@ function getFilteredOptions(params: FilteredOptionsParamsWithDefaultSearchValue maxRecentReportsToShow = CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, taxRates = {} as TaxRatesWithDefault, includeSelfDM = false, - includePolicyReportFieldOptions = false, - policyReportFieldOptions = [], - recentlyUsedPolicyReportFieldOptions = [], includeInvoiceRooms = false, action, sortByReportTypeInSearch = false, @@ -2215,9 +2113,6 @@ function getFilteredOptions(params: FilteredOptionsParamsWithDefaultSearchValue includeTaxRates, taxRates, includeSelfDM, - includePolicyReportFieldOptions, - policyReportFieldOptions, - recentlyUsedPolicyReportFieldOptions, includeInvoiceRooms, action, sortByReportTypeInSearch, @@ -2260,9 +2155,6 @@ function getAttendeeOptions( maxRecentReportsToShow: 0, taxRates: {} as TaxRatesWithDefault, includeSelfDM: false, - includePolicyReportFieldOptions: false, - policyReportFieldOptions: [], - recentlyUsedPolicyReportFieldOptions: [], includeInvoiceRooms, action, sortByReportTypeInSearch, diff --git a/src/libs/ReportFieldOptionsListUtils.ts b/src/libs/ReportFieldOptionsListUtils.ts new file mode 100644 index 000000000000..b3b302fdaaea --- /dev/null +++ b/src/libs/ReportFieldOptionsListUtils.ts @@ -0,0 +1,90 @@ +import * as Localize from './Localize'; +import type {Option} from './OptionsListUtils'; +import type * as ReportUtils from './ReportUtils'; + +/** + * Transforms the provided report field options into option objects. + * + * @param reportFieldOptions - an initial report field options array + */ +function getReportFieldOptions(reportFieldOptions: string[]): Option[] { + return reportFieldOptions.map((name) => ({ + text: name, + keyForList: name, + searchText: name, + tooltipText: name, + isDisabled: false, + })); +} + +/** + * Build the section list for report field options + */ +function getReportFieldOptionsSection({ + options, + recentlyUsedOptions, + selectedOptions, + searchValue, +}: { + options: string[]; + recentlyUsedOptions: string[]; + selectedOptions: Array>; + searchValue: string; +}) { + const reportFieldOptionsSections = []; + const selectedOptionKeys = selectedOptions.map(({text, keyForList, name}) => text ?? keyForList ?? name ?? '').filter((o) => !!o); + let indexOffset = 0; + + if (searchValue) { + const searchOptions = options.filter((option) => option.toLowerCase().includes(searchValue.toLowerCase())); + + reportFieldOptionsSections.push({ + // "Search" section + title: '', + shouldShow: true, + indexOffset, + data: getReportFieldOptions(searchOptions), + }); + + return reportFieldOptionsSections; + } + + const filteredRecentlyUsedOptions = recentlyUsedOptions.filter((recentlyUsedOption) => !selectedOptionKeys.includes(recentlyUsedOption)); + const filteredOptions = options.filter((option) => !selectedOptionKeys.includes(option)); + + if (selectedOptionKeys.length) { + reportFieldOptionsSections.push({ + // "Selected" section + title: '', + shouldShow: true, + indexOffset, + data: getReportFieldOptions(selectedOptionKeys), + }); + + indexOffset += selectedOptionKeys.length; + } + + if (filteredRecentlyUsedOptions.length > 0) { + reportFieldOptionsSections.push({ + // "Recent" section + title: Localize.translateLocal('common.recent'), + shouldShow: true, + indexOffset, + data: getReportFieldOptions(filteredRecentlyUsedOptions), + }); + + indexOffset += filteredRecentlyUsedOptions.length; + } + + reportFieldOptionsSections.push({ + // "All" section when items amount more than the threshold + title: Localize.translateLocal('common.all'), + shouldShow: true, + indexOffset, + data: getReportFieldOptions(filteredOptions), + }); + + return reportFieldOptionsSections; +} + +export {getReportFieldOptionsSection, getReportFieldOptions}; diff --git a/src/pages/EditReportFieldDropdown.tsx b/src/pages/EditReportFieldDropdown.tsx index a6bccdf3fa12..e8364d7d1f37 100644 --- a/src/pages/EditReportFieldDropdown.tsx +++ b/src/pages/EditReportFieldDropdown.tsx @@ -10,6 +10,7 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import localeCompare from '@libs/LocaleCompare'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportFieldOptionsListUtils from '@libs/ReportFieldOptionsListUtils'; import ONYXKEYS from '@src/ONYXKEYS'; type EditReportFieldDropdownPageComponentProps = { @@ -58,7 +59,7 @@ function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptio const [sections, headerMessage] = useMemo(() => { const validFieldOptions = fieldOptions?.filter((option) => !!option)?.sort(localeCompare); - const {policyReportFieldOptions} = OptionsListUtils.getFilteredOptions({ + const policyReportFieldOptions = ReportFieldOptionsListUtils.getReportFieldOptionsSection({ searchValue: debouncedSearchValue, selectedOptions: [ { @@ -67,21 +68,17 @@ function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptio text: fieldValue, }, ], - - includeP2P: false, - canInviteUser: false, - includePolicyReportFieldOptions: true, - policyReportFieldOptions: validFieldOptions, - recentlyUsedPolicyReportFieldOptions: recentlyUsedOptions, + options: validFieldOptions, + recentlyUsedOptions, }); - const policyReportFieldData = policyReportFieldOptions?.[0]?.data ?? []; + const policyReportFieldData = policyReportFieldOptions.at(0)?.data ?? []; const header = OptionsListUtils.getHeaderMessageForNonUserList(policyReportFieldData.length > 0, debouncedSearchValue); return [policyReportFieldOptions, header]; }, [recentlyUsedOptions, debouncedSearchValue, fieldValue, fieldOptions]); - const selectedOptionKey = useMemo(() => (sections?.[0]?.data ?? []).filter((option) => option.searchText === fieldValue)?.at(0)?.keyForList, [sections, fieldValue]); + const selectedOptionKey = useMemo(() => (sections.at(0)?.data ?? []).filter((option) => option.searchText === fieldValue)?.at(0)?.keyForList, [sections, fieldValue]); return ( Date: Mon, 11 Nov 2024 15:24:18 -0300 Subject: [PATCH 058/111] Move code to variable --- src/libs/TransactionUtils/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 00d296b6642e..3506d33ce665 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -833,8 +833,9 @@ function hasNoticeTypeViolation(transactionID: string, transactionViolations: On * Checks if any violations for the provided transaction are of type 'warning' */ function hasWarningTypeViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean | null): boolean { + const violations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]; const warningTypeViolations = - transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter( + violations?.filter( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === null || showInReview === (violation.showInReview ?? false)), ) ?? []; From 40d9f8d7a9c914e3fa684b684b5dae623e3dd562 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Mon, 11 Nov 2024 17:05:53 -0300 Subject: [PATCH 059/111] Ensure notices are correctly shown on report preview too --- src/components/ReportActionItem/ReportPreview.tsx | 1 + src/libs/ReportUtils.ts | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index b26cbf3a4d55..69c143051b37 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -157,6 +157,7 @@ function ReportPreview({ (hasMissingSmartscanFields && !iouSettled) || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing ReportUtils.hasViolations(iouReportID, transactionViolations, true) || + ReportUtils.hasNoticeTypeViolations(iouReportID, transactionViolations, true) || ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations, true) || (ReportUtils.isReportOwner(iouReport) && ReportUtils.hasReportViolations(iouReportID)) || ReportUtils.hasActionsWithErrors(iouReportID); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 3289b9fb3f5c..bb43f8031ef7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6357,6 +6357,14 @@ function hasWarningTypeViolations(reportID: string, transactionViolations: OnyxC return transactions.some((transaction) => TransactionUtils.hasWarningTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); } +/** + * Checks to see if a report contains a violation of type `notice` + */ +function hasNoticeTypeViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean): boolean { + const transactions = reportsTransactions[reportID] ?? []; + return transactions.some((transaction) => TransactionUtils.hasNoticeTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); +} + function hasReportViolations(reportID: string) { const reportViolations = allReportsViolations?.[`${ONYXKEYS.COLLECTION.REPORT_VIOLATIONS}${reportID}`]; return Object.values(reportViolations ?? {}).some((violations) => !isEmptyObject(violations)); @@ -8554,6 +8562,7 @@ export { hasUpdatedTotal, hasViolations, hasWarningTypeViolations, + hasNoticeTypeViolations, isActionCreator, isAdminRoom, isAdminsOnlyPostingRoom, From 0763e4b2a8236d17b6908a726fb64cebf963997b Mon Sep 17 00:00:00 2001 From: NJ-2020 Date: Mon, 11 Nov 2024 20:29:12 -0800 Subject: [PATCH 060/111] fix selecting non-existing user in offline mode --- src/libs/actions/Report.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index a0441e729582..c63d6706ccf4 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1001,7 +1001,10 @@ function openReport( // eslint-disable-next-line rulesdir/no-multiple-api-calls API.paginate(CONST.API_REQUEST_TYPE.WRITE, WRITE_COMMANDS.OPEN_REPORT, parameters, {optimisticData, successData, failureData}, paginationConfig, { checkAndFixConflictingRequest: (persistedRequests) => - resolveDuplicationConflictAction(persistedRequests, (request) => request.command === WRITE_COMMANDS.OPEN_REPORT && request.data?.reportID === reportID), + resolveDuplicationConflictAction( + persistedRequests, + (request) => request.command === WRITE_COMMANDS.OPEN_REPORT && request.data?.reportID === reportID && request.data?.emailList === parameters.emailList, + ), }); } } From 09f31d5431b01dc43375ddd60e481d979554473e Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:28:12 +0700 Subject: [PATCH 061/111] Add loading to load Onyx value --- src/ROUTES.ts | 4 ++-- src/libs/Navigation/types.ts | 1 - ...WorkspaceCompanyCardsSettingsFeedNamePage.tsx | 16 ++++++++++------ .../WorkspaceCompanyCardsSettingsPage.tsx | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 336c4b0cfa68..cd94035e0fff 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1238,8 +1238,8 @@ const ROUTES = { getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards/settings` as const, }, WORKSPACE_COMPANY_CARDS_SETTINGS_FEED_NAME: { - route: 'settings/workspaces/:policyID/company-cards/settings/feed-name/:feedName', - getRoute: (policyID: string, feedName: string) => `settings/workspaces/${policyID}/company-cards/settings/feed-name/${encodeURIComponent(feedName)}` as const, + route: 'settings/workspaces/:policyID/company-cards/settings/feed-name', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards/settings/feed-name` as const, }, WORKSPACE_RULES: { route: 'settings/workspaces/:policyID/rules', diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 1630954c666a..ba859efff944 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -854,7 +854,6 @@ type SettingsNavigatorParamList = { }; [SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS_FEED_NAME]: { policyID: string; - feedName?: string; }; [SCREENS.WORKSPACE.EXPENSIFY_CARD_DETAILS]: { policyID: string; diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx index 33ee035f133c..3bc7a7301163 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx @@ -5,6 +5,7 @@ import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; @@ -25,12 +26,13 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {WorkspaceCompanyCardFeedName} from '@src/types/form/WorkspaceCompanyCardFeedName'; import INPUT_IDS from '@src/types/form/WorkspaceTaxCustomName'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; type WorkspaceCompanyCardsSettingsFeedNamePageProps = StackScreenProps; function WorkspaceCompanyCardsSettingsFeedNamePage({ route: { - params: {policyID, feedName: selectedFeedName}, + params: {policyID}, }, }: WorkspaceCompanyCardsSettingsFeedNamePageProps) { const styles = useThemeStyles(); @@ -38,12 +40,10 @@ function WorkspaceCompanyCardsSettingsFeedNamePage({ const {inputCallbackRef} = useAutoFocusInput(); const policy = usePolicy(policyID); const workspaceAccountID = policy?.workspaceAccountID ?? -1; - const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`); - const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`); + const [lastSelectedFeed, lastSelectedFeedResult] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`); + const [cardFeeds, cardFeedsResult] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`); const selectedFeed = CardUtils.getSelectedFeed(lastSelectedFeed, cardFeeds); - const feedName = selectedFeed - ? cardFeeds?.settings?.companyCardNicknames?.[selectedFeed] ?? translate('workspace.companyCards.feedName', {feedName: CardUtils.getCardFeedName(selectedFeed)}) - : decodeURIComponent(selectedFeedName ?? ''); + const feedName = cardFeeds?.settings?.companyCardNicknames?.[selectedFeed] ?? translate('workspace.companyCards.feedName', {feedName: CardUtils.getCardFeedName(selectedFeed)}); const validate = useCallback( (values: FormOnyxValues) => { @@ -64,6 +64,10 @@ function WorkspaceCompanyCardsSettingsFeedNamePage({ Navigation.goBack(ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS.getRoute(policyID)); }; + if (isLoadingOnyxValue(cardFeedsResult) || isLoadingOnyxValue(lastSelectedFeedResult)) { + return ; + } + return ( { - Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS_FEED_NAME.getRoute(policyID, feedName)); + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_SETTINGS_FEED_NAME.getRoute(policyID)); }; const deleteCompanyCardFeed = () => { From ed867239a93c2f5074f1b5c8a73252229e1ff018 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 12 Nov 2024 13:58:41 +0800 Subject: [PATCH 062/111] fix missing highlight bg color when pressing on menu item --- src/styles/utils/index.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 881fbc278190..d1b28f29f8e8 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1125,19 +1125,19 @@ function getAmountWidth(amount: string): number { * Single true value will give result accordingly. */ function getItemBackgroundColorStyle(isSelected: boolean, isFocused: boolean, isDisabled: boolean, selectedBG: string, focusedBG: string): ViewStyle { - let backgroundColor; - if (isSelected) { - backgroundColor = selectedBG; - } else if (isDisabled) { - backgroundColor = undefined; - } else if (isFocused) { - backgroundColor = focusedBG; + return {backgroundColor: selectedBG}; } - return { - backgroundColor, - }; + if (isDisabled) { + return {backgroundColor: undefined}; + } + + if (isFocused) { + return {backgroundColor: focusedBG} + } + + return {}; } const staticStyleUtils = { From 1910badce53ff5b640d372f74d1d0e6dfdf8c9fa Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 12 Nov 2024 14:07:34 +0800 Subject: [PATCH 063/111] prettier --- src/styles/utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index d1b28f29f8e8..d196b3fdf09b 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1134,7 +1134,7 @@ function getItemBackgroundColorStyle(isSelected: boolean, isFocused: boolean, is } if (isFocused) { - return {backgroundColor: focusedBG} + return {backgroundColor: focusedBG}; } return {}; From 611d3fe9e6a67e97f46eca0ae1b397f15647a5c0 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 12 Nov 2024 13:14:36 +0700 Subject: [PATCH 064/111] fix: use pattern B for unassigning cards --- src/libs/actions/CompanyCards.ts | 12 ++++++++++-- .../companyCards/WorkspaceCompanyCardsList.tsx | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index 18779a284278..400cc95fad84 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -244,14 +244,20 @@ function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: stri onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${bankName}`, value: { - [cardID]: null, + [cardID]: { + ...card, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, }, }, { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.CARD_LIST, value: { - [cardID]: null, + [cardID]: { + ...card, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, }, }, ], @@ -263,6 +269,7 @@ function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: stri value: { [cardID]: { ...card, + pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), }, }, @@ -273,6 +280,7 @@ function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: stri value: { [cardID]: { ...card, + pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), }, }, diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx index 6e45b7993a98..4bf703bfb270 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx @@ -42,6 +42,7 @@ function WorkspaceCompanyCardsList({cardsList, policyID}: WorkspaceCompanyCardsL key={`${item.nameValuePairs?.cardTitle}_${index}`} errorRowStyles={styles.ph5} errors={item.errors} + pendingAction={item.pendingAction} > Date: Tue, 12 Nov 2024 08:22:25 +0100 Subject: [PATCH 065/111] add comments for new changes --- .../accounting/qbd/export/QuickbooksDesktopExportPage.tsx | 1 + .../QuickbooksDesktopPreferredExporterConfigurationPage.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx index d6fc05526c1d..ff8547952155 100644 --- a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx +++ b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopExportPage.tsx @@ -31,6 +31,7 @@ function QuickbooksDesktopExportPage({policy}: WithPolicyConnectionsProps) { { description: translate('workspace.accounting.preferredExporter'), onPress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_PREFERRED_EXPORTER.getRoute(policyID)), + // We use the logical OR (||) here instead of ?? because `exporter` could be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing title: qbdConfig?.export?.exporter || policyOwner, subscribedSettings: [CONST.QUICKBOOKS_DESKTOP_CONFIG.EXPORTER], diff --git a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopPreferredExporterConfigurationPage.tsx b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopPreferredExporterConfigurationPage.tsx index 2a227d7df344..b571f67e8350 100644 --- a/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopPreferredExporterConfigurationPage.tsx +++ b/src/pages/workspace/accounting/qbd/export/QuickbooksDesktopPreferredExporterConfigurationPage.tsx @@ -45,6 +45,7 @@ function QuickbooksDesktopPreferredExporterConfigurationPage({policy}: WithPolic value: exporter.email, text: exporter.email, keyForList: exporter.email, + // We use the logical OR (||) here instead of ?? because `exporter` could be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing isSelected: (currentExporter || policy?.owner) === exporter.email, }); From 3ef15a0c8d939001dc00475091dfbbd54e63358a Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 12 Nov 2024 16:08:14 +0700 Subject: [PATCH 066/111] fix: display a proper cardholder avatar --- .../workspace/companyCards/WorkspaceCompanyCardsListRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx index 91eddfd96936..2ce8c289c96e 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx @@ -27,7 +27,7 @@ function WorkspaceCompanyCardsListRow({cardholder, name, cardNumber}: WorkspaceC Date: Tue, 12 Nov 2024 16:39:51 +0700 Subject: [PATCH 067/111] fix: add detailed comments in test --- tests/unit/WorkspaceSettingsUtilsTest.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts index f804356d3706..9ee2b511379f 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.ts +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -21,7 +21,7 @@ describe('WorkspacesSettingsUtils', () => { }); describe('getBrickRoadForPolicy', () => { it('Should return "error"', async () => { - // Given mock data for reports, transaction violations, sessions, and report actions + // Given mock data for reports, transaction violations, sessions, and report actions. const report = Object.values(mockData.reports)?.at(0); const transactionViolations = mockData.transactionViolations; const reports = mockData.reports; @@ -40,12 +40,12 @@ describe('WorkspacesSettingsUtils', () => { // When calling getBrickRoadForPolicy with a report and report actions const result = getBrickRoadForPolicy(report as Report, reportActions as OnyxCollection); - // Then the result should be 'error' + // The result should be 'error' because there is at least one IOU action associated with a transaction that has a violation. expect(result).toBe('error'); }); it('Should return "undefined"', async () => { - // Given mock data for reports, transaction violations, sessions, and report actions + // Given mock data for reports, sessions, and report actions. Note: Transaction data is intentionally excluded. const report = Object.values(mockData.reports)?.at(0); const reports = mockData.reports; const session = mockData.session; @@ -62,7 +62,7 @@ describe('WorkspacesSettingsUtils', () => { // When calling getBrickRoadForPolicy with a report and report actions const result = getBrickRoadForPolicy(report as Report, reportActions as OnyxCollection); - // Then the result should be 'undefined' + // Then the result should be 'undefined' since no IOU action is linked to a transaction with a violation. expect(result).toBe(undefined); }); }); From cfb7a1cfccc346168e27f178fce0e55d6210fa5b Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 12 Nov 2024 18:40:42 +0700 Subject: [PATCH 068/111] fix: apply pattern B styles on a card item in member details page --- .../members/WorkspaceMemberDetailsPage.tsx | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index e079fdee90a0..e3700fbaacf0 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -315,25 +315,38 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM {translate('walletPage.assignedCards')}
- {(memberCards as MemberCard[]).map((memberCard) => ( - navigateToDetails(memberCard)} - shouldShowRightIcon - /> - ))} + {(memberCards as MemberCard[]).map((memberCard) => { + const isCardDeleted = memberCard.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + return ( + + navigateToDetails(memberCard)} + shouldRemoveHoverBackground={isCardDeleted} + disabled={isCardDeleted} + shouldShowRightIcon={!isCardDeleted} + style={[isCardDeleted ? styles.offlineFeedback.deleted : {}]} + /> + + ); + })} Date: Tue, 12 Nov 2024 19:54:07 +0700 Subject: [PATCH 069/111] fix: apply requested changes --- src/libs/actions/CompanyCards.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index 400cc95fad84..0b3cd227d1a5 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -245,7 +245,6 @@ function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: stri key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${bankName}`, value: { [cardID]: { - ...card, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }, }, @@ -255,7 +254,6 @@ function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: stri key: ONYXKEYS.CARD_LIST, value: { [cardID]: { - ...card, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }, }, @@ -268,7 +266,6 @@ function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: stri key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${bankName}`, value: { [cardID]: { - ...card, pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), }, @@ -279,7 +276,6 @@ function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: stri key: ONYXKEYS.CARD_LIST, value: { [cardID]: { - ...card, pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), }, From 9c8cda7f1fb9f77c8fd61028f80d83db86dfa662 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 12 Nov 2024 19:58:27 +0700 Subject: [PATCH 070/111] fix: apply requested changes --- src/libs/actions/CompanyCards.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index 0b3cd227d1a5..9fe24faaa155 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -260,6 +260,23 @@ function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: stri }, ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${bankName}`, + value: { + [cardID]: null, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.CARD_LIST, + value: { + [cardID]: null, + }, + }, + ], + failureData: [ { onyxMethod: Onyx.METHOD.MERGE, From 7b790110a0ecb46a8816b50d1cae60e8edef4e3e Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 12 Nov 2024 20:13:25 +0700 Subject: [PATCH 071/111] fix: minor change --- src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index e3700fbaacf0..3e72beee812b 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -219,7 +219,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM return ; } - const shouldShowCardsSection = (!!policy?.areExpensifyCardsEnabled && !!paymentAccountID) ?? (!!policy?.areCompanyCardsEnabled && hasMultipleFeeds); + const shouldShowCardsSection = (!!policy?.areExpensifyCardsEnabled && !!paymentAccountID) || (!!policy?.areCompanyCardsEnabled && hasMultipleFeeds); return ( Date: Tue, 12 Nov 2024 21:01:05 +0700 Subject: [PATCH 072/111] fix: disable navigating to card details page after unassigning --- .../workspace/companyCards/WorkspaceCompanyCardsList.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx index 4bf703bfb270..54a8fd916185 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx @@ -37,6 +37,7 @@ function WorkspaceCompanyCardsList({cardsList, policyID}: WorkspaceCompanyCardsL ({item, index}: ListRenderItemInfo) => { const cardID = Object.keys(cardsList ?? {}).find((id) => cardsList?.[id].cardID === item.cardID); const cardName = CardUtils.getCompanyCardNumber(cardsList?.cardList ?? {}, item.lastFourPAN); + const isCardDeleted = item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; return ( { - if (!cardID || !item?.accountID) { + if (!cardID || !item?.accountID || isCardDeleted) { return; } Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, cardID, item.bank)); From 72861ec34e6abc2cfdef5bdce461e91801db8ae6 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 12 Nov 2024 21:04:33 +0700 Subject: [PATCH 073/111] fix: minor change --- src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx index 54a8fd916185..9e9e2bf5208f 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx @@ -52,7 +52,7 @@ function WorkspaceCompanyCardsList({cardsList, policyID}: WorkspaceCompanyCardsL hoverStyle={styles.hoveredComponentBG} disabled={isCardDeleted} onPress={() => { - if (!cardID || !item?.accountID || isCardDeleted) { + if (!cardID || !item?.accountID) { return; } Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_DETAILS.getRoute(policyID, cardID, item.bank)); From 894537dd695944cf83a1f7c5b9c71db173ac1476 Mon Sep 17 00:00:00 2001 From: Tom Rhys Jones Date: Tue, 12 Nov 2024 14:25:49 +0000 Subject: [PATCH 074/111] EXFY public room --- contributingGuides/CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index be71cd4e115a..cb9c6e699900 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -11,6 +11,7 @@ You can create as many accounts as needed in order to test your changes directly 1. When testing chat functionality in the app please do this between accounts you or your fellow contributors own - **do not test chatting with Concierge**, as this diverts to our customer support team. Thank you. 2. A member of our customer onboarding team gets auto-assigned to every new policy created by a non-paying account to help them set up. Please **do not interact with these teams, ask for calls, or support on your issues.** If you do need to test functionality inside the defaultRooms (#admins & #announce) for any issues you’re working on, please let them know that you are a contributor and don’t need assistance. They will proceed to ignore the chat. +3. Please **do not post in any Expensify owned public room for testing** (e.g #exfy-roadmap, #new-expensify-feedback). These rooms include real customers and investors. Thanks! #### Generating Multiple Test Accounts You can generate multiple test accounts by using a `+` postfix, for example if your email is test@test.com, you can create multiple New Expensify accounts connected to the same email address by using test+123@test.com, test+456@test.com, etc. From b1b27d23d3e651951ac63b0b1cd7a05b4345c71e Mon Sep 17 00:00:00 2001 From: Tom Rhys Jones Date: Tue, 12 Nov 2024 14:42:07 +0000 Subject: [PATCH 075/111] Adding a URL link to a test public room --- contributingGuides/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index cb9c6e699900..0a9417820190 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -11,7 +11,7 @@ You can create as many accounts as needed in order to test your changes directly 1. When testing chat functionality in the app please do this between accounts you or your fellow contributors own - **do not test chatting with Concierge**, as this diverts to our customer support team. Thank you. 2. A member of our customer onboarding team gets auto-assigned to every new policy created by a non-paying account to help them set up. Please **do not interact with these teams, ask for calls, or support on your issues.** If you do need to test functionality inside the defaultRooms (#admins & #announce) for any issues you’re working on, please let them know that you are a contributor and don’t need assistance. They will proceed to ignore the chat. -3. Please **do not post in any Expensify owned public room for testing** (e.g #exfy-roadmap, #new-expensify-feedback). These rooms include real customers and investors. Thanks! +3. Please **do not post in any Expensify owned public room for testing** (e.g #exfy-roadmap, #new-expensify-feedback). These rooms include real customers and investors. You can create your own public rooms, or [use this test public room](https://staging.new.expensify.com/r/2091104345528462) on either staging or production. Thanks! #### Generating Multiple Test Accounts You can generate multiple test accounts by using a `+` postfix, for example if your email is test@test.com, you can create multiple New Expensify accounts connected to the same email address by using test+123@test.com, test+456@test.com, etc. From f6e8eae9fd69e8c851c63ac7a2657aa88bda7f2a Mon Sep 17 00:00:00 2001 From: Gandalf Date: Tue, 12 Nov 2024 21:01:49 +0530 Subject: [PATCH 076/111] Revert "fix: unify distance rates display" --- src/CONST.ts | 1 - src/libs/CurrencyUtils.ts | 3 +-- src/libs/PolicyUtils.ts | 11 ++++++----- tests/unit/PolicyUtilsTest.ts | 11 +++-------- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d93eb230f779..7f1ebb8d55d1 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5830,7 +5830,6 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, - MIN_TAX_RATE_DECIMAL_PLACES: 2, DOWNLOADS_PATH: '/Downloads', DOWNLOADS_TIMEOUT: 5000, diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index b701a32a7c98..f9ac681cb468 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -164,8 +164,7 @@ function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRE return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, - minimumFractionDigits: CONST.MIN_TAX_RATE_DECIMAL_PLACES, - maximumFractionDigits: CONST.MAX_TAX_RATE_DECIMAL_PLACES, + minimumFractionDigits: CONST.MAX_TAX_RATE_DECIMAL_PLACES, }); } diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index f038006730af..509539421419 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -129,7 +129,7 @@ function getNumericValue(value: number | string, toLocaleDigit: (arg: string) => if (Number.isNaN(numValue)) { return NaN; } - return numValue; + return numValue.toFixed(CONST.CUSTOM_UNITS.RATE_DECIMALS); } /** @@ -161,10 +161,11 @@ function getRateDisplayValue(value: number, toLocaleDigit: (arg: string) => stri } if (withDecimals) { - const decimalPart = numValue.toString().split('.').at(1) ?? ''; - // Set the fraction digits to be between 2 and 4 (OD Behavior) - const fractionDigits = Math.min(Math.max(decimalPart.length, CONST.MIN_TAX_RATE_DECIMAL_PLACES), CONST.MAX_TAX_RATE_DECIMAL_PLACES); - return Number(numValue).toFixed(fractionDigits).toString().replace('.', toLocaleDigit('.')); + const decimalPart = numValue.toString().split('.').at(1); + if (decimalPart) { + const fixedDecimalPoints = decimalPart.length > 2 && !decimalPart.endsWith('0') ? 3 : 2; + return Number(numValue).toFixed(fixedDecimalPoints).toString().replace('.', toLocaleDigit('.')); + } } return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.toString().length); diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index e760bb2040c7..8178bb99e877 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -34,19 +34,14 @@ describe('PolicyUtils', () => { expect(rate).toEqual('10.50'); }); - it('should return non-integer value with 4 decimals as is', () => { - const rate = PolicyUtils.getRateDisplayValue(10.5312, toLocaleDigitMock, true); - expect(rate).toEqual('10.5312'); - }); - it('should return non-integer value with 3 decimals as is', () => { const rate = PolicyUtils.getRateDisplayValue(10.531, toLocaleDigitMock, true); expect(rate).toEqual('10.531'); }); - it('should return non-integer value with 4+ decimals cut to 4', () => { - const rate = PolicyUtils.getRateDisplayValue(10.531255, toLocaleDigitMock, true); - expect(rate).toEqual('10.5313'); + it('should return non-integer value with 3+ decimals cut to 3', () => { + const rate = PolicyUtils.getRateDisplayValue(10.531345, toLocaleDigitMock, true); + expect(rate).toEqual('10.531'); }); }); }); From be4d7f50949d52aa9f2cb9a8ac495ef19473edde Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 12 Nov 2024 16:09:01 +0000 Subject: [PATCH 077/111] Update version to 9.0.60-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2ec58aab1afa..8d276b919449 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009006000 - versionName "9.0.60-0" + versionCode 1009006001 + versionName "9.0.60-1" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 7b263d98cf27..3951444e867d 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.60.0 + 9.0.60.1 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 90916937f184..1929661c2645 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.60.0 + 9.0.60.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 5e3c61e29256..4ac199c2902c 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.60 CFBundleVersion - 9.0.60.0 + 9.0.60.1 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 0a5aee49900d..e3c9b91eec59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.60-0", + "version": "9.0.60-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.60-0", + "version": "9.0.60-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 54b3a3c945cb..3616a4638f61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.60-0", + "version": "9.0.60-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 58e0f20dfccbb3b9cd716578992aa683ab7d5a6d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 12 Nov 2024 22:20:24 +0530 Subject: [PATCH 078/111] clean up as per requests --- src/libs/actions/Policy/Policy.ts | 21 ++++++------------- .../MoneyRequestParticipantsSelector.tsx | 2 +- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index ebb3309fdf76..6ad8ca07821c 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -199,10 +199,10 @@ Onyx.connect({ callback: (val) => (allRecentlyUsedCurrencies = val ?? []), }); -let nvpActivePolicyID: OnyxEntry; +let activePolicyID: OnyxEntry; Onyx.connect({ key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, - callback: (value) => (nvpActivePolicyID = value), + callback: (value) => (activePolicyID = value), }); /** @@ -230,15 +230,6 @@ function getPolicy(policyID: string | undefined): OnyxEntry { return allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; } -/** - * Returns a primary policy for the user - */ -function getPrimaryPolicy(activePolicyID: OnyxEntry, currentUserLogin: string | undefined): Policy | undefined { - const activeAdminWorkspaces = PolicyUtils.getActiveAdminWorkspaces(allPolicies, currentUserLogin); - const primaryPolicy: Policy | null | undefined = activeAdminWorkspaces.find((policy) => policy.id === activePolicyID); - return primaryPolicy ?? activeAdminWorkspaces.at(0); -} - /** Check if the policy has invoicing company details */ function hasInvoicingDetails(policy: OnyxEntry): boolean { return !!policy?.invoice?.companyName && !!policy?.invoice?.companyWebsite; @@ -247,8 +238,8 @@ function hasInvoicingDetails(policy: OnyxEntry): boolean { /** * Returns a primary invoice workspace for the user */ -function getInvoicePrimaryWorkspace(activePolicyID: OnyxEntry, currentUserLogin: string | undefined): Policy | undefined { - if (PolicyUtils.canSendInvoiceFromWorkspace(activePolicyID)) { +function getInvoicePrimaryWorkspace(currentUserLogin: string | undefined): Policy | undefined { + if (PolicyUtils.canSendInvoiceFromWorkspace(activePolicyID ?? '-1')) { return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID ?? '-1'}`]; } const activeAdminWorkspaces = PolicyUtils.getActiveAdminWorkspaces(allPolicies, currentUserLogin); @@ -1627,7 +1618,7 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName const optimisticCategoriesData = buildOptimisticPolicyCategories(policyID, CONST.POLICY.DEFAULT_CATEGORIES); - const shouldSetCreatedWorkspaceAsActivePolicy = !!nvpActivePolicyID && allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${nvpActivePolicyID}`]?.type === CONST.POLICY.TYPE.PERSONAL; + const shouldSetCreatedWorkspaceAsActivePolicy = !!activePolicyID && allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`]?.type === CONST.POLICY.TYPE.PERSONAL; const optimisticData: OnyxUpdate[] = [ { @@ -1821,7 +1812,7 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName failureData.push({ onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, - value: nvpActivePolicyID ?? '', + value: activePolicyID ?? '', }); } diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index df8d7797b41f..4478951555ef 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -241,7 +241,7 @@ function MoneyRequestParticipantsSelector({ ]; if (iouType === CONST.IOU.TYPE.INVOICE) { - const policyID = option.item && ReportUtils.isInvoiceRoom(option.item) ? option.policyID : Policy.getInvoicePrimaryWorkspace(activePolicyID, currentUserLogin)?.id; + const policyID = option.item && ReportUtils.isInvoiceRoom(option.item) ? option.policyID : Policy.getInvoicePrimaryWorkspace(currentUserLogin)?.id; newParticipants.push({ policyID, isSender: true, From 53aa6466e0d43b7035d2b888471e0091cb71fe81 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 12 Nov 2024 22:21:08 +0530 Subject: [PATCH 079/111] remove undefined variable --- src/libs/actions/Policy/Policy.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 6ad8ca07821c..f514a9b27158 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -4617,7 +4617,6 @@ export { setPolicyCustomTaxName, clearPolicyErrorField, isCurrencySupportedForDirectReimbursement, - getPrimaryPolicy, getInvoicePrimaryWorkspace, createDraftWorkspace, savePreferredExportMethod, From ece58f0dfb7ae11e9adf785b75479c57a2d20ae7 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 12 Nov 2024 09:56:17 -0700 Subject: [PATCH 080/111] disable button offline --- src/components/SelectionList/Search/ActionCell.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/SelectionList/Search/ActionCell.tsx b/src/components/SelectionList/Search/ActionCell.tsx index 55e2cf6f849d..0a360a96e7c7 100644 --- a/src/components/SelectionList/Search/ActionCell.tsx +++ b/src/components/SelectionList/Search/ActionCell.tsx @@ -4,6 +4,7 @@ import Badge from '@components/Badge'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -45,6 +46,7 @@ function ActionCell({ const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const {isOffline} = useNetwork(); const text = translate(actionTranslationsMap[action]); @@ -98,6 +100,7 @@ function ActionCell({ innerStyles={buttonInnerStyles} isLoading={isLoading} success + isDisabled={isOffline} /> ); } From 94f9bd2d79f11b6ae5391785aead94734e0db489 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 31 Oct 2024 16:43:07 +0100 Subject: [PATCH 081/111] chore: remove the homepage_initial_render event --- contributingGuides/PERFORMANCE_METRICS.md | 3 +-- src/CONST.ts | 1 - src/libs/Navigation/AppNavigator/AuthScreens.tsx | 4 ---- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index 6c40e346a3ce..ad9a6509f7ae 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -14,7 +14,6 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `js_loaded` | ✅ | The time it takes for the JavaScript bundle to load.

**Platforms:** Android, iOS | **Android:** Starts in the `onCreate` method.

**iOS:** Starts in the AppDelegate's `didFinishLaunchingWithOptions` method. | Stops at the first render of the app via native module on the JS side. | | `_app_in_foreground` | ✅ | The time when the app is running in the foreground and available to the user.

**Platforms:** Android, iOS | **Android:** Starts when the first activity to reach the foreground has its `onResume()` method called.

**iOS:** Starts when the application receives the `UIApplicationDidBecomeActiveNotification` notification. | **Android:** Stops when the last activity to leave the foreground has its `onStop()` method called.

**iOS:** Stops when it receives the `UIApplicationWillResignActiveNotification` notification. | | `_app_in_background` | ✅ | Time when the app is running in the background.

**Platforms:** Android, iOS | **Android:** Starts when the last activity to leave the foreground has its `onStop()` method called.

**iOS:** Starts when the application receives the `UIApplicationWillResignActiveNotification` notification. | **Android:** Stops when the first activity to reach the foreground has its `onResume()` method called.

**iOS:** Stops when it receives the `UIApplicationDidBecomeActiveNotification` notification. | -| `homepage_initial_render` | ✅ | Time taken for the initial render of the app for a logged in user.

**Platforms:** All | Starts with the first render of the `AuthScreens` component. | Stops once the `AuthScreens` component is mounted. | | `sidebar_loaded` | ❌ | Time taken for the Sidebar to load.

**Platforms:** All | Starts when the Sidebar is mounted. | Stops when the Splash Screen is hidden. | | `calc_most_recent_last_modified_action` | ✅ | Time taken to find the most recently modified report action or report.

**Platforms:** All | Starts when the app reconnects to the network | Ends when the app reconnects to the network and the most recent report action or report is found. | | `search_render` | ✅ | Time taken to render the Chat Finder page.

**Platforms:** All | Starts when the Chat Finder icon in LHN is pressed. | Stops when the list of available options is rendered for the first time. | @@ -46,4 +45,4 @@ To ensure this documentation remains accurate and useful, please adhere to the f ## Additional Resources - [Firebase Documentation](https://firebase.google.com/docs) -- [Firebase Performance Monitoring](https://firebase.google.com/docs/perf-mon) \ No newline at end of file +- [Firebase Performance Monitoring](https://firebase.google.com/docs/perf-mon) diff --git a/src/CONST.ts b/src/CONST.ts index d93eb230f779..79a18ab2f588 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1256,7 +1256,6 @@ const CONST = { SEARCH_ROUTER_RENDER: 'search_router_render', CHAT_RENDER: 'chat_render', OPEN_REPORT: 'open_report', - HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render', REPORT_INITIAL_RENDER: 'report_initial_render', SWITCH_REPORT: 'switch_report', OPEN_REPORT_FROM_PREVIEW: 'open_report_from_preview', diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 58586250f958..d7f3cd2e80cc 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -248,8 +248,6 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie // eslint-disable-next-line react-compiler/react-compiler if (isInitialRender.current) { - Timing.start(CONST.TIMING.HOMEPAGE_INITIAL_RENDER); - const currentURL = getCurrentUrl(); if (currentURL) { initialReportID = new URL(currentURL).pathname.match(CONST.REGEX.REPORT_ID_FROM_PATH)?.at(1); @@ -313,8 +311,6 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie } Download.clearDownloads(); - Timing.end(CONST.TIMING.HOMEPAGE_INITIAL_RENDER); - const unsubscribeOnyxModal = onyxSubscribe({ key: ONYXKEYS.MODAL, callback: (modalArg) => { From 647c3f129d0b63f23161de6c121299406e4c0194 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 31 Oct 2024 16:57:10 +0100 Subject: [PATCH 082/111] chore: remove the chat_render event --- contributingGuides/PERFORMANCE_METRICS.md | 1 - src/CONST.ts | 1 - src/libs/E2E/tests/chatOpeningTest.e2e.ts | 16 ---------------- src/pages/home/ReportScreen.tsx | 9 +-------- 4 files changed, 1 insertion(+), 26 deletions(-) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index ad9a6509f7ae..bb0c4a55e4f0 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -24,7 +24,6 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `switch_report` | ✅ | Time taken to open report.

**Platforms:** All | Starts when the chat in the LHN is pressed. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_from_preview` | ✅ | Time taken to open a report from preview.

(previously `switch_report_from_preview`)

**Platforms:** All | Starts when the user presses the Report Preview. | Stops when the `ReportActionsList` finishes laying out. | | `switch_report_from_preview` | ❌ | **[REMOVED]** Time taken to open a report from preview. | Starts when the user presses the Report Preview. | Stops when the `ReportActionsList` finishes laying out. | -| `chat_render` | ✅ | Time taken to render the Report screen.

**Platforms:** All | Starts when the `ReportScreen` is being rendered for the first time. | Stops once the `ReportScreen` component is mounted. | | `report_initial_render` | ❌ | Time taken to render the Report screen.

**Platforms:** All | Starts when the first item is rendered in the `LHNOptionsList`. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_thread` | ✅ | Time taken to open a thread in a report.

**Platforms:** All | Starts when user presses Report Action Item. | Stops when the `ReportActionsList` finishes laying out. | | `message_sent` | ❌ | Time taken to send a message.

**Platforms:** All | Starts when the new message is sent. | Stops when the message is being rendered in the chat. | diff --git a/src/CONST.ts b/src/CONST.ts index 79a18ab2f588..b511c2343791 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1254,7 +1254,6 @@ const CONST = { TIMING: { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', SEARCH_ROUTER_RENDER: 'search_router_render', - CHAT_RENDER: 'chat_render', OPEN_REPORT: 'open_report', REPORT_INITIAL_RENDER: 'report_initial_render', SWITCH_REPORT: 'switch_report', diff --git a/src/libs/E2E/tests/chatOpeningTest.e2e.ts b/src/libs/E2E/tests/chatOpeningTest.e2e.ts index cf0c4889aa69..82fd00fd975d 100644 --- a/src/libs/E2E/tests/chatOpeningTest.e2e.ts +++ b/src/libs/E2E/tests/chatOpeningTest.e2e.ts @@ -46,22 +46,6 @@ const test = (config: NativeConfig) => { console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); - if (entry.name === CONST.TIMING.CHAT_RENDER) { - E2EClient.submitTestResults({ - branch: Config.E2E_BRANCH, - name: `${name} Chat opening`, - metric: entry.duration, - unit: 'ms', - }) - .then(() => { - console.debug('[E2E] Done with chat opening, exiting…'); - renderChatResolve(); - }) - .catch((err) => { - console.debug('[E2E] Error while submitting test results:', err); - }); - } - if (entry.name === CONST.TIMING.OPEN_REPORT) { E2EClient.submitTestResults({ branch: Config.E2E_BRANCH, diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 4c3ed5c705a5..cbc5b72730b5 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -230,11 +230,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro const [scrollPosition, setScrollPosition] = useState({}); const wasReportAccessibleRef = useRef(false); - // eslint-disable-next-line react-compiler/react-compiler - if (firstRenderRef.current) { - Timing.start(CONST.TIMING.CHAT_RENDER); - Performance.markStart(CONST.TIMING.CHAT_RENDER); - } + const [isComposerFocus, setIsComposerFocus] = useState(false); const shouldAdjustScrollView = useMemo(() => isComposerFocus && !modal?.willAlertModalBecomeVisible, [isComposerFocus, modal]); const viewportOffsetTop = useViewportOffsetTop(shouldAdjustScrollView); @@ -487,9 +483,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro useAppFocusEvent(clearNotifications); useEffect(() => { - Timing.end(CONST.TIMING.CHAT_RENDER); - Performance.markEnd(CONST.TIMING.CHAT_RENDER); - const interactionTask = InteractionManager.runAfterInteractions(() => { ComposerActions.setShouldShowComposeInput(true); }); From cb25dc4cd690bd063467b8b5891befbd98ce951c Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 31 Oct 2024 17:12:42 +0100 Subject: [PATCH 083/111] chore: remove the report_initial_render event --- contributingGuides/PERFORMANCE_METRICS.md | 1 - src/CONST.ts | 1 - src/libs/E2E/tests/chatOpeningTest.e2e.ts | 2 +- src/libs/E2E/tests/linkingTest.e2e.ts | 11 ----------- src/libs/actions/App.ts | 1 - src/pages/home/report/ReportActionsView.tsx | 1 - 6 files changed, 1 insertion(+), 16 deletions(-) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index bb0c4a55e4f0..29cead03a774 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -24,7 +24,6 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `switch_report` | ✅ | Time taken to open report.

**Platforms:** All | Starts when the chat in the LHN is pressed. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_from_preview` | ✅ | Time taken to open a report from preview.

(previously `switch_report_from_preview`)

**Platforms:** All | Starts when the user presses the Report Preview. | Stops when the `ReportActionsList` finishes laying out. | | `switch_report_from_preview` | ❌ | **[REMOVED]** Time taken to open a report from preview. | Starts when the user presses the Report Preview. | Stops when the `ReportActionsList` finishes laying out. | -| `report_initial_render` | ❌ | Time taken to render the Report screen.

**Platforms:** All | Starts when the first item is rendered in the `LHNOptionsList`. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_thread` | ✅ | Time taken to open a thread in a report.

**Platforms:** All | Starts when user presses Report Action Item. | Stops when the `ReportActionsList` finishes laying out. | | `message_sent` | ❌ | Time taken to send a message.

**Platforms:** All | Starts when the new message is sent. | Stops when the message is being rendered in the chat. | diff --git a/src/CONST.ts b/src/CONST.ts index b511c2343791..7361b6561345 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1255,7 +1255,6 @@ const CONST = { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', SEARCH_ROUTER_RENDER: 'search_router_render', OPEN_REPORT: 'open_report', - REPORT_INITIAL_RENDER: 'report_initial_render', SWITCH_REPORT: 'switch_report', OPEN_REPORT_FROM_PREVIEW: 'open_report_from_preview', OPEN_REPORT_THREAD: 'open_report_thread', diff --git a/src/libs/E2E/tests/chatOpeningTest.e2e.ts b/src/libs/E2E/tests/chatOpeningTest.e2e.ts index 82fd00fd975d..40e5d2677090 100644 --- a/src/libs/E2E/tests/chatOpeningTest.e2e.ts +++ b/src/libs/E2E/tests/chatOpeningTest.e2e.ts @@ -27,7 +27,7 @@ const test = (config: NativeConfig) => { console.debug('[E2E] Logged in, getting chat opening metrics and submitting them…'); - const [renderChatPromise, renderChatResolve] = getPromiseWithResolve(); + const [renderChatPromise] = getPromiseWithResolve(); const [chatTTIPromise, chatTTIResolve] = getPromiseWithResolve(); Promise.all([renderChatPromise, chatTTIPromise]).then(() => { diff --git a/src/libs/E2E/tests/linkingTest.e2e.ts b/src/libs/E2E/tests/linkingTest.e2e.ts index 18ba438c2ca6..441d6d76a042 100644 --- a/src/libs/E2E/tests/linkingTest.e2e.ts +++ b/src/libs/E2E/tests/linkingTest.e2e.ts @@ -1,7 +1,6 @@ import {DeviceEventEmitter} from 'react-native'; import type {NativeConfig} from 'react-native-config'; import Config from 'react-native-config'; -import Timing from '@libs/actions/Timing'; import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; @@ -22,7 +21,6 @@ const test = (config: NativeConfig) => { console.debug('[E2E] Logging in for comment linking'); const reportID = getConfigValueOrThrow('reportID', config); - const linkedReportID = getConfigValueOrThrow('linkedReportID', config); const linkedReportActionID = getConfigValueOrThrow('linkedReportActionID', config); const name = getConfigValueOrThrow('name', config); @@ -61,15 +59,6 @@ const test = (config: NativeConfig) => { return; } - if (entry.name === CONST.TIMING.REPORT_INITIAL_RENDER) { - console.debug('[E2E] Navigating to linked report action…'); - Timing.start(CONST.TIMING.SWITCH_REPORT); - Performance.markStart(CONST.TIMING.SWITCH_REPORT); - - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(linkedReportID, linkedReportActionID)); - return; - } - if (entry.name === CONST.TIMING.SWITCH_REPORT) { console.debug('[E2E] Linking: 1'); diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index 5a594a19e15a..7fe1745285fb 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -179,7 +179,6 @@ function setSidebarLoaded() { } Onyx.set(ONYXKEYS.IS_SIDEBAR_LOADED, true); - Performance.markStart(CONST.TIMING.REPORT_INITIAL_RENDER); } let appState: AppStateStatus; diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index 8896611905ca..21a05f7567dc 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -430,7 +430,6 @@ function ReportActionsView({ // Capture the init measurement only once not per each chat switch as the value gets overwritten if (!ReportActionsView.initMeasured) { Performance.markEnd(CONST.TIMING.OPEN_REPORT); - Performance.markEnd(CONST.TIMING.REPORT_INITIAL_RENDER); ReportActionsView.initMeasured = true; } else { Performance.markEnd(CONST.TIMING.SWITCH_REPORT); From 67a7ec819cc678e950bcd1d195abc56211d25ff5 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 31 Oct 2024 17:36:22 +0100 Subject: [PATCH 084/111] chore: remove the sidebar_loaded event --- contributingGuides/PERFORMANCE_METRICS.md | 1 - src/CONST.ts | 1 - src/Expensify.tsx | 1 - src/libs/E2E/tests/chatOpeningTest.e2e.ts | 10 -------- src/libs/E2E/tests/linkingTest.e2e.ts | 9 ------- .../E2E/tests/openSearchRouterTest.e2e.ts | 24 ------------------- src/libs/E2E/tests/reportTypingTest.e2e.ts | 4 ---- src/libs/Performance.tsx | 4 ---- .../SidebarScreen/BaseSidebarScreen.tsx | 5 ---- 9 files changed, 59 deletions(-) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index 29cead03a774..56db207b2425 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -14,7 +14,6 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `js_loaded` | ✅ | The time it takes for the JavaScript bundle to load.

**Platforms:** Android, iOS | **Android:** Starts in the `onCreate` method.

**iOS:** Starts in the AppDelegate's `didFinishLaunchingWithOptions` method. | Stops at the first render of the app via native module on the JS side. | | `_app_in_foreground` | ✅ | The time when the app is running in the foreground and available to the user.

**Platforms:** Android, iOS | **Android:** Starts when the first activity to reach the foreground has its `onResume()` method called.

**iOS:** Starts when the application receives the `UIApplicationDidBecomeActiveNotification` notification. | **Android:** Stops when the last activity to leave the foreground has its `onStop()` method called.

**iOS:** Stops when it receives the `UIApplicationWillResignActiveNotification` notification. | | `_app_in_background` | ✅ | Time when the app is running in the background.

**Platforms:** Android, iOS | **Android:** Starts when the last activity to leave the foreground has its `onStop()` method called.

**iOS:** Starts when the application receives the `UIApplicationWillResignActiveNotification` notification. | **Android:** Stops when the first activity to reach the foreground has its `onResume()` method called.

**iOS:** Stops when it receives the `UIApplicationDidBecomeActiveNotification` notification. | -| `sidebar_loaded` | ❌ | Time taken for the Sidebar to load.

**Platforms:** All | Starts when the Sidebar is mounted. | Stops when the Splash Screen is hidden. | | `calc_most_recent_last_modified_action` | ✅ | Time taken to find the most recently modified report action or report.

**Platforms:** All | Starts when the app reconnects to the network | Ends when the app reconnects to the network and the most recent report action or report is found. | | `search_render` | ✅ | Time taken to render the Chat Finder page.

**Platforms:** All | Starts when the Chat Finder icon in LHN is pressed. | Stops when the list of available options is rendered for the first time. | | `load_search_options` | ✅ | Time taken to generate the list of options used in Chat Finder.

**Platforms:** All | Starts when the `getSearchOptions` function is called. | Stops when the list of available options is generated. | diff --git a/src/CONST.ts b/src/CONST.ts index 7361b6561345..b1a88118ac2a 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1258,7 +1258,6 @@ const CONST = { SWITCH_REPORT: 'switch_report', OPEN_REPORT_FROM_PREVIEW: 'open_report_from_preview', OPEN_REPORT_THREAD: 'open_report_thread', - SIDEBAR_LOADED: 'sidebar_loaded', LOAD_SEARCH_OPTIONS: 'load_search_options', MESSAGE_SENT: 'message_sent', COLD: 'cold', diff --git a/src/Expensify.tsx b/src/Expensify.tsx index e07b03a6d405..40e937af2d1b 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -138,7 +138,6 @@ function Expensify() { const onSplashHide = useCallback(() => { setSplashScreenState(CONST.BOOT_SPLASH_STATE.HIDDEN); - Performance.markEnd(CONST.TIMING.SIDEBAR_LOADED); }, [setSplashScreenState]); useLayoutEffect(() => { diff --git a/src/libs/E2E/tests/chatOpeningTest.e2e.ts b/src/libs/E2E/tests/chatOpeningTest.e2e.ts index 40e5d2677090..f7ec35018028 100644 --- a/src/libs/E2E/tests/chatOpeningTest.e2e.ts +++ b/src/libs/E2E/tests/chatOpeningTest.e2e.ts @@ -5,16 +5,13 @@ import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; -import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; const test = (config: NativeConfig) => { // check for login (if already logged in the action will simply resolve) console.debug('[E2E] Logging in for chat opening'); - const reportID = getConfigValueOrThrow('reportID', config); const name = getConfigValueOrThrow('name', config); E2ELogin().then((neededLogin) => { @@ -37,13 +34,6 @@ const test = (config: NativeConfig) => { }); Performance.subscribeToMeasurements((entry) => { - if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { - console.debug(`[E2E] Sidebar loaded, navigating to report…`); - Performance.markStart(CONST.TIMING.OPEN_REPORT); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); - return; - } - console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); if (entry.name === CONST.TIMING.OPEN_REPORT) { diff --git a/src/libs/E2E/tests/linkingTest.e2e.ts b/src/libs/E2E/tests/linkingTest.e2e.ts index 441d6d76a042..b517d91658b4 100644 --- a/src/libs/E2E/tests/linkingTest.e2e.ts +++ b/src/libs/E2E/tests/linkingTest.e2e.ts @@ -6,10 +6,8 @@ import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; -import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; type ViewableItem = { reportActionID?: string; @@ -20,7 +18,6 @@ type ViewableItemResponse = Array<{item?: ViewableItem}>; const test = (config: NativeConfig) => { console.debug('[E2E] Logging in for comment linking'); - const reportID = getConfigValueOrThrow('reportID', config); const linkedReportActionID = getConfigValueOrThrow('linkedReportActionID', config); const name = getConfigValueOrThrow('name', config); @@ -53,12 +50,6 @@ const test = (config: NativeConfig) => { }); Performance.subscribeToMeasurements((entry) => { - if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { - console.debug('[E2E] Sidebar loaded, navigating to a report…'); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); - return; - } - if (entry.name === CONST.TIMING.SWITCH_REPORT) { console.debug('[E2E] Linking: 1'); diff --git a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts index de9464c9c286..f3741f2e2b53 100644 --- a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts +++ b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts @@ -1,6 +1,5 @@ import Config from 'react-native-config'; import type {NativeConfig} from 'react-native-config'; -import * as E2EGenericPressableWrapper from '@components/Pressable/GenericPressable/index.e2e'; import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; @@ -36,29 +35,6 @@ const test = (config: NativeConfig) => { Performance.subscribeToMeasurements((entry) => { console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); - if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { - const props = E2EGenericPressableWrapper.getPressableProps('searchButton'); - if (!props) { - console.debug('[E2E] Search button not found, failing test!'); - E2EClient.submitTestResults({ - branch: Config.E2E_BRANCH, - error: 'Search button not found', - name: `${name} Open Search Router TTI`, - }).then(() => E2EClient.submitTestDone()); - return; - } - if (!props.onPress) { - console.debug('[E2E] Search button found but onPress prop was not present, failing test!'); - E2EClient.submitTestResults({ - branch: Config.E2E_BRANCH, - error: 'Search button found but onPress prop was not present', - name: `${name} Open Search Router TTI`, - }).then(() => E2EClient.submitTestDone()); - return; - } - // Open the search router - props.onPress(); - } if (entry.name === CONST.TIMING.SEARCH_ROUTER_RENDER) { E2EClient.submitTestResults({ diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.ts b/src/libs/E2E/tests/reportTypingTest.e2e.ts index e042a688c37d..f25859194541 100644 --- a/src/libs/E2E/tests/reportTypingTest.e2e.ts +++ b/src/libs/E2E/tests/reportTypingTest.e2e.ts @@ -53,10 +53,6 @@ const test = (config: NativeConfig) => { return; } - if (entry.name !== CONST.TIMING.SIDEBAR_LOADED) { - return; - } - console.debug(`[E2E] Sidebar loaded, navigating to a report…`); // Crowded Policy (Do Not Delete) Report, has a input bar available: Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); diff --git a/src/libs/Performance.tsx b/src/libs/Performance.tsx index ef2b08e47229..6d9319276f9a 100644 --- a/src/libs/Performance.tsx +++ b/src/libs/Performance.tsx @@ -160,10 +160,6 @@ if (Metrics.canCapturePerformanceMetrics()) { } // Capture any custom measures or metrics below - if (mark.name === `${CONST.TIMING.SIDEBAR_LOADED}_end`) { - Performance.measureFailSafe('contentAppeared_To_screenTTI', 'contentAppeared', mark.name); - Performance.measureTTI(mark.name); - } }); }).observe({type: 'mark', buffered: true}); }; diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index e77f2000b85f..3421d72270fc 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -31,11 +31,6 @@ function BaseSidebarScreen() { const {shouldUseNarrowLayout} = useResponsiveLayout(); const [activeWorkspace] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID ?? -1}`); - useEffect(() => { - Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); - Timing.start(CONST.TIMING.SIDEBAR_LOADED); - }, []); - useEffect(() => { if (!!activeWorkspace || activeWorkspaceID === undefined) { return; From fbc01be3e5aae6261fe2bf3dc79e7b936889ec7c Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 31 Oct 2024 17:46:34 +0100 Subject: [PATCH 085/111] chore: remove the switch_report event --- contributingGuides/PERFORMANCE_METRICS.md | 2 -- src/CONST.ts | 1 - src/libs/E2E/tests/linkingTest.e2e.ts | 21 +------------------ src/pages/home/report/ReportActionsView.tsx | 15 +++---------- src/pages/home/sidebar/SidebarLinks.tsx | 8 ++----- src/pages/home/sidebar/SidebarLinksData.tsx | 14 +++---------- .../SidebarScreen/BaseSidebarScreen.tsx | 16 +------------- tests/utils/LHNTestUtils.tsx | 1 - 8 files changed, 10 insertions(+), 68 deletions(-) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index 56db207b2425..36ffc6ee3b8d 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -20,9 +20,7 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `search_filter_options` | ✅ | Time taken to filter search options in Chat Finder by given search value.

**Platforms:** All | Starts when user types something in the Chat Finder search input. | Stops when the list of filtered options is generated. | | `trie_initialization` | ✅ | Time taken to build the emoji trie.

**Platforms:** All | Starts when emoji trie begins to build. | Stops when emoji trie building is complete. | | `open_report` | ❌ | Time taken to open a report.

**Platforms:** All | Starts when the row in the `LHNOptionsList` is pressed. | Stops when the `ReportActionsList` finishes laying out. | -| `switch_report` | ✅ | Time taken to open report.

**Platforms:** All | Starts when the chat in the LHN is pressed. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_from_preview` | ✅ | Time taken to open a report from preview.

(previously `switch_report_from_preview`)

**Platforms:** All | Starts when the user presses the Report Preview. | Stops when the `ReportActionsList` finishes laying out. | -| `switch_report_from_preview` | ❌ | **[REMOVED]** Time taken to open a report from preview. | Starts when the user presses the Report Preview. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_thread` | ✅ | Time taken to open a thread in a report.

**Platforms:** All | Starts when user presses Report Action Item. | Stops when the `ReportActionsList` finishes laying out. | | `message_sent` | ❌ | Time taken to send a message.

**Platforms:** All | Starts when the new message is sent. | Stops when the message is being rendered in the chat. | diff --git a/src/CONST.ts b/src/CONST.ts index b1a88118ac2a..c49f92e9d1ef 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1255,7 +1255,6 @@ const CONST = { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', SEARCH_ROUTER_RENDER: 'search_router_render', OPEN_REPORT: 'open_report', - SWITCH_REPORT: 'switch_report', OPEN_REPORT_FROM_PREVIEW: 'open_report_from_preview', OPEN_REPORT_THREAD: 'open_report_thread', LOAD_SEARCH_OPTIONS: 'load_search_options', diff --git a/src/libs/E2E/tests/linkingTest.e2e.ts b/src/libs/E2E/tests/linkingTest.e2e.ts index b517d91658b4..efe304a5cd12 100644 --- a/src/libs/E2E/tests/linkingTest.e2e.ts +++ b/src/libs/E2E/tests/linkingTest.e2e.ts @@ -1,13 +1,10 @@ import {DeviceEventEmitter} from 'react-native'; import type {NativeConfig} from 'react-native-config'; -import Config from 'react-native-config'; import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; -import Performance from '@libs/Performance'; -import CONST from '@src/CONST'; type ViewableItem = { reportActionID?: string; @@ -19,7 +16,6 @@ const test = (config: NativeConfig) => { console.debug('[E2E] Logging in for comment linking'); const linkedReportActionID = getConfigValueOrThrow('linkedReportActionID', config); - const name = getConfigValueOrThrow('name', config); E2ELogin().then((neededLogin) => { if (neededLogin) { @@ -27,7 +23,7 @@ const test = (config: NativeConfig) => { } const [appearMessagePromise, appearMessageResolve] = getPromiseWithResolve(); - const [switchReportPromise, switchReportResolve] = getPromiseWithResolve(); + const [switchReportPromise] = getPromiseWithResolve(); Promise.all([appearMessagePromise, switchReportPromise]) .then(() => { @@ -48,21 +44,6 @@ const test = (config: NativeConfig) => { console.debug(`[E2E] Provided message id '${res?.at(0)?.item?.reportActionID}' doesn't match to an expected '${linkedReportActionID}'. Waiting for a next one…`); } }); - - Performance.subscribeToMeasurements((entry) => { - if (entry.name === CONST.TIMING.SWITCH_REPORT) { - console.debug('[E2E] Linking: 1'); - - E2EClient.submitTestResults({ - branch: Config.E2E_BRANCH, - name, - metric: entry.duration, - unit: 'ms', - }); - - switchReportResolve(); - } - }); }); }; diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index 21a05f7567dc..c2e0fded42f5 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -6,7 +6,6 @@ import {InteractionManager} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; -import useInitialValue from '@hooks/useInitialValue'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -285,7 +284,6 @@ function ReportActionsView({ const hasMoreCached = reportActions.length < combinedReportActions.length; const newestReportAction = useMemo(() => reportActions?.at(0), [reportActions]); const mostRecentIOUReportActionID = useMemo(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActions), [reportActions]); - const hasCachedActionOnFirstRender = useInitialValue(() => reportActions.length > 0); const hasNewestReportAction = reportActions.at(0)?.created === report.lastVisibleActionCreated || reportActions.at(0)?.created === transactionThreadReport?.lastVisibleActionCreated; const oldestReportAction = useMemo(() => reportActions?.at(-1), [reportActions]); @@ -427,17 +425,11 @@ function ReportActionsView({ } didLayout.current = true; - // Capture the init measurement only once not per each chat switch as the value gets overwritten - if (!ReportActionsView.initMeasured) { - Performance.markEnd(CONST.TIMING.OPEN_REPORT); - ReportActionsView.initMeasured = true; - } else { - Performance.markEnd(CONST.TIMING.SWITCH_REPORT); - } - Timing.end(CONST.TIMING.SWITCH_REPORT, hasCachedActionOnFirstRender ? CONST.TIMING.WARM : CONST.TIMING.COLD); + + Performance.markEnd(CONST.TIMING.OPEN_REPORT); Timing.end(CONST.TIMING.OPEN_REPORT_THREAD); Timing.end(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW); - }, [hasCachedActionOnFirstRender]); + }, []); // Check if the first report action in the list is the one we're currently linked to const isTheFirstReportActionIsLinked = newestReportAction?.reportActionID === reportActionID; @@ -500,7 +492,6 @@ function ReportActionsView({ } ReportActionsView.displayName = 'ReportActionsView'; -ReportActionsView.initMeasured = false; function arePropsEqual(oldProps: ReportActionsViewProps, newProps: ReportActionsViewProps): boolean { if (!lodashIsEqual(oldProps.reportActions, newProps.reportActions)) { diff --git a/src/pages/home/sidebar/SidebarLinks.tsx b/src/pages/home/sidebar/SidebarLinks.tsx index e62f2525e70b..e2df0ff6f33a 100644 --- a/src/pages/home/sidebar/SidebarLinks.tsx +++ b/src/pages/home/sidebar/SidebarLinks.tsx @@ -17,9 +17,6 @@ import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; type SidebarLinksProps = { - /** Toggles the navigation menu open and closed */ - onLinkClick: () => void; - /** Safe area insets required for mobile devices margins */ insets: EdgeInsets; @@ -40,7 +37,7 @@ type SidebarLinksProps = { activeWorkspaceID: string | undefined; }; -function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport}: SidebarLinksProps) { +function SidebarLinks({insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport}: SidebarLinksProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {updateLocale} = useLocalize(); @@ -75,9 +72,8 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID)); - onLinkClick(); }, - [shouldUseNarrowLayout, isActiveReport, onLinkClick], + [shouldUseNarrowLayout, isActiveReport], ); const viewMode = priorityMode === CONST.PRIORITY_MODE.GSD ? CONST.OPTION_MODE.COMPACT : CONST.OPTION_MODE.DEFAULT; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 7dfbdbaf7299..931bfd6c0d66 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -24,14 +24,11 @@ type SidebarLinksDataOnyxProps = { }; type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { - /** Toggles the navigation menu open and closed */ - onLinkClick: () => void; - /** Safe area insets required for mobile devices margins */ insets: EdgeInsets; }; -function SidebarLinksData({insets, isLoadingApp = true, onLinkClick, priorityMode = CONST.PRIORITY_MODE.DEFAULT}: SidebarLinksDataProps) { +function SidebarLinksData({insets, isLoadingApp = true, priorityMode = CONST.PRIORITY_MODE.DEFAULT}: SidebarLinksDataProps) { const isFocused = useIsFocused(); const styles = useThemeStyles(); const activeWorkspaceID = useActiveWorkspaceFromNavigationState(); @@ -63,7 +60,6 @@ function SidebarLinksData({insets, isLoadingApp = true, onLinkClick, priorityMod > ({ initialValue: CONST.PRIORITY_MODE.DEFAULT, }, })( - /* + /* While working on audit on the App Start App metric we noticed that by memoizing SidebarLinksData we can avoid 2 additional run of getOrderedReportIDs. With that we can reduce app start up time by ~2s on heavy account. More details - https://github.com/Expensify/App/issues/35234#issuecomment-1926914534 */ memo( SidebarLinksData, - (prevProps, nextProps) => - prevProps.isLoadingApp === nextProps.isLoadingApp && - prevProps.priorityMode === nextProps.priorityMode && - lodashIsEqual(prevProps.insets, nextProps.insets) && - prevProps.onLinkClick === nextProps.onLinkClick, + (prevProps, nextProps) => prevProps.isLoadingApp === nextProps.isLoadingApp && prevProps.priorityMode === nextProps.priorityMode && lodashIsEqual(prevProps.insets, nextProps.insets), ), ); diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 3421d72270fc..93a608f2df22 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -10,20 +10,9 @@ import {updateLastAccessedWorkspace} from '@libs/actions/Policy/Policy'; import * as Browser from '@libs/Browser'; import TopBar from '@libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar'; import Navigation from '@libs/Navigation/Navigation'; -import Performance from '@libs/Performance'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; -import Timing from '@userActions/Timing'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -/** - * Function called when a pinned chat is selected. - */ -const startTimer = () => { - Timing.start(CONST.TIMING.SWITCH_REPORT); - Performance.markStart(CONST.TIMING.SWITCH_REPORT); -}; - function BaseSidebarScreen() { const styles = useThemeStyles(); const activeWorkspaceID = useActiveWorkspaceFromNavigationState(); @@ -58,10 +47,7 @@ function BaseSidebarScreen() { shouldDisplaySearch={shouldDisplaySearch} /> - + )} diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index ae9afd095d37..6fb767cfb3e3 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -251,7 +251,6 @@ function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { * */} {}} insets={{ top: 0, left: 0, From 6f0eabb1f91a8a3767670f83d6f9629bce900cde Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 31 Oct 2024 17:51:29 +0100 Subject: [PATCH 086/111] feat: start remote tracking the message_sent event --- contributingGuides/PERFORMANCE_METRICS.md | 2 +- .../home/report/ReportActionCompose/ReportActionCompose.tsx | 2 ++ src/pages/home/report/comment/TextCommentFragment.tsx | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index 36ffc6ee3b8d..89e7d10cee66 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -22,7 +22,7 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `open_report` | ❌ | Time taken to open a report.

**Platforms:** All | Starts when the row in the `LHNOptionsList` is pressed. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_from_preview` | ✅ | Time taken to open a report from preview.

(previously `switch_report_from_preview`)

**Platforms:** All | Starts when the user presses the Report Preview. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_thread` | ✅ | Time taken to open a thread in a report.

**Platforms:** All | Starts when user presses Report Action Item. | Stops when the `ReportActionsList` finishes laying out. | -| `message_sent` | ❌ | Time taken to send a message.

**Platforms:** All | Starts when the new message is sent. | Stops when the message is being rendered in the chat. | +| `message_sent` | ✅ | Time taken to send a message.

**Platforms:** All | Starts when the new message is sent. | Stops when the message is being rendered in the chat. | ## Documentation Maintenance diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 23b059f2fda2..8a6b88b8c25c 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -43,6 +43,7 @@ import ReportTypingIndicator from '@pages/home/report/ReportTypingIndicator'; import variables from '@styles/variables'; import * as EmojiPickerActions from '@userActions/EmojiPickerAction'; import * as Report from '@userActions/Report'; +import Timing from '@userActions/Timing'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -274,6 +275,7 @@ function ReportActionCompose({ attachmentFileRef.current = null; } else { Performance.markStart(CONST.TIMING.MESSAGE_SENT, {message: newCommentTrimmed}); + Timing.start(CONST.TIMING.MESSAGE_SENT); onSubmit(newCommentTrimmed); } }, diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx index 530acc46233d..94c8f46845c6 100644 --- a/src/pages/home/report/comment/TextCommentFragment.tsx +++ b/src/pages/home/report/comment/TextCommentFragment.tsx @@ -14,6 +14,7 @@ import * as EmojiUtils from '@libs/EmojiUtils'; import Performance from '@libs/Performance'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import variables from '@styles/variables'; +import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import type {OriginalMessageSource} from '@src/types/onyx/OriginalMessage'; import type {Message} from '@src/types/onyx/ReportAction'; @@ -53,6 +54,7 @@ function TextCommentFragment({fragment, styleAsDeleted, styleAsMuted = false, so useEffect(() => { Performance.markEnd(CONST.TIMING.MESSAGE_SENT, {message: text}); + Timing.end(CONST.TIMING.MESSAGE_SENT); }, [text]); // If the only difference between fragment.text and fragment.html is
tags and emoji tag From e09796c9694213a4ba98f2e63de0be0dd598041e Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 31 Oct 2024 17:54:30 +0100 Subject: [PATCH 087/111] feat: start remote tracking the open_report event --- contributingGuides/PERFORMANCE_METRICS.md | 2 +- src/components/LHNOptionsList/OptionRowLHN.tsx | 2 ++ src/pages/home/report/ReportActionsView.tsx | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index 89e7d10cee66..c7af6bae0223 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -19,7 +19,7 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `load_search_options` | ✅ | Time taken to generate the list of options used in Chat Finder.

**Platforms:** All | Starts when the `getSearchOptions` function is called. | Stops when the list of available options is generated. | | `search_filter_options` | ✅ | Time taken to filter search options in Chat Finder by given search value.

**Platforms:** All | Starts when user types something in the Chat Finder search input. | Stops when the list of filtered options is generated. | | `trie_initialization` | ✅ | Time taken to build the emoji trie.

**Platforms:** All | Starts when emoji trie begins to build. | Stops when emoji trie building is complete. | -| `open_report` | ❌ | Time taken to open a report.

**Platforms:** All | Starts when the row in the `LHNOptionsList` is pressed. | Stops when the `ReportActionsList` finishes laying out. | +| `open_report` | ✅ | Time taken to open a report.

**Platforms:** All | Starts when the row in the `LHNOptionsList` is pressed. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_from_preview` | ✅ | Time taken to open a report from preview.

(previously `switch_report_from_preview`)

**Platforms:** All | Starts when the user presses the Report Preview. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_thread` | ✅ | Time taken to open a thread in a report.

**Platforms:** All | Starts when user presses Report Action Item. | Stops when the `ReportActionsList` finishes laying out. | | `message_sent` | ✅ | Time taken to send a message.

**Platforms:** All | Starts when the new message is sent. | Stops when the message is being rendered in the chat. | diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 94116181bccb..3e3f4d1b8e5d 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -30,6 +30,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import FreeTrial from '@pages/settings/Subscription/FreeTrial'; import variables from '@styles/variables'; +import Timing from '@userActions/Timing'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -193,6 +194,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti ref={popoverAnchor} onPress={(event) => { Performance.markStart(CONST.TIMING.OPEN_REPORT); + Timing.start(CONST.TIMING.OPEN_REPORT); event?.preventDefault(); // Enable Composer to focus on clicking the same chat after opening the context menu. diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index c2e0fded42f5..7b515c060205 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -427,6 +427,7 @@ function ReportActionsView({ didLayout.current = true; Performance.markEnd(CONST.TIMING.OPEN_REPORT); + Timing.end(CONST.TIMING.OPEN_REPORT); Timing.end(CONST.TIMING.OPEN_REPORT_THREAD); Timing.end(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW); }, []); From 5f27a44d56dca7843ac216dfe0576ef5d69b6bde Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 4 Nov 2024 14:17:32 +0100 Subject: [PATCH 088/111] feat: update docs & impl for the open_search event --- contributingGuides/PERFORMANCE_METRICS.md | 2 +- src/CONST.ts | 2 +- src/components/Search/SearchRouter/SearchButton.tsx | 4 ++-- src/components/Search/SearchRouter/SearchRouterList.tsx | 4 ++-- src/libs/E2E/tests/openSearchRouterTest.e2e.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index c7af6bae0223..6c72d08a65c0 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -15,7 +15,7 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `_app_in_foreground` | ✅ | The time when the app is running in the foreground and available to the user.

**Platforms:** Android, iOS | **Android:** Starts when the first activity to reach the foreground has its `onResume()` method called.

**iOS:** Starts when the application receives the `UIApplicationDidBecomeActiveNotification` notification. | **Android:** Stops when the last activity to leave the foreground has its `onStop()` method called.

**iOS:** Stops when it receives the `UIApplicationWillResignActiveNotification` notification. | | `_app_in_background` | ✅ | Time when the app is running in the background.

**Platforms:** Android, iOS | **Android:** Starts when the last activity to leave the foreground has its `onStop()` method called.

**iOS:** Starts when the application receives the `UIApplicationWillResignActiveNotification` notification. | **Android:** Stops when the first activity to reach the foreground has its `onResume()` method called.

**iOS:** Stops when it receives the `UIApplicationDidBecomeActiveNotification` notification. | | `calc_most_recent_last_modified_action` | ✅ | Time taken to find the most recently modified report action or report.

**Platforms:** All | Starts when the app reconnects to the network | Ends when the app reconnects to the network and the most recent report action or report is found. | -| `search_render` | ✅ | Time taken to render the Chat Finder page.

**Platforms:** All | Starts when the Chat Finder icon in LHN is pressed. | Stops when the list of available options is rendered for the first time. | +| `open_search` | ✅ | Time taken to open up the Search Router.

**Platforms:** All | Starts when the Search Router icon in LHN is pressed. | Stops when the list of available options finishes laying out. | | `load_search_options` | ✅ | Time taken to generate the list of options used in Chat Finder.

**Platforms:** All | Starts when the `getSearchOptions` function is called. | Stops when the list of available options is generated. | | `search_filter_options` | ✅ | Time taken to filter search options in Chat Finder by given search value.

**Platforms:** All | Starts when user types something in the Chat Finder search input. | Stops when the list of filtered options is generated. | | `trie_initialization` | ✅ | Time taken to build the emoji trie.

**Platforms:** All | Starts when emoji trie begins to build. | Stops when emoji trie building is complete. | diff --git a/src/CONST.ts b/src/CONST.ts index c49f92e9d1ef..7694e0a91488 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1253,7 +1253,7 @@ const CONST = { }, TIMING: { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', - SEARCH_ROUTER_RENDER: 'search_router_render', + OPEN_SEARCH: 'open_search', OPEN_REPORT: 'open_report', OPEN_REPORT_FROM_PREVIEW: 'open_report_from_preview', OPEN_REPORT_THREAD: 'open_report_thread', diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index 76eacd8b991d..90699e951998 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -30,8 +30,8 @@ function SearchButton({style}: SearchButtonProps) { accessibilityLabel={translate('common.search')} style={[styles.flexRow, styles.touchableButtonImage, style]} onPress={Session.checkIfActionIsAllowed(() => { - Timing.start(CONST.TIMING.SEARCH_ROUTER_RENDER); - Performance.markStart(CONST.TIMING.SEARCH_ROUTER_RENDER); + Timing.start(CONST.TIMING.OPEN_SEARCH); + Performance.markStart(CONST.TIMING.OPEN_SEARCH); openSearchRouter(); })} diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index cc854ff926c3..45e30a6bad6d 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -69,8 +69,8 @@ type SearchRouterListProps = { }; const setPerformanceTimersEnd = () => { - Timing.end(CONST.TIMING.SEARCH_ROUTER_RENDER); - Performance.markEnd(CONST.TIMING.SEARCH_ROUTER_RENDER); + Timing.end(CONST.TIMING.OPEN_SEARCH); + Performance.markEnd(CONST.TIMING.OPEN_SEARCH); }; function getContextualSearchQuery(reportName: string) { diff --git a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts index f3741f2e2b53..d1fd27b9bac7 100644 --- a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts +++ b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts @@ -36,7 +36,7 @@ const test = (config: NativeConfig) => { Performance.subscribeToMeasurements((entry) => { console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); - if (entry.name === CONST.TIMING.SEARCH_ROUTER_RENDER) { + if (entry.name === CONST.TIMING.OPEN_SEARCH) { E2EClient.submitTestResults({ branch: Config.E2E_BRANCH, name: `${name} Open Search Router TTI`, From c7a661a54112aed48d2effff270f02c0d86780e4 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 4 Nov 2024 14:24:01 +0100 Subject: [PATCH 089/111] docs: update descriptions for the search_filter_options and load_filter_options events --- contributingGuides/PERFORMANCE_METRICS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index 6c72d08a65c0..d45b7c2e625c 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -16,8 +16,8 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `_app_in_background` | ✅ | Time when the app is running in the background.

**Platforms:** Android, iOS | **Android:** Starts when the last activity to leave the foreground has its `onStop()` method called.

**iOS:** Starts when the application receives the `UIApplicationWillResignActiveNotification` notification. | **Android:** Stops when the first activity to reach the foreground has its `onResume()` method called.

**iOS:** Stops when it receives the `UIApplicationDidBecomeActiveNotification` notification. | | `calc_most_recent_last_modified_action` | ✅ | Time taken to find the most recently modified report action or report.

**Platforms:** All | Starts when the app reconnects to the network | Ends when the app reconnects to the network and the most recent report action or report is found. | | `open_search` | ✅ | Time taken to open up the Search Router.

**Platforms:** All | Starts when the Search Router icon in LHN is pressed. | Stops when the list of available options finishes laying out. | -| `load_search_options` | ✅ | Time taken to generate the list of options used in Chat Finder.

**Platforms:** All | Starts when the `getSearchOptions` function is called. | Stops when the list of available options is generated. | -| `search_filter_options` | ✅ | Time taken to filter search options in Chat Finder by given search value.

**Platforms:** All | Starts when user types something in the Chat Finder search input. | Stops when the list of filtered options is generated. | +| `load_search_options` | ✅ | Time taken to generate the list of options used in the Search Router.

**Platforms:** All | Starts when the `getSearchOptions` function is called. | Stops when the list of available options is generated. | +| `search_filter_options` | ✅ | Time taken to filter search options in the Search Router by the given search value.

**Platforms:** All | Starts when user types something in the Search Router search input. | Stops when the list of filtered options is generated. | | `trie_initialization` | ✅ | Time taken to build the emoji trie.

**Platforms:** All | Starts when emoji trie begins to build. | Stops when emoji trie building is complete. | | `open_report` | ✅ | Time taken to open a report.

**Platforms:** All | Starts when the row in the `LHNOptionsList` is pressed. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_from_preview` | ✅ | Time taken to open a report from preview.

(previously `switch_report_from_preview`)

**Platforms:** All | Starts when the user presses the Report Preview. | Stops when the `ReportActionsList` finishes laying out. | From c1a0d42642381b4cd19eb4a43d2ce024cffd7549 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Tue, 5 Nov 2024 15:31:29 +0100 Subject: [PATCH 090/111] Revert "chore: remove the sidebar_loaded event" This reverts commit f50765a6c83b37b1477f7d9edbe232e52792783c. --- contributingGuides/PERFORMANCE_METRICS.md | 1 + src/CONST.ts | 1 + src/Expensify.tsx | 1 + src/libs/E2E/tests/chatOpeningTest.e2e.ts | 10 ++++++++ src/libs/E2E/tests/linkingTest.e2e.ts | 13 ++++++++++ .../E2E/tests/openSearchRouterTest.e2e.ts | 24 +++++++++++++++++++ src/libs/E2E/tests/reportTypingTest.e2e.ts | 4 ++++ src/libs/Performance.tsx | 4 ++++ .../SidebarScreen/BaseSidebarScreen.tsx | 5 ++++ 9 files changed, 63 insertions(+) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index d45b7c2e625c..4412de96a472 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -14,6 +14,7 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `js_loaded` | ✅ | The time it takes for the JavaScript bundle to load.

**Platforms:** Android, iOS | **Android:** Starts in the `onCreate` method.

**iOS:** Starts in the AppDelegate's `didFinishLaunchingWithOptions` method. | Stops at the first render of the app via native module on the JS side. | | `_app_in_foreground` | ✅ | The time when the app is running in the foreground and available to the user.

**Platforms:** Android, iOS | **Android:** Starts when the first activity to reach the foreground has its `onResume()` method called.

**iOS:** Starts when the application receives the `UIApplicationDidBecomeActiveNotification` notification. | **Android:** Stops when the last activity to leave the foreground has its `onStop()` method called.

**iOS:** Stops when it receives the `UIApplicationWillResignActiveNotification` notification. | | `_app_in_background` | ✅ | Time when the app is running in the background.

**Platforms:** Android, iOS | **Android:** Starts when the last activity to leave the foreground has its `onStop()` method called.

**iOS:** Starts when the application receives the `UIApplicationWillResignActiveNotification` notification. | **Android:** Stops when the first activity to reach the foreground has its `onResume()` method called.

**iOS:** Stops when it receives the `UIApplicationDidBecomeActiveNotification` notification. | +| `sidebar_loaded` | ❌ | Time taken for the Sidebar to load.

**Platforms:** All | Starts when the Sidebar is mounted. | Stops when the Splash Screen is hidden. | | `calc_most_recent_last_modified_action` | ✅ | Time taken to find the most recently modified report action or report.

**Platforms:** All | Starts when the app reconnects to the network | Ends when the app reconnects to the network and the most recent report action or report is found. | | `open_search` | ✅ | Time taken to open up the Search Router.

**Platforms:** All | Starts when the Search Router icon in LHN is pressed. | Stops when the list of available options finishes laying out. | | `load_search_options` | ✅ | Time taken to generate the list of options used in the Search Router.

**Platforms:** All | Starts when the `getSearchOptions` function is called. | Stops when the list of available options is generated. | diff --git a/src/CONST.ts b/src/CONST.ts index 7694e0a91488..2fdef3e7a3d0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1257,6 +1257,7 @@ const CONST = { OPEN_REPORT: 'open_report', OPEN_REPORT_FROM_PREVIEW: 'open_report_from_preview', OPEN_REPORT_THREAD: 'open_report_thread', + SIDEBAR_LOADED: 'sidebar_loaded', LOAD_SEARCH_OPTIONS: 'load_search_options', MESSAGE_SENT: 'message_sent', COLD: 'cold', diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 40e937af2d1b..e07b03a6d405 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -138,6 +138,7 @@ function Expensify() { const onSplashHide = useCallback(() => { setSplashScreenState(CONST.BOOT_SPLASH_STATE.HIDDEN); + Performance.markEnd(CONST.TIMING.SIDEBAR_LOADED); }, [setSplashScreenState]); useLayoutEffect(() => { diff --git a/src/libs/E2E/tests/chatOpeningTest.e2e.ts b/src/libs/E2E/tests/chatOpeningTest.e2e.ts index f7ec35018028..40e5d2677090 100644 --- a/src/libs/E2E/tests/chatOpeningTest.e2e.ts +++ b/src/libs/E2E/tests/chatOpeningTest.e2e.ts @@ -5,13 +5,16 @@ import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; +import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; const test = (config: NativeConfig) => { // check for login (if already logged in the action will simply resolve) console.debug('[E2E] Logging in for chat opening'); + const reportID = getConfigValueOrThrow('reportID', config); const name = getConfigValueOrThrow('name', config); E2ELogin().then((neededLogin) => { @@ -34,6 +37,13 @@ const test = (config: NativeConfig) => { }); Performance.subscribeToMeasurements((entry) => { + if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { + console.debug(`[E2E] Sidebar loaded, navigating to report…`); + Performance.markStart(CONST.TIMING.OPEN_REPORT); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); + return; + } + console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); if (entry.name === CONST.TIMING.OPEN_REPORT) { diff --git a/src/libs/E2E/tests/linkingTest.e2e.ts b/src/libs/E2E/tests/linkingTest.e2e.ts index efe304a5cd12..00238858dc54 100644 --- a/src/libs/E2E/tests/linkingTest.e2e.ts +++ b/src/libs/E2E/tests/linkingTest.e2e.ts @@ -5,6 +5,10 @@ import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; +import Navigation from '@libs/Navigation/Navigation'; +import Performance from '@libs/Performance'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; type ViewableItem = { reportActionID?: string; @@ -15,6 +19,7 @@ type ViewableItemResponse = Array<{item?: ViewableItem}>; const test = (config: NativeConfig) => { console.debug('[E2E] Logging in for comment linking'); + const reportID = getConfigValueOrThrow('reportID', config); const linkedReportActionID = getConfigValueOrThrow('linkedReportActionID', config); E2ELogin().then((neededLogin) => { @@ -44,6 +49,14 @@ const test = (config: NativeConfig) => { console.debug(`[E2E] Provided message id '${res?.at(0)?.item?.reportActionID}' doesn't match to an expected '${linkedReportActionID}'. Waiting for a next one…`); } }); + + Performance.subscribeToMeasurements((entry) => { + if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { + console.debug('[E2E] Sidebar loaded, navigating to a report…'); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); + return; + } + }); }); }; diff --git a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts index d1fd27b9bac7..4fd2b26e63c8 100644 --- a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts +++ b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts @@ -1,5 +1,6 @@ import Config from 'react-native-config'; import type {NativeConfig} from 'react-native-config'; +import * as E2EGenericPressableWrapper from '@components/Pressable/GenericPressable/index.e2e'; import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; @@ -35,6 +36,29 @@ const test = (config: NativeConfig) => { Performance.subscribeToMeasurements((entry) => { console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); + if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { + const props = E2EGenericPressableWrapper.getPressableProps('searchButton'); + if (!props) { + console.debug('[E2E] Search button not found, failing test!'); + E2EClient.submitTestResults({ + branch: Config.E2E_BRANCH, + error: 'Search button not found', + name: `${name} Open Search Router TTI`, + }).then(() => E2EClient.submitTestDone()); + return; + } + if (!props.onPress) { + console.debug('[E2E] Search button found but onPress prop was not present, failing test!'); + E2EClient.submitTestResults({ + branch: Config.E2E_BRANCH, + error: 'Search button found but onPress prop was not present', + name: `${name} Open Search Router TTI`, + }).then(() => E2EClient.submitTestDone()); + return; + } + // Open the search router + props.onPress(); + } if (entry.name === CONST.TIMING.OPEN_SEARCH) { E2EClient.submitTestResults({ diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.ts b/src/libs/E2E/tests/reportTypingTest.e2e.ts index f25859194541..e042a688c37d 100644 --- a/src/libs/E2E/tests/reportTypingTest.e2e.ts +++ b/src/libs/E2E/tests/reportTypingTest.e2e.ts @@ -53,6 +53,10 @@ const test = (config: NativeConfig) => { return; } + if (entry.name !== CONST.TIMING.SIDEBAR_LOADED) { + return; + } + console.debug(`[E2E] Sidebar loaded, navigating to a report…`); // Crowded Policy (Do Not Delete) Report, has a input bar available: Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); diff --git a/src/libs/Performance.tsx b/src/libs/Performance.tsx index 6d9319276f9a..ef2b08e47229 100644 --- a/src/libs/Performance.tsx +++ b/src/libs/Performance.tsx @@ -160,6 +160,10 @@ if (Metrics.canCapturePerformanceMetrics()) { } // Capture any custom measures or metrics below + if (mark.name === `${CONST.TIMING.SIDEBAR_LOADED}_end`) { + Performance.measureFailSafe('contentAppeared_To_screenTTI', 'contentAppeared', mark.name); + Performance.measureTTI(mark.name); + } }); }).observe({type: 'mark', buffered: true}); }; diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 93a608f2df22..6f8b2b71fe53 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -20,6 +20,11 @@ function BaseSidebarScreen() { const {shouldUseNarrowLayout} = useResponsiveLayout(); const [activeWorkspace] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID ?? -1}`); + useEffect(() => { + Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); + Timing.start(CONST.TIMING.SIDEBAR_LOADED); + }, []); + useEffect(() => { if (!!activeWorkspace || activeWorkspaceID === undefined) { return; From 0d3cf61fa7777df1b832f415f5f2655e5fb7d8c8 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Tue, 5 Nov 2024 15:35:10 +0100 Subject: [PATCH 091/111] fix: imports --- src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 6f8b2b71fe53..0dcb1124ee3e 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -10,7 +10,10 @@ import {updateLastAccessedWorkspace} from '@libs/actions/Policy/Policy'; import * as Browser from '@libs/Browser'; import TopBar from '@libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar'; import Navigation from '@libs/Navigation/Navigation'; +import Performance from '@libs/Performance'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; +import Timing from '@userActions/Timing'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; function BaseSidebarScreen() { From 2e7f51aac66347ddc9ddf9e331cfb4fd9fc0ceee Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Tue, 5 Nov 2024 15:57:29 +0100 Subject: [PATCH 092/111] chore: clear imports linter checks --- src/libs/actions/App.ts | 1 - src/pages/home/ReportScreen.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index 7fe1745285fb..f304c4522796 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -14,7 +14,6 @@ import {buildEmojisTrie} from '@libs/EmojiTrie'; import Log from '@libs/Log'; import getCurrentUrl from '@libs/Navigation/currentUrl'; import Navigation from '@libs/Navigation/Navigation'; -import Performance from '@libs/Performance'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as SessionUtils from '@libs/SessionUtils'; import {clearSoundAssetsCache} from '@libs/Sound'; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index cbc5b72730b5..62fd04f7572a 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -29,11 +29,9 @@ import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useViewportOffsetTop from '@hooks/useViewportOffsetTop'; -import Timing from '@libs/actions/Timing'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import clearReportNotifications from '@libs/Notification/clearReportNotifications'; -import Performance from '@libs/Performance'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; From 8c3da544b42a49a88a6d74601fa7757f1c11692d Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Tue, 5 Nov 2024 16:01:07 +0100 Subject: [PATCH 093/111] chore: rm redundant promise unwrap for removed chat_render --- src/libs/E2E/tests/chatOpeningTest.e2e.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/E2E/tests/chatOpeningTest.e2e.ts b/src/libs/E2E/tests/chatOpeningTest.e2e.ts index 40e5d2677090..62a01e43755d 100644 --- a/src/libs/E2E/tests/chatOpeningTest.e2e.ts +++ b/src/libs/E2E/tests/chatOpeningTest.e2e.ts @@ -27,10 +27,9 @@ const test = (config: NativeConfig) => { console.debug('[E2E] Logged in, getting chat opening metrics and submitting them…'); - const [renderChatPromise] = getPromiseWithResolve(); const [chatTTIPromise, chatTTIResolve] = getPromiseWithResolve(); - Promise.all([renderChatPromise, chatTTIPromise]).then(() => { + chatTTIPromise.then(() => { console.debug(`[E2E] Submitting!`); E2EClient.submitTestDone(); From ee03b3dfee9cc40bbdfff14ec8954ff4d86afea9 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Tue, 5 Nov 2024 16:38:02 +0100 Subject: [PATCH 094/111] fix: linking test for chats --- src/libs/E2E/tests/linkingTest.e2e.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/libs/E2E/tests/linkingTest.e2e.ts b/src/libs/E2E/tests/linkingTest.e2e.ts index 00238858dc54..f9c4b5ae33b4 100644 --- a/src/libs/E2E/tests/linkingTest.e2e.ts +++ b/src/libs/E2E/tests/linkingTest.e2e.ts @@ -1,4 +1,5 @@ import {DeviceEventEmitter} from 'react-native'; +import Config from 'react-native-config'; import type {NativeConfig} from 'react-native-config'; import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; @@ -21,6 +22,7 @@ const test = (config: NativeConfig) => { const reportID = getConfigValueOrThrow('reportID', config); const linkedReportActionID = getConfigValueOrThrow('linkedReportActionID', config); + const name = getConfigValueOrThrow('name', config); E2ELogin().then((neededLogin) => { if (neededLogin) { @@ -28,9 +30,9 @@ const test = (config: NativeConfig) => { } const [appearMessagePromise, appearMessageResolve] = getPromiseWithResolve(); - const [switchReportPromise] = getPromiseWithResolve(); + const [openReportPromise, openReportResolve] = getPromiseWithResolve(); - Promise.all([appearMessagePromise, switchReportPromise]) + Promise.all([appearMessagePromise, openReportPromise]) .then(() => { console.debug('[E2E] Test completed successfully, exiting…'); E2EClient.submitTestDone(); @@ -56,6 +58,18 @@ const test = (config: NativeConfig) => { Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); return; } + + if (entry.name === CONST.TIMING.OPEN_REPORT) { + console.debug('[E2E] Linking: 1'); + + E2EClient.submitTestResults({ + branch: Config.E2E_BRANCH, + name, + metric: entry.duration, + unit: 'ms', + }); + openReportResolve(); + } }); }); }; From 903f2c430b37615ae97d2cf83396c3f46e59b36d Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Tue, 5 Nov 2024 16:45:12 +0100 Subject: [PATCH 095/111] chore: empty line --- src/libs/E2E/tests/linkingTest.e2e.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/E2E/tests/linkingTest.e2e.ts b/src/libs/E2E/tests/linkingTest.e2e.ts index f9c4b5ae33b4..6488d0c8bc70 100644 --- a/src/libs/E2E/tests/linkingTest.e2e.ts +++ b/src/libs/E2E/tests/linkingTest.e2e.ts @@ -68,6 +68,7 @@ const test = (config: NativeConfig) => { metric: entry.duration, unit: 'ms', }); + openReportResolve(); } }); From 82d58be7468147d0f3c6698ece42689b0f9ebd92 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Tue, 5 Nov 2024 17:44:29 +0100 Subject: [PATCH 096/111] feat: do not start the sidebar_loaded event --- src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index 0dcb1124ee3e..b3812ca4a9cb 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -25,7 +25,6 @@ function BaseSidebarScreen() { useEffect(() => { Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); - Timing.start(CONST.TIMING.SIDEBAR_LOADED); }, []); useEffect(() => { From b0cc39264b3a28a12841be82a137a4e926637d65 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Wed, 6 Nov 2024 10:51:42 +0100 Subject: [PATCH 097/111] chore: rm redundant timing module import --- src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index b3812ca4a9cb..057189ae22c1 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -12,7 +12,6 @@ import TopBar from '@libs/Navigation/AppNavigator/createCustomBottomTabNavigator import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; -import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; From eb7f2db245d587a59df0d1a8d27bde494d07652b Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 8 Nov 2024 17:31:41 +0100 Subject: [PATCH 098/111] fix: linking test assertion scenario --- src/libs/E2E/tests/linkingTest.e2e.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/E2E/tests/linkingTest.e2e.ts b/src/libs/E2E/tests/linkingTest.e2e.ts index 6488d0c8bc70..2a85a5dabe6c 100644 --- a/src/libs/E2E/tests/linkingTest.e2e.ts +++ b/src/libs/E2E/tests/linkingTest.e2e.ts @@ -21,6 +21,7 @@ const test = (config: NativeConfig) => { console.debug('[E2E] Logging in for comment linking'); const reportID = getConfigValueOrThrow('reportID', config); + const linkedReportID = getConfigValueOrThrow('linkedReportID', config); const linkedReportActionID = getConfigValueOrThrow('linkedReportActionID', config); const name = getConfigValueOrThrow('name', config); @@ -55,12 +56,15 @@ const test = (config: NativeConfig) => { Performance.subscribeToMeasurements((entry) => { if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { console.debug('[E2E] Sidebar loaded, navigating to a report…'); + Performance.markStart(CONST.TIMING.OPEN_REPORT); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); return; } if (entry.name === CONST.TIMING.OPEN_REPORT) { console.debug('[E2E] Linking: 1'); + console.debug('[E2E] Navigating to the linked report action…'); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(linkedReportID, linkedReportActionID)); E2EClient.submitTestResults({ branch: Config.E2E_BRANCH, From 291dc29cff49cc20bccf2398e6b6ec4286069ef0 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 8 Nov 2024 17:43:48 +0100 Subject: [PATCH 099/111] feat: add missing performance markers, rename message_sent to send_message --- src/CONST.ts | 2 +- src/components/ReportActionItem/ReportPreview.tsx | 2 ++ src/libs/E2E/tests/reportTypingTest.e2e.ts | 2 +- .../home/report/ReportActionCompose/ReportActionCompose.tsx | 4 ++-- src/pages/home/report/ReportActionItemThread.tsx | 4 +++- src/pages/home/report/ReportActionsView.tsx | 4 ++++ src/pages/home/report/comment/TextCommentFragment.tsx | 4 ++-- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 2fdef3e7a3d0..875539a19e4d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1259,7 +1259,7 @@ const CONST = { OPEN_REPORT_THREAD: 'open_report_thread', SIDEBAR_LOADED: 'sidebar_loaded', LOAD_SEARCH_OPTIONS: 'load_search_options', - MESSAGE_SENT: 'message_sent', + SEND_MESSAGE: 'send_message', COLD: 'cold', WARM: 'warm', REPORT_ACTION_ITEM_LAYOUT_DEBOUNCE_TIME: 1500, diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 9067f1abb11a..d476d1198808 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -27,6 +27,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import HapticFeedback from '@libs/HapticFeedback'; import Navigation from '@libs/Navigation/Navigation'; +import Performance from '@libs/Performance'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -456,6 +457,7 @@ function ReportPreview({ { + Performance.markStart(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW); Timing.start(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(iouReportID)); }} diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.ts b/src/libs/E2E/tests/reportTypingTest.e2e.ts index e042a688c37d..473bf317e6c0 100644 --- a/src/libs/E2E/tests/reportTypingTest.e2e.ts +++ b/src/libs/E2E/tests/reportTypingTest.e2e.ts @@ -43,7 +43,7 @@ const test = (config: NativeConfig) => { }); Performance.subscribeToMeasurements((entry) => { - if (entry.name === CONST.TIMING.MESSAGE_SENT) { + if (entry.name === CONST.TIMING.SEND_MESSAGE) { E2EClient.submitTestResults({ branch: Config.E2E_BRANCH, name: `${name} Message sent`, diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 8a6b88b8c25c..4285916b593d 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -274,8 +274,8 @@ function ReportActionCompose({ Report.addAttachment(reportID, attachmentFileRef.current, newCommentTrimmed); attachmentFileRef.current = null; } else { - Performance.markStart(CONST.TIMING.MESSAGE_SENT, {message: newCommentTrimmed}); - Timing.start(CONST.TIMING.MESSAGE_SENT); + Performance.markStart(CONST.TIMING.SEND_MESSAGE, {message: newCommentTrimmed}); + Timing.start(CONST.TIMING.SEND_MESSAGE); onSubmit(newCommentTrimmed); } }, diff --git a/src/pages/home/report/ReportActionItemThread.tsx b/src/pages/home/report/ReportActionItemThread.tsx index 94a8592d9607..13072a653749 100644 --- a/src/pages/home/report/ReportActionItemThread.tsx +++ b/src/pages/home/report/ReportActionItemThread.tsx @@ -7,6 +7,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Timing from '@libs/actions/Timing'; +import Performance from '@libs/Performance'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import type {Icon} from '@src/types/onyx/OnyxCommon'; @@ -45,8 +46,9 @@ function ReportActionItemThread({numberOfReplies, icons, mostRecentReply, childR { - Report.navigateToAndOpenChildReport(childReportID); + Performance.markStart(CONST.TIMING.OPEN_REPORT_THREAD); Timing.start(CONST.TIMING.OPEN_REPORT_THREAD); + Report.navigateToAndOpenChildReport(childReportID); }} role={CONST.ROLE.BUTTON} accessibilityLabel={`${numberOfReplies} ${replyText}`} diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index 7b515c060205..31c0bc0fa752 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -428,7 +428,11 @@ function ReportActionsView({ Performance.markEnd(CONST.TIMING.OPEN_REPORT); Timing.end(CONST.TIMING.OPEN_REPORT); + + Performance.markEnd(CONST.TIMING.OPEN_REPORT_THREAD); Timing.end(CONST.TIMING.OPEN_REPORT_THREAD); + + Performance.markEnd(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW); Timing.end(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW); }, []); diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx index 94c8f46845c6..ab06a594a17f 100644 --- a/src/pages/home/report/comment/TextCommentFragment.tsx +++ b/src/pages/home/report/comment/TextCommentFragment.tsx @@ -53,8 +53,8 @@ function TextCommentFragment({fragment, styleAsDeleted, styleAsMuted = false, so const {shouldUseNarrowLayout} = useResponsiveLayout(); useEffect(() => { - Performance.markEnd(CONST.TIMING.MESSAGE_SENT, {message: text}); - Timing.end(CONST.TIMING.MESSAGE_SENT); + Performance.markEnd(CONST.TIMING.SEND_MESSAGE, {message: text}); + Timing.end(CONST.TIMING.SEND_MESSAGE); }, [text]); // If the only difference between fragment.text and fragment.html is
tags and emoji tag From 669e3a085b02233e9b52c50da1526c7dd4d1f901 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 8 Nov 2024 17:53:13 +0100 Subject: [PATCH 100/111] chore: rm redundant timing module import --- src/libs/Navigation/AppNavigator/AuthScreens.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index d7f3cd2e80cc..a061d5b52d22 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -43,7 +43,6 @@ import * as PriorityMode from '@userActions/PriorityMode'; import * as Report from '@userActions/Report'; import * as Session from '@userActions/Session'; import toggleTestToolsModal from '@userActions/TestTool'; -import Timing from '@userActions/Timing'; import * as User from '@userActions/User'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; From 216db097bcae11f55b238b6dd4909d2977b84030 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 8 Nov 2024 17:54:33 +0100 Subject: [PATCH 101/111] docs: rename message_sent to send_message --- contributingGuides/PERFORMANCE_METRICS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index 4412de96a472..2ab652aad055 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -23,7 +23,7 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `open_report` | ✅ | Time taken to open a report.

**Platforms:** All | Starts when the row in the `LHNOptionsList` is pressed. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_from_preview` | ✅ | Time taken to open a report from preview.

(previously `switch_report_from_preview`)

**Platforms:** All | Starts when the user presses the Report Preview. | Stops when the `ReportActionsList` finishes laying out. | | `open_report_thread` | ✅ | Time taken to open a thread in a report.

**Platforms:** All | Starts when user presses Report Action Item. | Stops when the `ReportActionsList` finishes laying out. | -| `message_sent` | ✅ | Time taken to send a message.

**Platforms:** All | Starts when the new message is sent. | Stops when the message is being rendered in the chat. | +| `send_message` | ✅ | Time taken to send a message.

**Platforms:** All | Starts when the new message is sent. | Stops when the message is being rendered in the chat. | ## Documentation Maintenance From 11ff391035828260fc004e324d7195a6755db1c7 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Tue, 12 Nov 2024 15:02:50 +0100 Subject: [PATCH 102/111] fix: sidebar_loaded end event trigger & docs --- contributingGuides/PERFORMANCE_METRICS.md | 2 +- src/Expensify.tsx | 2 -- src/libs/actions/App.ts | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contributingGuides/PERFORMANCE_METRICS.md b/contributingGuides/PERFORMANCE_METRICS.md index 2ab652aad055..ecebbaae4e0e 100644 --- a/contributingGuides/PERFORMANCE_METRICS.md +++ b/contributingGuides/PERFORMANCE_METRICS.md @@ -14,7 +14,7 @@ Project is using Firebase for tracking these metrics. However, not all of them a | `js_loaded` | ✅ | The time it takes for the JavaScript bundle to load.

**Platforms:** Android, iOS | **Android:** Starts in the `onCreate` method.

**iOS:** Starts in the AppDelegate's `didFinishLaunchingWithOptions` method. | Stops at the first render of the app via native module on the JS side. | | `_app_in_foreground` | ✅ | The time when the app is running in the foreground and available to the user.

**Platforms:** Android, iOS | **Android:** Starts when the first activity to reach the foreground has its `onResume()` method called.

**iOS:** Starts when the application receives the `UIApplicationDidBecomeActiveNotification` notification. | **Android:** Stops when the last activity to leave the foreground has its `onStop()` method called.

**iOS:** Stops when it receives the `UIApplicationWillResignActiveNotification` notification. | | `_app_in_background` | ✅ | Time when the app is running in the background.

**Platforms:** Android, iOS | **Android:** Starts when the last activity to leave the foreground has its `onStop()` method called.

**iOS:** Starts when the application receives the `UIApplicationWillResignActiveNotification` notification. | **Android:** Stops when the first activity to reach the foreground has its `onResume()` method called.

**iOS:** Stops when it receives the `UIApplicationDidBecomeActiveNotification` notification. | -| `sidebar_loaded` | ❌ | Time taken for the Sidebar to load.

**Platforms:** All | Starts when the Sidebar is mounted. | Stops when the Splash Screen is hidden. | +| `sidebar_loaded` | ❌ | Time taken for the Sidebar to load.

**Platforms:** All | Starts when the Sidebar is mounted. | Stops when the LHN finishes laying out. | | `calc_most_recent_last_modified_action` | ✅ | Time taken to find the most recently modified report action or report.

**Platforms:** All | Starts when the app reconnects to the network | Ends when the app reconnects to the network and the most recent report action or report is found. | | `open_search` | ✅ | Time taken to open up the Search Router.

**Platforms:** All | Starts when the Search Router icon in LHN is pressed. | Stops when the list of available options finishes laying out. | | `load_search_options` | ✅ | Time taken to generate the list of options used in the Search Router.

**Platforms:** All | Starts when the `getSearchOptions` function is called. | Stops when the list of available options is generated. | diff --git a/src/Expensify.tsx b/src/Expensify.tsx index e07b03a6d405..1d0100add00f 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -30,7 +30,6 @@ import NavigationRoot from './libs/Navigation/NavigationRoot'; import NetworkConnection from './libs/NetworkConnection'; import PushNotification from './libs/Notification/PushNotification'; import './libs/Notification/PushNotification/subscribePushNotification'; -import Performance from './libs/Performance'; import setCrashlyticsUserId from './libs/setCrashlyticsUserId'; import StartupTimer from './libs/StartupTimer'; // This lib needs to be imported, but it has nothing to export since all it contains is an Onyx connection @@ -138,7 +137,6 @@ function Expensify() { const onSplashHide = useCallback(() => { setSplashScreenState(CONST.BOOT_SPLASH_STATE.HIDDEN); - Performance.markEnd(CONST.TIMING.SIDEBAR_LOADED); }, [setSplashScreenState]); useLayoutEffect(() => { diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index f304c4522796..f778405ee6e8 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -14,6 +14,7 @@ import {buildEmojisTrie} from '@libs/EmojiTrie'; import Log from '@libs/Log'; import getCurrentUrl from '@libs/Navigation/currentUrl'; import Navigation from '@libs/Navigation/Navigation'; +import Performance from '@libs/Performance'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as SessionUtils from '@libs/SessionUtils'; import {clearSoundAssetsCache} from '@libs/Sound'; @@ -178,6 +179,7 @@ function setSidebarLoaded() { } Onyx.set(ONYXKEYS.IS_SIDEBAR_LOADED, true); + Performance.markEnd(CONST.TIMING.SIDEBAR_LOADED); } let appState: AppStateStatus; From 72879a3326a3b306a0845783e01155841225954d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 12 Nov 2024 22:49:48 +0530 Subject: [PATCH 103/111] unit tests --- tests/actions/PolicyTest.ts | 17 +++++++++++++++++ tests/utils/collections/policies.ts | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 2ede9f5e5228..fd88e75e01d3 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -6,6 +6,7 @@ import * as Policy from '@src/libs/actions/Policy/Policy'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy as PolicyType, Report, ReportAction, ReportActions} from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/Report'; +import createRandomPolicy from '../utils/collections/policies'; import * as TestHelper from '../utils/TestHelper'; import type {MockFetch} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -33,6 +34,9 @@ describe('actions/Policy', () => { it('creates a new workspace', async () => { (fetch as MockFetch)?.pause?.(); Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID}); + const fakePolicy = createRandomPolicy(0, CONST.POLICY.TYPE.PERSONAL); + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + Onyx.set(`${ONYXKEYS.NVP_ACTIVE_POLICY_ID}`, fakePolicy.id); await waitForBatchedUpdates(); let adminReportID; @@ -52,6 +56,19 @@ describe('actions/Policy', () => { }); }); + const activePolicyID: OnyxEntry = await new Promise((resolve) => { + const connection = Onyx.connect({ + key: `${ONYXKEYS.NVP_ACTIVE_POLICY_ID}`, + callback: (id) => { + Onyx.disconnect(connection); + resolve(id); + }, + }); + }); + console.log(activePolicyID, policyID, fakePolicy), "dsaad---------------------"; + // check if NVP_ACTIVE_POLICY_ID is updated to created policy id + expect(activePolicyID).toBe(policyID); + // check if policy was created with correct values expect(policy?.id).toBe(policyID); expect(policy?.name).toBe(WORKSPACE_NAME); diff --git a/tests/utils/collections/policies.ts b/tests/utils/collections/policies.ts index 47bf996afb7e..bda1c3242997 100644 --- a/tests/utils/collections/policies.ts +++ b/tests/utils/collections/policies.ts @@ -3,11 +3,11 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import type {Policy} from '@src/types/onyx'; -export default function createRandomPolicy(index: number): Policy { +export default function createRandomPolicy(index: number, type?: ValueOf): Policy { return { id: index.toString(), name: randWord(), - type: rand(Object.values(CONST.POLICY.TYPE)), + type: type ?? rand(Object.values(CONST.POLICY.TYPE)), autoReporting: randBoolean(), isPolicyExpenseChatEnabled: randBoolean(), autoReportingFrequency: rand( From 84edcc12c4d153792843773b820f84d917ffbaf0 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 12 Nov 2024 22:50:23 +0530 Subject: [PATCH 104/111] remove console log --- tests/actions/PolicyTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index fd88e75e01d3..0bcd22e05bb7 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -65,7 +65,7 @@ describe('actions/Policy', () => { }, }); }); - console.log(activePolicyID, policyID, fakePolicy), "dsaad---------------------"; + // check if NVP_ACTIVE_POLICY_ID is updated to created policy id expect(activePolicyID).toBe(policyID); From bd98aa5ab37dc8be68f3140d788714915040de02 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 12 Nov 2024 17:57:54 +0000 Subject: [PATCH 105/111] Update version to 9.0.60-2 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 8d276b919449..0a4d6cef5521 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009006001 - versionName "9.0.60-1" + versionCode 1009006002 + versionName "9.0.60-2" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 3951444e867d..15a7f15fcba1 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@
CFBundleVersion - 9.0.60.1 + 9.0.60.2 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 1929661c2645..db4a5d2a9490 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.60.1 + 9.0.60.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 4ac199c2902c..1913cf3a7481 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.60 CFBundleVersion - 9.0.60.1 + 9.0.60.2 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index e3c9b91eec59..71fc5e9704b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.60-1", + "version": "9.0.60-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.60-1", + "version": "9.0.60-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3616a4638f61..0f05f3a5e7f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.60-1", + "version": "9.0.60-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 4611f3613749a18b511d04d6973718b116acbabb Mon Sep 17 00:00:00 2001 From: Michael Wardrop Date: Tue, 12 Nov 2024 16:01:34 -0500 Subject: [PATCH 106/111] Update Configure-Quickbooks-Online.md https://github.com/Expensify/Expensify/issues/441282 --- .../connections/quickbooks-online/Configure-Quickbooks-Online.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/connections/quickbooks-online/Configure-Quickbooks-Online.md b/docs/articles/expensify-classic/connections/quickbooks-online/Configure-Quickbooks-Online.md index 3fd1df0c0a1c..a6e19f8fd549 100644 --- a/docs/articles/expensify-classic/connections/quickbooks-online/Configure-Quickbooks-Online.md +++ b/docs/articles/expensify-classic/connections/quickbooks-online/Configure-Quickbooks-Online.md @@ -40,6 +40,7 @@ The following steps help you determine how data will be exported from Expensify - Journal Entries - This is a single itemized journal entry for each Expensify report. - _Non-reimbursable expenses_: Non-reimbursable expenses export to QuickBooks Online as: - Credit Card expenses - Each expense will be exported as a bank transaction with its transaction date. + - Note: The Expensify Card transactions will always export as Credit Card charges, even if the non-reimbursable setting is configured differently (such as a Vendor Bill.) - Debit Card Expenses - Each expense will be exported as a bank transaction with its transaction date. - Vendor Bills - A single detailed vendor bill is generated for each Expensify report. - If the accounting period is closed, the vendor bill will be posted on the first day of the next open period. If you choose to export non-reimbursable expenses as Vendor Bills, you can assign a default vendor to the bill. From 0e07520100780643a85047bfdc0e5ef4145b2cd1 Mon Sep 17 00:00:00 2001 From: Carlos Alvarez Date: Tue, 12 Nov 2024 14:19:52 -0700 Subject: [PATCH 107/111] Revert "Move RBR to workspace chats instead of transaction threads" --- .../LHNOptionsList/OptionRowLHNData.tsx | 2 +- .../MoneyRequestPreviewContent.tsx | 4 +- .../ReportActionItem/ReportPreview.tsx | 5 +- src/libs/DebugUtils.ts | 2 +- src/libs/OptionsListUtils.ts | 2 +- src/libs/ReportUtils.ts | 90 ++++++++++++------- src/libs/SidebarUtils.ts | 21 +++-- src/libs/TransactionUtils/index.ts | 33 ++++--- src/libs/WorkspacesSettingsUtils.ts | 8 +- src/pages/Debug/Report/DebugReportPage.tsx | 6 +- src/types/onyx/TransactionViolation.ts | 3 - tests/unit/DebugUtilsTest.ts | 79 ++++++++++++---- 12 files changed, 177 insertions(+), 78 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 0fe2a1542ca3..3c40210a5d99 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -37,7 +37,7 @@ function OptionRowLHNData({ const optionItemRef = useRef(); - const shouldDisplayViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(fullReport, transactionViolations); + const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(fullReport, transactionViolations, parentReportAction); const shouldDisplayReportViolations = ReportUtils.isReportOwner(fullReport) && ReportUtils.hasReportViolations(reportID); const optionItem = useMemo(() => { diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 8d9def814549..336b7dea9654 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -115,8 +115,8 @@ function MoneyRequestPreviewContent({ const isOnHold = TransactionUtils.isOnHold(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', transactionViolations, true); - const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', transactionViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); + const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', transactionViolations); + const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', transactionViolations) && ReportUtils.isPaidGroupPolicy(iouReport); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 1edb6fe9fc9f..d476d1198808 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -157,9 +157,8 @@ function ReportPreview({ const hasErrors = (hasMissingSmartscanFields && !iouSettled) || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - ReportUtils.hasViolations(iouReportID, transactionViolations, true) || - ReportUtils.hasNoticeTypeViolations(iouReportID, transactionViolations, true) || - ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations, true) || + ReportUtils.hasViolations(iouReportID, transactionViolations) || + ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations) || (ReportUtils.isReportOwner(iouReport) && ReportUtils.hasReportViolations(iouReportID)) || ReportUtils.hasActionsWithErrors(iouReportID); const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 223c04df4eac..6b3b2a70ede0 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -588,7 +588,7 @@ function getReasonForShowingRowInLHN(report: OnyxEntry, hasRBR = false): return null; } - const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations); const reason = ReportUtils.reasonForReportToBeInOptionList({ report, diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 8d1084b5342f..6bcb353cf065 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1704,7 +1704,7 @@ function getOptions( // Filter out all the reports that shouldn't be displayed const filteredReportOptions = options.reports.filter((option) => { const report = option.item; - const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations); return ReportUtils.shouldReportBeInOptionList({ report, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1cd37cf94ffe..73721d094ebe 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6324,53 +6324,65 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b } /** - * Should we display a RBR on the LHN on this report due to violations? + * Checks to see if a report's parentAction is an expense that contains a violation type of either violation or warning */ -function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionViolations: OnyxCollection): boolean { - // We only show the RBR in the highest level, which is the workspace chat - if (!report || !isPolicyExpenseChat(report)) { +function doesTransactionThreadHaveViolations( + report: OnyxInputOrEntry, + transactionViolations: OnyxCollection, + parentReportAction: OnyxInputOrEntry, +): boolean { + if (!ReportActionsUtils.isMoneyRequestAction(parentReportAction)) { return false; } - - // We only show the RBR to the submitter - if (!isCurrentUserSubmitter(report.reportID ?? '')) { + const {IOUTransactionID, IOUReportID} = ReportActionsUtils.getOriginalMessage(parentReportAction) ?? {}; + if (!IOUTransactionID || !IOUReportID) { return false; } - - // Get all potential reports, which are the ones that are: - // - Owned by the same user - // - Are either open or submitted - // - Belong to the same workspace - // And if any have a violation, then it should have a RBR - const allReports = Object.values(ReportConnection.getAllReports() ?? {}) as Report[]; - const potentialReports = allReports.filter((r) => r.ownerAccountID === currentUserAccountID && (r.stateNum ?? 0) <= 1 && r.policyID === report.policyID); - return potentialReports.some( - (potentialReport) => hasViolations(potentialReport.reportID, transactionViolations) || hasWarningTypeViolations(potentialReport.reportID, transactionViolations), + if (!isCurrentUserSubmitter(IOUReportID)) { + return false; + } + if (report?.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report?.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED) { + return false; + } + return ( + TransactionUtils.hasViolation(IOUTransactionID, transactionViolations) || + TransactionUtils.hasWarningTypeViolation(IOUTransactionID, transactionViolations) || + (isPaidGroupPolicy(report) && TransactionUtils.hasModifiedAmountOrDateViolation(IOUTransactionID, transactionViolations)) ); } /** - * Checks to see if a report contains a violation + * Checks if we should display violation - we display violations when the expense has violation and it is not settled */ -function hasViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean): boolean { - const transactions = reportsTransactions[reportID] ?? []; - return transactions.some((transaction) => TransactionUtils.hasViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); +function shouldDisplayTransactionThreadViolations( + report: OnyxEntry, + transactionViolations: OnyxCollection, + parentReportAction: OnyxEntry, +): boolean { + if (!ReportActionsUtils.isMoneyRequestAction(parentReportAction)) { + return false; + } + const {IOUReportID} = ReportActionsUtils.getOriginalMessage(parentReportAction) ?? {}; + if (isSettled(IOUReportID) || isReportApproved(IOUReportID?.toString())) { + return false; + } + return doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); } /** - * Checks to see if a report contains a violation of type `warning` + * Checks to see if a report contains a violation */ -function hasWarningTypeViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean): boolean { +function hasViolations(reportID: string, transactionViolations: OnyxCollection): boolean { const transactions = reportsTransactions[reportID] ?? []; - return transactions.some((transaction) => TransactionUtils.hasWarningTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); + return transactions.some((transaction) => TransactionUtils.hasViolation(transaction.transactionID, transactionViolations)); } /** - * Checks to see if a report contains a violation of type `notice` + * Checks to see if a report contains a violation of type `warning` */ -function hasNoticeTypeViolations(reportID: string, transactionViolations: OnyxCollection, shouldShowInReview?: boolean): boolean { +function hasWarningTypeViolations(reportID: string, transactionViolations: OnyxCollection): boolean { const transactions = reportsTransactions[reportID] ?? []; - return transactions.some((transaction) => TransactionUtils.hasNoticeTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); + return transactions.some((transaction) => TransactionUtils.hasWarningTypeViolation(transaction.transactionID, transactionViolations)); } function hasReportViolations(reportID: string) { @@ -6392,6 +6404,23 @@ function shouldAdminsRoomBeVisible(report: OnyxEntry): boolean { return true; } +/** + * Check whether report has violations + */ +function shouldShowViolations(report: Report, transactionViolations: OnyxCollection) { + const {parentReportID, parentReportActionID} = report ?? {}; + const canGetParentReport = parentReportID && parentReportActionID && allReportActions; + if (!canGetParentReport) { + return false; + } + const parentReportActions = allReportActions ? allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? {} : {}; + const parentReportAction = parentReportActions[parentReportActionID] ?? null; + if (!parentReportAction) { + return false; + } + return shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction); +} + type ReportErrorsAndReportActionThatRequiresAttention = { errors: ErrorFields; reportAction?: OnyxEntry; @@ -6474,7 +6503,7 @@ function hasReportErrorsOtherThanFailedReceipt(report: Report, doesReportHaveVio let doesTransactionThreadReportHasViolations = false; if (oneTransactionThreadReportID) { const transactionReport = getReport(oneTransactionThreadReportID); - doesTransactionThreadReportHasViolations = !!transactionReport && shouldDisplayViolationsRBRInLHN(transactionReport, transactionViolations); + doesTransactionThreadReportHasViolations = !!transactionReport && shouldShowViolations(transactionReport, transactionViolations); } return ( doesTransactionThreadReportHasViolations || @@ -8468,6 +8497,7 @@ export { chatIncludesConcierge, createDraftTransactionAndNavigateToParticipantSelector, doesReportBelongToWorkspace, + doesTransactionThreadHaveViolations, findLastAccessedReport, findSelfDMReportID, formatReportLastMessageText, @@ -8571,7 +8601,6 @@ export { hasUpdatedTotal, hasViolations, hasWarningTypeViolations, - hasNoticeTypeViolations, isActionCreator, isAdminRoom, isAdminsOnlyPostingRoom, @@ -8673,7 +8702,7 @@ export { shouldDisableRename, shouldDisableThread, shouldDisplayThreadReplies, - shouldDisplayViolationsRBRInLHN, + shouldDisplayTransactionThreadViolations, shouldReportBeInOptionList, shouldReportShowSubscript, shouldShowFlagComment, @@ -8721,6 +8750,7 @@ export { buildOptimisticChangeFieldAction, isPolicyRelatedReport, hasReportErrorsOtherThanFailedReceipt, + shouldShowViolations, getAllReportErrors, getAllReportActionsErrorsAndReportActionThatRequiresAttention, hasInvoiceReports, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index b8acec00af05..d47cee3745a0 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -110,7 +110,7 @@ function getOrderedReportIDs( return; } const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); - const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations); const isHidden = ReportUtils.getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; const hasErrorsOtherThanFailedReceipt = ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations); @@ -239,11 +239,22 @@ function getReasonAndReportActionThatHasRedBrickRoad( ): ReasonAndReportActionThatHasRedBrickRoad | null { const {errors, reportAction} = ReportUtils.getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions); const hasErrors = Object.keys(errors).length !== 0; + const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, ReportActionsUtils.getAllReportActions(report.reportID)); - if (ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations)) { - return { - reason: CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS, - }; + if (oneTransactionThreadReportID) { + const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); + + if ( + ReportUtils.shouldDisplayTransactionThreadViolations( + oneTransactionThreadReport, + transactionViolations, + ReportActionsUtils.getAllReportActions(report.reportID)[oneTransactionThreadReport?.parentReportActionID ?? '-1'], + ) + ) { + return { + reason: CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS, + }; + } } if (hasErrors) { diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 1fc7cad2f456..25d0d93c99c7 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -866,37 +866,41 @@ function isOnHoldByTransactionID(transactionID: string): boolean { /** * Checks if any violations for the provided transaction are of type 'violation' */ -function hasViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { +function hasViolation(transactionID: string, transactionViolations: OnyxCollection): boolean { return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), + (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION, ); } /** * Checks if any violations for the provided transaction are of type 'notice' */ -function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { - return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), - ); +function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection): boolean { + return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE); } /** * Checks if any violations for the provided transaction are of type 'warning' */ -function hasWarningTypeViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean | null): boolean { - const violations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]; - const warningTypeViolations = - violations?.filter( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === null || showInReview === (violation.showInReview ?? false)), - ) ?? []; - +function hasWarningTypeViolation(transactionID: string, transactionViolations: OnyxCollection): boolean { + const warningTypeViolations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter( + (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING, + ); const hasOnlyDupeDetectionViolation = warningTypeViolations?.every((violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION); if (!Permissions.canUseDupeDetection(allBetas ?? []) && hasOnlyDupeDetectionViolation) { return false; } - return warningTypeViolations.length > 0; + return !!warningTypeViolations && warningTypeViolations.length > 0; +} + +/** + * Checks if any violations for the provided transaction are of modifiedAmount or modifiedDate + */ +function hasModifiedAmountOrDateViolation(transactionID: string, transactionViolations: OnyxCollection): boolean { + return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( + (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.MODIFIED_AMOUNT || violation.name === CONST.VIOLATIONS.MODIFIED_DATE, + ); } /** @@ -1288,6 +1292,7 @@ export { shouldShowBrokenConnectionViolation, hasNoticeTypeViolation, hasWarningTypeViolation, + hasModifiedAmountOrDateViolation, isCustomUnitRateIDForP2P, getRateID, getTransaction, diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index 18e2fa179764..eb03d8b6def9 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -78,7 +78,13 @@ const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection if (oneTransactionThreadReportID && !doesReportContainErrors) { const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); - if (ReportUtils.shouldDisplayViolationsRBRInLHN(oneTransactionThreadReport, allTransactionViolations)) { + if ( + ReportUtils.shouldDisplayTransactionThreadViolations( + oneTransactionThreadReport, + allTransactionViolations, + reportActions[oneTransactionThreadReport?.parentReportActionID ?? '-1'], + ) + ) { doesReportContainErrors = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } } diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx index 67fa0a6c5113..5fa26cbf1835 100644 --- a/src/pages/Debug/Report/DebugReportPage.tsx +++ b/src/pages/Debug/Report/DebugReportPage.tsx @@ -53,13 +53,15 @@ function DebugReportPage({ const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID ?? '-1'}`); + const parentReportAction = parentReportActions && report?.parentReportID ? parentReportActions[report?.parentReportActionID ?? '-1'] : undefined; const metadata = useMemo(() => { if (!report) { return []; } - const shouldDisplayViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations); + const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction); const shouldDisplayReportViolations = ReportUtils.isReportOwner(report) && ReportUtils.hasReportViolations(reportID); const hasViolations = !!shouldDisplayViolations || shouldDisplayReportViolations; const {reason: reasonGBR, reportAction: reportActionGBR} = DebugUtils.getReasonAndReportActionForGBRInLHNRow(report) ?? {}; @@ -111,7 +113,7 @@ function DebugReportPage({ : undefined, }, ]; - }, [report, reportActions, reportID, transactionViolations, translate]); + }, [parentReportAction, report, reportActions, reportID, transactionViolations, translate]); if (!report) { return ; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index bfb215a1bbdb..bf8ecc7ebdde 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -92,9 +92,6 @@ type TransactionViolation = { /** Additional violation information to provide the user */ data?: TransactionViolationData; - - /** Indicates if this violation should be shown in review */ - showInReview?: boolean; }; /** Collection of transaction violations */ diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index abeaff971194..c5d84341deee 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -693,6 +693,46 @@ describe('DebugUtils', () => { }); expect(reason).toBe('debug.reasonVisibleInLHN.pinnedByUser'); }); + it('returns correct reason when report has IOU violations', async () => { + const threadReport = { + ...baseReport, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + parentReportID: '0', + parentReportActionID: '0', + }; + await Onyx.multiSet({ + [ONYXKEYS.SESSION]: { + accountID: 1234, + }, + [`${ONYXKEYS.COLLECTION.REPORT}0` as const]: { + reportID: '0', + type: CONST.REPORT.TYPE.EXPENSE, + ownerAccountID: 1234, + }, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}0` as const]: { + // eslint-disable-next-line @typescript-eslint/naming-convention + '0': { + reportActionID: '0', + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + message: { + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + IOUTransactionID: '0', + IOUReportID: '0', + }, + }, + }, + [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: threadReport, + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}0` as const]: [ + { + type: CONST.VIOLATION_TYPES.VIOLATION, + name: CONST.VIOLATIONS.MODIFIED_AMOUNT, + }, + ], + }); + const reason = DebugUtils.getReasonForShowingRowInLHN(threadReport); + expect(reason).toBe('debug.reasonVisibleInLHN.hasIOUViolations'); + }); it('returns correct reason when report has add workspace room errors', () => { const reason = DebugUtils.getReasonForShowingRowInLHN({ ...baseReport, @@ -1490,13 +1530,28 @@ describe('DebugUtils', () => { ) ?? {}; expect(reason).toBe('debug.reasonRBR.hasViolations'); }); - it('returns correct reason when there are reports on the workspace chat with violations', async () => { + it('returns correct reason when there are transaction thread violations', async () => { const report: Report = { reportID: '0', - type: CONST.REPORT.TYPE.CHAT, + type: CONST.REPORT.TYPE.EXPENSE, ownerAccountID: 1234, - policyID: '1', - chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + }; + const reportActions: ReportActions = { + // eslint-disable-next-line @typescript-eslint/naming-convention + '0': { + reportActionID: '0', + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + message: { + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + IOUTransactionID: '0', + IOUReportID: '0', + amount: 10, + currency: CONST.CURRENCY.USD, + text: '', + }, + created: '2024-07-13 06:02:11.111', + childReportID: '1', + }, }; await Onyx.multiSet({ [ONYXKEYS.SESSION]: { @@ -1507,23 +1562,17 @@ describe('DebugUtils', () => { reportID: '1', parentReportActionID: '0', stateNum: CONST.REPORT.STATE_NUM.OPEN, - ownerAccountID: 1234, - policyID: '1', - }, - [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { - transactionID: '1', - amount: 10, - modifiedAmount: 10, - reportID: '0', + statusNum: CONST.REPORT.STATE_NUM.SUBMITTED, }, - [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}1` as const]: [ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}0` as const]: reportActions, + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}0` as const]: [ { type: CONST.VIOLATION_TYPES.VIOLATION, - name: CONST.VIOLATIONS.MISSING_CATEGORY, + name: CONST.VIOLATIONS.MODIFIED_AMOUNT, }, ], }); - const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, {}, false) ?? {}; + const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, reportActions, false) ?? {}; expect(reason).toBe('debug.reasonRBR.hasTransactionThreadViolations'); }); }); From f23a5f03291cc681e54f5973059cf99384b059cd Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 12 Nov 2024 15:31:47 -0700 Subject: [PATCH 108/111] resolve conflicts --- src/CONST.ts | 3 - src/components/Search/SearchPageHeader.tsx | 2 +- src/components/Search/types.ts | 8 -- .../SelectionList/Search/ActionCell.tsx | 41 +++---- .../Search/ExpenseItemHeaderNarrow.tsx | 3 - .../SelectionList/Search/ReportListItem.tsx | 7 +- .../Search/TransactionListItem.tsx | 6 +- .../Search/TransactionListItemRow.tsx | 4 - .../SelectionList/SearchTableHeaderColumn.tsx | 0 .../PayMoneyRequestOnSearchParams.ts | 5 +- src/libs/PolicyUtils.ts | 11 +- src/libs/ReportUtils.ts | 30 +++-- src/libs/SearchUIUtils.ts | 65 +---------- src/libs/TransactionUtils/index.ts | 3 +- src/libs/actions/IOU.ts | 27 ++--- src/libs/actions/Search.ts | 78 ++----------- src/types/onyx/Report.ts | 2 +- src/types/onyx/SearchResults.ts | 104 +----------------- 18 files changed, 67 insertions(+), 332 deletions(-) create mode 100644 src/components/SelectionList/SearchTableHeaderColumn.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 056151742c25..4e873163cc95 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5848,9 +5848,6 @@ const CONST = { ACTION_TYPES: { VIEW: 'view', REVIEW: 'review', - SUBMIT: 'submit', - APPROVE: 'approve', - PAY: 'pay', DONE: 'done', PAID: 'paid', }, diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index d73937aeadd9..a330be3d5ff6 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -182,7 +182,7 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) { return; } - const reportIDList = selectedReports?.filter((report) => !!report) ?? []; + const reportIDList = (selectedReports?.filter((report) => !!report) as string[]) ?? []; SearchActions.exportSearchItemsToCSV( {query: status, jsonQuery: JSON.stringify(queryJSON), reportIDList, transactionIDList: selectedTransactionsKeys, policyIDs: [activeWorkspaceID ?? '']}, () => { diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 130ad7ae6f6e..74bf7b16d020 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -24,13 +24,6 @@ type SelectedTransactionInfo = { /** Model of selected results */ type SelectedTransactions = Record; -/** Model of payment data used by Search bulk actions */ -type PaymentData = { - reportID: string; - amount: number; - paymentType: ValueOf; -}; - type SortOrder = ValueOf; type SearchColumnType = ValueOf; type ExpenseSearchStatus = ValueOf; @@ -124,6 +117,5 @@ export type { TripSearchStatus, ChatSearchStatus, SearchAutocompleteResult, - PaymentData, SearchAutocompleteQueryRange, }; diff --git a/src/components/SelectionList/Search/ActionCell.tsx b/src/components/SelectionList/Search/ActionCell.tsx index 0a360a96e7c7..bd3d729e4257 100644 --- a/src/components/SelectionList/Search/ActionCell.tsx +++ b/src/components/SelectionList/Search/ActionCell.tsx @@ -16,9 +16,6 @@ import type {SearchTransactionAction} from '@src/types/onyx/SearchResults'; const actionTranslationsMap: Record = { view: 'common.view', review: 'common.review', - submit: 'common.submit', - approve: 'iou.approve', - pay: 'iou.pay', done: 'common.done', paid: 'iou.settledExpensify', }; @@ -30,18 +27,9 @@ type ActionCellProps = { goToItem: () => void; isChildListItem?: boolean; parentAction?: string; - isLoading?: boolean; }; -function ActionCell({ - action = CONST.SEARCH.ACTION_TYPES.VIEW, - isLargeScreenWidth = true, - isSelected = false, - goToItem, - isChildListItem = false, - parentAction = '', - isLoading = false, -}: ActionCellProps) { +function ActionCell({action = CONST.SEARCH.ACTION_TYPES.VIEW, isLargeScreenWidth = true, isSelected = false, goToItem, isChildListItem = false, parentAction = ''}: ActionCellProps) { const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); @@ -75,8 +63,9 @@ function ActionCell({ ); } + const buttonInnerStyles = isSelected ? styles.buttonDefaultHovered : {}; + if (action === CONST.SEARCH.ACTION_TYPES.VIEW || shouldUseViewAction) { - const buttonInnerStyles = isSelected ? styles.buttonDefaultHovered : {}; return isLargeScreenWidth ? (