diff --git a/src/components/Indicator.tsx b/src/components/Indicator.tsx index 5ae227d1de70..4d352b6a6cde 100644 --- a/src/components/Indicator.tsx +++ b/src/components/Indicator.tsx @@ -1,9 +1,10 @@ import React from 'react'; import {StyleSheet, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import {isConnectionInProgress} from '@libs/actions/connections'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import * as UserUtils from '@libs/UserUtils'; @@ -41,6 +42,7 @@ type IndicatorProps = IndicatorOnyxProps; function Indicator({reimbursementAccount, policies, bankAccountList, fundList, userWallet, walletTerms, loginList}: IndicatorOnyxProps) { const theme = useTheme(); const styles = useThemeStyles(); + const [allConnectionSyncProgresses] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}`); // If a policy was just deleted from Onyx, then Onyx will pass a null value to the props, and // those should be cleaned out before doing any error checking @@ -55,7 +57,13 @@ function Indicator({reimbursementAccount, policies, bankAccountList, fundList, u () => Object.values(cleanPolicies).some(PolicyUtils.hasPolicyError), () => Object.values(cleanPolicies).some(PolicyUtils.hasCustomUnitsError), () => Object.values(cleanPolicies).some(PolicyUtils.hasEmployeeListError), - () => Object.values(cleanPolicies).some(PolicyUtils.hasSyncError), + () => + Object.values(cleanPolicies).some((cleanPolicy) => + PolicyUtils.hasSyncError( + cleanPolicy, + isConnectionInProgress(allConnectionSyncProgresses?.[`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${cleanPolicy?.id}`], cleanPolicy), + ), + ), () => SubscriptionUtils.hasSubscriptionRedDotError(), () => Object.keys(reimbursementAccount?.errors ?? {}).length > 0, () => !!loginList && UserUtils.hasLoginListError(loginList), diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 002f794ae247..07ce5b1539df 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -89,8 +89,8 @@ function hasPolicyCategoriesError(policyCategories: OnyxEntry) /** * Checks if the policy had a sync error. */ -function hasSyncError(policy: OnyxEntry): boolean { - return (Object.keys(policy?.connections ?? {}) as ConnectionName[]).some((connection) => !!getSynchronizationErrorMessage(policy, connection, false)); +function hasSyncError(policy: OnyxEntry, isSyncInProgress: boolean): boolean { + return (Object.keys(policy?.connections ?? {}) as ConnectionName[]).some((connection) => !!getSynchronizationErrorMessage(policy, connection, isSyncInProgress)); } /** @@ -159,8 +159,8 @@ function getUnitRateValue(toLocaleDigit: (arg: string) => string, customUnitRate /** * Get the brick road indicator status for a policy. The policy has an error status if there is a policy member error, a custom unit error or a field error. */ -function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry): ValueOf | undefined { - if (hasEmployeeListError(policy) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy) || hasSyncError(policy)) { +function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry, isConnectionInProgress: boolean): ValueOf | undefined { + if (hasEmployeeListError(policy) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy) || hasSyncError(policy, isConnectionInProgress)) { return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } return undefined; diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index 9b96c8404bf6..ed46b0b5f5ec 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -5,7 +5,8 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, ReimbursementAccount, Report, ReportAction, ReportActions, TransactionViolations} from '@src/types/onyx'; -import type {Unit} from '@src/types/onyx/Policy'; +import type {PolicyConnectionSyncProgress, Unit} from '@src/types/onyx/Policy'; +import {isConnectionInProgress} from './actions/connections'; import * as CurrencyUtils from './CurrencyUtils'; import type {Phrase, PhraseParameters} from './Localize'; import * as OptionsListUtils from './OptionsListUtils'; @@ -18,14 +19,6 @@ type CheckingMethod = () => boolean; type BrickRoad = ValueOf | undefined; -let allPolicies: OnyxCollection; - -Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY, - waitForCollectionCallback: true, - callback: (value) => (allPolicies = value), -}); - let reimbursementAccount: OnyxEntry; Onyx.connect({ @@ -100,7 +93,7 @@ const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection return shouldShowGreenDotIndicator ? CONST.BRICK_ROAD_INDICATOR_STATUS.INFO : undefined; }; -function hasGlobalWorkspaceSettingsRBR(policies: OnyxCollection) { +function hasGlobalWorkspaceSettingsRBR(policies: OnyxCollection, allConnectionProgresses: OnyxCollection) { // When attempting to open a policy with an invalid policyID, the policy collection is updated to include policy objects with error information. // Only policies displayed on the policy list page should be verified. Otherwise, the user will encounter an RBR unrelated to any policies on the list. const cleanPolicies = Object.fromEntries(Object.entries(policies ?? {}).filter(([, policy]) => policy?.id)); @@ -110,7 +103,10 @@ function hasGlobalWorkspaceSettingsRBR(policies: OnyxCollection) { () => Object.values(cleanPolicies).some(hasCustomUnitsError), () => Object.values(cleanPolicies).some(hasTaxRateError), () => Object.values(cleanPolicies).some(hasEmployeeListError), - () => Object.values(cleanPolicies).some(hasSyncError), + () => + Object.values(cleanPolicies).some((cleanPolicy) => + hasSyncError(cleanPolicy, isConnectionInProgress(allConnectionProgresses?.[`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${cleanPolicy?.id}`], cleanPolicy)), + ), () => Object.keys(reimbursementAccount?.errors ?? {}).length > 0, ]; @@ -154,19 +150,6 @@ function getChatTabBrickRoad(policyID?: string): BrickRoad | undefined { return undefined; } -function checkIfWorkspaceSettingsTabHasRBR(policyID?: string) { - if (!policyID) { - return hasGlobalWorkspaceSettingsRBR(allPolicies); - } - const policy = allPolicies ? allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] : null; - - if (!policy) { - return false; - } - - return hasWorkspaceSettingsRBR(policy); -} - /** * @returns a map where the keys are policyIDs and the values are BrickRoads for each policy */ @@ -318,7 +301,6 @@ export { getWorkspacesBrickRoads, getWorkspacesUnreadStatuses, hasGlobalWorkspaceSettingsRBR, - checkIfWorkspaceSettingsTabHasRBR, hasWorkspaceSettingsRBR, getChatTabBrickRoad, getUnitTranslationKey, diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts index fa2e274204a2..4c7ec69e6134 100644 --- a/src/libs/actions/connections/index.ts +++ b/src/libs/actions/connections/index.ts @@ -1,3 +1,4 @@ +import {differenceInMinutes, isValid, parseISO} from 'date-fns'; import isObject from 'lodash/isObject'; import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; @@ -9,7 +10,7 @@ import * as Localize from '@libs/Localize'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; -import type {ConnectionName, Connections, PolicyConnectionName} from '@src/types/onyx/Policy'; +import type {ConnectionName, Connections, PolicyConnectionName, PolicyConnectionSyncProgress} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -383,7 +384,8 @@ function getSynchronizationErrorMessage(policy: OnyxEntry, connectionNam } const connection = policy?.connections?.[connectionName]; - if (isSyncInProgress || isEmptyObject(connection?.lastSync) || connection?.lastSync?.isSuccessful) { + + if (isSyncInProgress || isEmptyObject(connection?.lastSync) || connection?.lastSync?.isSuccessful !== false || !connection?.lastSync?.errorDate) { return; } return `${syncError} ("${connection?.lastSync?.errorMessage}")`; @@ -448,6 +450,20 @@ function copyExistingPolicyConnection(connectedPolicyID: string, targetPolicyID: ); } +function isConnectionInProgress(connectionSyncProgress: OnyxEntry, policy?: OnyxEntry): boolean { + if (!policy || !connectionSyncProgress) { + return false; + } + + const lastSyncProgressDate = parseISO(connectionSyncProgress?.timestamp ?? ''); + return ( + !!connectionSyncProgress?.stageInProgress && + (connectionSyncProgress.stageInProgress !== CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE || !policy?.connections?.[connectionSyncProgress.connectionName]) && + isValid(lastSyncProgressDate) && + differenceInMinutes(new Date(), lastSyncProgressDate) < CONST.POLICY.CONNECTIONS.SYNC_STAGE_TIMEOUT_MINUTES + ); +} + export { removePolicyConnection, updatePolicyConnectionConfig, @@ -458,4 +474,5 @@ export { syncConnection, copyExistingPolicyConnection, isConnectionUnverified, + isConnectionInProgress, }; diff --git a/src/pages/home/sidebar/AllSettingsScreen.tsx b/src/pages/home/sidebar/AllSettingsScreen.tsx index dc969cb98622..c0322fc0fcf7 100644 --- a/src/pages/home/sidebar/AllSettingsScreen.tsx +++ b/src/pages/home/sidebar/AllSettingsScreen.tsx @@ -30,6 +30,7 @@ function AllSettingsScreen({policies}: AllSettingsScreenProps) { const waitForNavigate = useWaitForNavigation(); const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const [allConnectionSyncProgresses] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}`); const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION); @@ -48,7 +49,7 @@ function AllSettingsScreen({policies}: AllSettingsScreenProps) { })(); }, focused: !shouldUseNarrowLayout, - brickRoadIndicator: hasGlobalWorkspaceSettingsRBR(policies) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + brickRoadIndicator: hasGlobalWorkspaceSettingsRBR(policies, allConnectionSyncProgresses) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, }, ...(privateSubscription ? [ @@ -90,7 +91,7 @@ function AllSettingsScreen({policies}: AllSettingsScreenProps) { hoverAndPressStyle: styles.hoveredComponentBG, brickRoadIndicator: item.brickRoadIndicator, })); - }, [shouldUseNarrowLayout, policies, privateSubscription, waitForNavigate, translate, styles]); + }, [shouldUseNarrowLayout, policies, privateSubscription, waitForNavigate, translate, styles, allConnectionSyncProgresses]); return ( Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policy.id)), - brickRoadIndicator: reimbursementAccountBrickRoadIndicator ?? PolicyUtils.getPolicyBrickRoadIndicatorStatus(policy), + brickRoadIndicator: + reimbursementAccountBrickRoadIndicator ?? + PolicyUtils.getPolicyBrickRoadIndicatorStatus( + policy, + isConnectionInProgress(allConnectionSyncProgresses?.[`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy.id}`], policy), + ), pendingAction: policy.pendingAction, errors: policy.errors, dismissError: () => dismissWorkspaceError(policy.id, policy.pendingAction), @@ -356,7 +363,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}: }; }) .sort((a, b) => localeCompare(a.title, b.title)); - }, [reimbursementAccount?.errors, policies, isOffline, theme.textLight, policyRooms, session?.email]); + }, [reimbursementAccount?.errors, policies, isOffline, theme.textLight, policyRooms, session?.email, allConnectionSyncProgresses]); const getHeaderButton = () => (