diff --git a/src/hooks/useSearchTypeMenuSections.ts b/src/hooks/useSearchTypeMenuSections.ts index 2777e665e363..328a6ba449b2 100644 --- a/src/hooks/useSearchTypeMenuSections.ts +++ b/src/hooks/useSearchTypeMenuSections.ts @@ -15,6 +15,7 @@ const policySelector = (policy: OnyxEntry): OnyxEntry => type: policy.type, role: policy.role, owner: policy.owner, + connections: policy.connections, outputCurrency: policy.outputCurrency, isPolicyExpenseChatEnabled: policy.isPolicyExpenseChatEnabled, reimburser: policy.reimburser, diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index f0fa943594e1..2c91cb6e03a6 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -41,6 +41,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; +import type {ConnectionName} from '@src/types/onyx/Policy'; import type {SaveSearchItem} from '@src/types/onyx/SaveSearch'; import type SearchResults from '@src/types/onyx/SearchResults'; import type { @@ -58,6 +59,7 @@ import type { SearchWithdrawalIDGroup, } from '@src/types/onyx/SearchResults'; import type IconAsset from '@src/types/utils/IconAsset'; +import {hasSynchronizationErrorMessage} from './actions/connections'; import {canApproveIOU, canIOUBePaid, canSubmitReport} from './actions/IOU'; import {createNewReport, createTransactionThreadReport, openReport} from './actions/Report'; import {updateSearchResultsWithTransactionThreadReportID} from './actions/Search'; @@ -556,6 +558,10 @@ function getSuggestedSearchesVisibility( const isExporter = policy.exporter === currentUserEmail; const isApprover = policy.approver === currentUserEmail; const isApprovalEnabled = policy.approvalMode ? policy.approvalMode !== CONST.POLICY.APPROVAL_MODE.OPTIONAL : false; + + const hasExportError = (Object.keys(policy.connections ?? {}) as ConnectionName[]).some((connection) => { + return hasSynchronizationErrorMessage(policy, connection, false); + }); const isPaymentEnabled = arePaymentsEnabled(policy); const hasVBBA = !!policy.achAccount?.bankAccountID && policy.achAccount.state === CONST.BANK_ACCOUNT.STATE.OPEN; const hasReimburser = !!policy.achAccount?.reimburser; @@ -571,7 +577,7 @@ function getSuggestedSearchesVisibility( const hasPendingApprovals = hasPendingApprovalTasks(reports, currentUserAccountID); const isEligibleForApproveSuggestion = isPaidPolicy && isApprovalEnabled && (isApprover || isSubmittedTo || hasPendingApprovals); - const isEligibleForExportSuggestion = isExporter; + const isEligibleForExportSuggestion = isExporter && !hasExportError; const isEligibleForStatementsSuggestion = isPaidPolicy && !!policy.areCompanyCardsEnabled && hasCardFeed; const isEligibleForUnapprovedCashSuggestion = isPaidPolicy && isAdmin && isApprovalEnabled && isPaymentEnabled; const isEligibleForUnapprovedCardSuggestion = isPaidPolicy && isAdmin && isApprovalEnabled && (hasCardFeed || isECardEnabled); @@ -2284,6 +2290,7 @@ export { getSuggestedSearches, getListItem, getSections, + getSuggestedSearchesVisibility, getShouldShowMerchant, getSortedSections, isTransactionGroupListItemType, diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 69eab20718a7..d6763fc15f9f 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -1,3 +1,4 @@ +import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import ChatListItem from '@components/SelectionList/ChatListItem'; import TransactionGroupListItem from '@components/SelectionList/Search/TransactionGroupListItem'; @@ -26,6 +27,7 @@ import * as SearchUIUtils from '@src/libs/SearchUIUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; +import type {Connections} from '@src/types/onyx/Policy'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import {formatPhoneNumber, localeCompare} from '../../utils/TestHelper'; import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; @@ -2445,6 +2447,53 @@ describe('SearchUIUtils', () => { expect(isTaxAmountLengthLong2).toBe(true); }); + describe('Test getSuggestedSearchesVisibility', () => { + test('Should not show export if there are no valid connections', () => { + const policyKey = `policy_${policyID}`; + + const policies: OnyxCollection = { + [policyKey]: { + exporter: adminEmail, + approver: adminEmail, + approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL, + role: CONST.POLICY.ROLE.ADMIN, + // Failed connection + connections: { + [CONST.POLICY.CONNECTIONS.NAME.NETSUITE]: { + verified: false, + lastSync: { + errorDate: new Date().toISOString(), + errorMessage: 'Error', + isAuthenticationError: true, + isConnected: false, + isSuccessful: false, + source: 'NEWEXPENSIFY', + successfulDate: '', + }, + }, + } as Connections, + } as OnyxTypes.Policy, + }; + + const response = SearchUIUtils.getSuggestedSearchesVisibility(adminEmail, {}, policies, {}, adminAccountID); + expect(response.export).toBe(false); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + policies[policyKey]!.connections![CONST.POLICY.CONNECTIONS.NAME.NETSUITE].lastSync = { + errorDate: '', + errorMessage: '', + isAuthenticationError: false, + isConnected: true, + isSuccessful: true, + source: 'NEWEXPENSIFY', + successfulDate: new Date().toISOString(), + }; + + const response2 = SearchUIUtils.getSuggestedSearchesVisibility(adminEmail, {}, policies, {}, adminAccountID); + expect(response2.export).toBe(true); + }); + }); + describe('Test getColumnsToShow', () => { test('Should only show columns when at least one transaction has a value for them', () => { // Use the existing transaction as a base and modify only the fields we need to test