Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix GBR and error briefly show after connecting to QBO, Xero. #47483

Merged
merged 25 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/components/Indicator.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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
Expand All @@ -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),
Expand Down
8 changes: 4 additions & 4 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ function hasPolicyCategoriesError(policyCategories: OnyxEntry<PolicyCategories>)
/**
* Checks if the policy had a sync error.
*/
function hasSyncError(policy: OnyxEntry<Policy>): boolean {
return (Object.keys(policy?.connections ?? {}) as ConnectionName[]).some((connection) => !!getSynchronizationErrorMessage(policy, connection, false));
function hasSyncError(policy: OnyxEntry<Policy>, isSyncInProgress: boolean): boolean {
return (Object.keys(policy?.connections ?? {}) as ConnectionName[]).some((connection) => !!getSynchronizationErrorMessage(policy, connection, isSyncInProgress));
}

/**
Expand Down Expand Up @@ -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<Policy>): ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS> | undefined {
if (hasEmployeeListError(policy) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy) || hasSyncError(policy)) {
function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry<Policy>, isConnectionInProgress: boolean): ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS> | undefined {
if (hasEmployeeListError(policy) || hasCustomUnitsError(policy) || hasPolicyErrorFields(policy) || hasSyncError(policy, isConnectionInProgress)) {
return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
}
return undefined;
Expand Down
32 changes: 7 additions & 25 deletions src/libs/WorkspacesSettingsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -18,14 +19,6 @@ type CheckingMethod = () => boolean;

type BrickRoad = ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS> | undefined;

let allPolicies: OnyxCollection<Policy>;

Onyx.connect({
key: ONYXKEYS.COLLECTION.POLICY,
waitForCollectionCallback: true,
callback: (value) => (allPolicies = value),
});

let reimbursementAccount: OnyxEntry<ReimbursementAccount>;

Onyx.connect({
Expand Down Expand Up @@ -100,7 +93,7 @@ const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection
return shouldShowGreenDotIndicator ? CONST.BRICK_ROAD_INDICATOR_STATUS.INFO : undefined;
};

function hasGlobalWorkspaceSettingsRBR(policies: OnyxCollection<Policy>) {
function hasGlobalWorkspaceSettingsRBR(policies: OnyxCollection<Policy>, allConnectionProgresses: OnyxCollection<PolicyConnectionSyncProgress>) {
// 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));
Expand All @@ -110,7 +103,10 @@ function hasGlobalWorkspaceSettingsRBR(policies: OnyxCollection<Policy>) {
() => 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,
];

Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -318,7 +301,6 @@ export {
getWorkspacesBrickRoads,
getWorkspacesUnreadStatuses,
hasGlobalWorkspaceSettingsRBR,
checkIfWorkspaceSettingsTabHasRBR,
hasWorkspaceSettingsRBR,
getChatTabBrickRoad,
getUnitTranslationKey,
Expand Down
21 changes: 19 additions & 2 deletions src/libs/actions/connections/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';

Expand Down Expand Up @@ -383,7 +384,8 @@ function getSynchronizationErrorMessage(policy: OnyxEntry<Policy>, 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}")`;
Expand Down Expand Up @@ -448,6 +450,20 @@ function copyExistingPolicyConnection(connectedPolicyID: string, targetPolicyID:
);
}

function isConnectionInProgress(connectionSyncProgress: OnyxEntry<PolicyConnectionSyncProgress>, policy?: OnyxEntry<Policy>): 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,
Expand All @@ -458,4 +474,5 @@ export {
syncConnection,
copyExistingPolicyConnection,
isConnectionUnverified,
isConnectionInProgress,
};
5 changes: 3 additions & 2 deletions src/pages/home/sidebar/AllSettingsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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
? [
Expand Down Expand Up @@ -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 (
<ScreenWrapper
Expand Down
5 changes: 3 additions & 2 deletions src/pages/settings/InitialSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ function InitialSettingsPage({userWallet, bankAccountList, fundList, walletTerms
const {translate} = useLocalize();
const activeCentralPaneRoute = useActiveCentralPaneRoute();
const emojiCode = currentUserPersonalDetails?.status?.emojiCode ?? '';
const [allConnectionSyncProgresses] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}`);

const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION);
const subscriptionPlan = useSubscriptionPlan();
Expand Down Expand Up @@ -195,7 +196,7 @@ function InitialSettingsPage({userWallet, bankAccountList, fundList, walletTerms
translationKey: 'common.workspaces',
icon: Expensicons.Building,
routeName: ROUTES.SETTINGS_WORKSPACES,
brickRoadIndicator: hasGlobalWorkspaceSettingsRBR(policies) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
brickRoadIndicator: hasGlobalWorkspaceSettingsRBR(policies, allConnectionSyncProgresses) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
},
{
translationKey: 'allSettingsScreen.domains',
Expand Down Expand Up @@ -225,7 +226,7 @@ function InitialSettingsPage({userWallet, bankAccountList, fundList, walletTerms
sectionTranslationKey: 'common.workspaces',
items,
};
}, [policies, privateSubscription?.errors, styles.badgeSuccess, styles.workspaceSettingsSectionContainer, subscriptionPlan, translate]);
}, [policies, privateSubscription?.errors, styles.badgeSuccess, styles.workspaceSettingsSectionContainer, subscriptionPlan, translate, allConnectionSyncProgresses]);

/**
* Retuns a list of menu items data for general section
Expand Down
6 changes: 4 additions & 2 deletions src/pages/workspace/WorkspaceInitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import {useOnyx, withOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import ConfirmModal from '@components/ConfirmModal';
Expand All @@ -21,6 +21,7 @@ import usePrevious from '@hooks/usePrevious';
import useSingleExecution from '@hooks/useSingleExecution';
import useThemeStyles from '@hooks/useThemeStyles';
import useWaitForNavigation from '@hooks/useWaitForNavigation';
import {isConnectionInProgress} from '@libs/actions/connections';
import getTopmostRouteName from '@libs/Navigation/getTopmostRouteName';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
Expand Down Expand Up @@ -95,7 +96,8 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc
const policy = policyDraft?.id ? policyDraft : policyProp;
const [isCurrencyModalOpen, setIsCurrencyModalOpen] = useState(false);
const hasPolicyCreationError = !!(policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD && !isEmptyObject(policy.errors));
const hasSyncError = PolicyUtils.hasSyncError(policy);
const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy?.id}`);
const hasSyncError = PolicyUtils.hasSyncError(policy, isConnectionInProgress(connectionSyncProgress, policy));
const waitForNavigate = useWaitForNavigation();
const {singleExecution, isExecuting} = useSingleExecution();
const activeRoute = useNavigationState(getTopmostRouteName);
Expand Down
13 changes: 10 additions & 3 deletions src/pages/workspace/WorkspacesListPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useCallback, useMemo, useState} from 'react';
import {FlatList, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import {useOnyx, withOnyx} from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import Button from '@components/Button';
Expand All @@ -25,6 +25,7 @@ import useNetwork from '@hooks/useNetwork';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {isConnectionInProgress} from '@libs/actions/connections';
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
import localeCompare from '@libs/LocaleCompare';
import Navigation from '@libs/Navigation/Navigation';
Expand Down Expand Up @@ -123,6 +124,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}:
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
const [allConnectionSyncProgresses] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS);

const {activeWorkspaceID, setActiveWorkspaceID} = useActiveWorkspace();

Expand Down Expand Up @@ -338,7 +340,12 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}:
title: policy.name,
icon: policy.avatarURL ? policy.avatarURL : ReportUtils.getDefaultWorkspaceAvatar(policy.name),
action: () => 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),
Expand All @@ -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 = () => (
<Button
Expand Down
17 changes: 6 additions & 11 deletions src/pages/workspace/accounting/PolicyAccountingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {differenceInMinutes, isValid, parseISO} from 'date-fns';
import React, {useEffect, useMemo, useRef, useState} from 'react';
import {ActivityIndicator, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
Expand All @@ -23,7 +22,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {getSynchronizationErrorMessage, isAuthenticationError, isConnectionUnverified, removePolicyConnection, syncConnection} from '@libs/actions/connections';
import {getSynchronizationErrorMessage, isAuthenticationError, isConnectionInProgress, isConnectionUnverified, removePolicyConnection, syncConnection} from '@libs/actions/connections';
import {
areSettingsInErrorFields,
findCurrentXeroOrganization,
Expand Down Expand Up @@ -62,12 +61,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
const {canUseWorkspaceFeeds} = usePermissions();
const {startIntegrationFlow, popoverAnchorRefs} = useAccountingContext();

const lastSyncProgressDate = parseISO(connectionSyncProgress?.timestamp ?? '');
const isSyncInProgress =
!!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;
const isSyncInProgress = isConnectionInProgress(connectionSyncProgress, policy);

const accountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME);
const connectedIntegration = getConnectedIntegration(policy, accountingIntegrations) ?? connectionSyncProgress?.connectionName;
Expand Down Expand Up @@ -290,9 +284,10 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
errorText: synchronizationError,
errorTextStyle: [styles.mt5],
shouldShowRedDotIndicator: true,
description: isSyncInProgress
? translate('workspace.accounting.connections.syncStageName', connectionSyncProgress.stageInProgress)
: translate('workspace.accounting.lastSync', datetimeToRelative),
description:
isSyncInProgress && connectionSyncProgress?.stageInProgress
? translate('workspace.accounting.connections.syncStageName', connectionSyncProgress.stageInProgress)
: translate('workspace.accounting.lastSync', datetimeToRelative),
rightComponent: isSyncInProgress ? (
<ActivityIndicator
style={[styles.popoverMenuIcon]}
Expand Down
Loading