diff --git a/src/CONST.ts b/src/CONST.ts index 9ed2903941b6..6e49ad9c12bc 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1472,6 +1472,8 @@ const CONST = { ALPHABETIC_AND_LATIN_CHARS: /^[\p{Script=Latin} ]*$/u, NON_ALPHABETIC_AND_NON_LATIN_CHARS: /[^\p{Script=Latin}]/gu, ACCENT_LATIN_CHARS: /[\u00C0-\u017F]/g, + INVALID_DISPLAY_NAME_LHN: /[^\p{L}\p{N}\u00C0-\u017F\s-]/gu, + INVALID_DISPLAY_NAME_ONLY_LHN: /^[^\p{L}\p{N}\u00C0-\u017F]$/gu, POSITIVE_INTEGER: /^\d+$/, PO_BOX: /\b[P|p]?(OST|ost)?\.?\s*[O|o|0]?(ffice|FFICE)?\.?\s*[B|b][O|o|0]?[X|x]?\.?\s+[#]?(\d+)\b/, ANY_VALUE: /^.+$/, diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 27f424ad1b70..f5545f402b14 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -32,6 +32,7 @@ function LHNOptionsList({ draftComments = {}, transactionViolations = {}, onFirstItemRendered = () => {}, + reportIDsWithErrors = {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); const {canUseViolations} = usePermissions(); @@ -63,6 +64,7 @@ function LHNOptionsList({ const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions); const lastReportAction = sortedReportActions[0]; + const reportErrors = reportIDsWithErrors[reportID] ?? {}; // Get the transaction for the last report action let lastReportActionTransactionID = ''; @@ -91,6 +93,7 @@ function LHNOptionsList({ transactionViolations={transactionViolations} canUseViolations={canUseViolations} onLayout={onLayoutItem} + reportErrors={reportErrors} /> ); }, @@ -109,6 +112,7 @@ function LHNOptionsList({ transactionViolations, canUseViolations, onLayoutItem, + reportIDsWithErrors, ], ); diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index a18d5a8ec1ec..a3394190d0c1 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -28,6 +28,7 @@ function OptionRowLHNData({ lastReportActionTransaction = {}, transactionViolations, canUseViolations, + reportErrors, ...propsToForward }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; @@ -40,11 +41,11 @@ function OptionRowLHNData({ // Note: ideally we'd have this as a dependent selector in onyx! const item = SidebarUtils.getOptionData({ report: fullReport, - reportActions, personalDetails, preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, policy, parentReportAction, + reportErrors, hasViolations: !!hasViolations, }); if (deepEqual(item, optionItemRef.current)) { @@ -69,6 +70,7 @@ function OptionRowLHNData({ transactionViolations, canUseViolations, receiptTransactions, + reportErrors, ]); useEffect(() => { diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 58bea97f04c9..c122ab018392 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -7,6 +7,7 @@ import type {CurrentReportIDContextValue} from '@components/withCurrentReportID' import type CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx'; +import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; type OptionMode = ValueOf; @@ -58,6 +59,9 @@ type CustomLHNOptionsListProps = { /** Callback to fire when the list is laid out */ onFirstItemRendered: () => void; + + /** Report IDs with errors mapping to their corresponding error objects */ + reportIDsWithErrors: Record; }; type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps; @@ -113,6 +117,9 @@ type OptionRowLHNDataProps = { /** Callback to execute when the OptionList lays out */ onLayout?: (event: LayoutChangeEvent) => void; + + /** The report errors */ + reportErrors: OnyxCommon.Errors | undefined; }; type OptionRowLHNProps = { diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index bdf1ba90583d..631bbfe4aa4a 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -481,7 +481,7 @@ function getSearchText( /** * Get an object of error messages keyed by microtime by combining all error objects related to the report. */ -function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry): OnyxCommon.Errors { +function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry, transactions: OnyxCollection = allTransactions): OnyxCommon.Errors { const reportErrors = report?.errors ?? {}; const reportErrorFields = report?.errorFields ?? {}; const reportActionErrors: OnyxCommon.ErrorFields = Object.values(reportActions ?? {}).reduce( @@ -493,7 +493,7 @@ function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry< if (parentReportAction?.actorAccountID === currentUserAccountID && ReportActionUtils.isTransactionThread(parentReportAction)) { const transactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction?.originalMessage?.IOUTransactionID : null; - const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const transaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; if (TransactionUtils.hasMissingSmartscanFields(transaction ?? null) && !ReportUtils.isSettled(transaction?.reportID)) { reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxError('report.genericSmartscanFailureMessage'); } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index a9cbefddec94..841640d64b93 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -7,6 +7,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, PersonalDetailsList, TransactionViolation} from '@src/types/onyx'; import type Beta from '@src/types/onyx/Beta'; +import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type Policy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type {ReportActions} from '@src/types/onyx/ReportAction'; @@ -59,6 +60,13 @@ function compareStringDates(a: string, b: string): 0 | 1 | -1 { return 0; } +function filterDisplayName(displayName: string): string { + if (CONST.REGEX.INVALID_DISPLAY_NAME_ONLY_LHN.test(displayName)) { + return displayName; + } + return displayName.replace(CONST.REGEX.INVALID_DISPLAY_NAME_LHN, '').trim(); +} + /** * @returns An array of reportIDs sorted in the proper order */ @@ -68,22 +76,27 @@ function getOrderedReportIDs( betas: Beta[], policies: Record, priorityMode: ValueOf, - allReportActions: OnyxCollection, + allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], + reportIDsWithErrors: Record = {}, + canUseViolations = false, ): string[] { const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; const allReportsDictValues = Object.values(allReports); + const reportIDsWithViolations = new Set(); + // Filter out all the reports that shouldn't be displayed let reportsToDisplay = allReportsDictValues.filter((report) => { const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`; - const parentReportActions = allReportActions?.[parentReportActionsKey]; - const parentReportAction = parentReportActions?.find((action) => action && report && action?.reportActionID === report?.parentReportActionID); - const doesReportHaveViolations = - betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); + const parentReportAction = allReportActions?.[parentReportActionsKey]?.[report.parentReportActionID ?? '']; + const doesReportHaveViolations = canUseViolations && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); + if (doesReportHaveViolations) { + reportIDsWithViolations.add(report.reportID); + } return ReportUtils.shouldReportBeInOptionList({ report, currentReportId: currentReportId ?? '', @@ -105,7 +118,7 @@ function getOrderedReportIDs( } // The LHN is split into four distinct groups, and each group is sorted a little differently. The groups will ALWAYS be in this order: - // 1. Pinned/GBR - Always sorted by reportDisplayName + // 1. Pinned/GBR/RBR - Always sorted by reportDisplayName // 2. Drafts - Always sorted by reportDisplayName // 3. Non-archived reports and settled IOUs // - Sorted by lastVisibleActionCreated in default (most recent) view mode @@ -113,7 +126,7 @@ function getOrderedReportIDs( // 4. Archived reports // - Sorted by lastVisibleActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode - const pinnedAndGBRReports: Report[] = []; + const pinnedAndBrickRoadReports: Report[] = []; const draftReports: Report[] = []; const nonArchivedReports: Report[] = []; const archivedReports: Report[] = []; @@ -127,12 +140,14 @@ function getOrderedReportIDs( // However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add // the reportDisplayName property to the report object directly. // eslint-disable-next-line no-param-reassign - report.displayName = ReportUtils.getReportName(report); + report.displayName = filterDisplayName(ReportUtils.getReportName(report)); + + const hasRBR = report.reportID in reportIDsWithErrors || reportIDsWithViolations.has(report.reportID); const isPinned = report.isPinned ?? false; const reportAction = ReportActionsUtils.getReportAction(report.parentReportID ?? '', report.parentReportActionID ?? ''); - if (isPinned || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) { - pinnedAndGBRReports.push(report); + if (isPinned || hasRBR || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) { + pinnedAndBrickRoadReports.push(report); } else if (report.hasDraft) { draftReports.push(report); } else if (ReportUtils.isArchivedRoom(report)) { @@ -143,7 +158,7 @@ function getOrderedReportIDs( }); // Sort each group of reports accordingly - pinnedAndGBRReports.sort((a, b) => (a?.displayName && b?.displayName ? localeCompare(a.displayName, b.displayName) : 0)); + pinnedAndBrickRoadReports.sort((a, b) => (a?.displayName && b?.displayName ? localeCompare(a.displayName, b.displayName) : 0)); draftReports.sort((a, b) => (a?.displayName && b?.displayName ? localeCompare(a.displayName, b.displayName) : 0)); if (isInDefaultMode) { @@ -161,7 +176,7 @@ function getOrderedReportIDs( // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); + const LHNReports = [...pinnedAndBrickRoadReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); return LHNReports; } @@ -170,19 +185,19 @@ function getOrderedReportIDs( */ function getOptionData({ report, - reportActions, personalDetails, preferredLocale, policy, parentReportAction, + reportErrors, hasViolations, }: { report: OnyxEntry; - reportActions: OnyxEntry; personalDetails: OnyxEntry; preferredLocale: DeepValueOf; policy: OnyxEntry | undefined; parentReportAction: OnyxEntry | undefined; + reportErrors: OnyxCommon.Errors | undefined; hasViolations: boolean; }): ReportUtils.OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for @@ -195,7 +210,7 @@ function getOptionData({ const result: ReportUtils.OptionData = { text: '', alternateText: null, - allReportErrors: OptionsListUtils.getAllReportErrors(report, reportActions), + allReportErrors: reportErrors, brickRoadIndicator: null, tooltipText: null, subtitle: null, diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index e872bbad008a..0a97f00c5002 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -43,7 +43,7 @@ const propTypes = { isActiveReport: PropTypes.func.isRequired, }; -function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy}) { +function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy, reportIDsWithErrors}) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const modal = useRef({}); @@ -154,6 +154,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority shouldDisableFocusOptions={isSmallScreenWidth} optionMode={viewMode} onFirstItemRendered={App.setSidebarLoaded} + reportIDsWithErrors={reportIDsWithErrors} /> {isLoading && optionListItems.length === 0 && ( diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index c4cc0713c596..3a1b17aa7fbd 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -1,6 +1,6 @@ import {deepEqual} from 'fast-equals'; import lodashGet from 'lodash/get'; -import lodashMap from 'lodash/map'; +import lodashMapValues from 'lodash/mapValues'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; @@ -13,9 +13,11 @@ import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultPro import withNavigationFocus from '@components/withNavigationFocus'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; @@ -35,20 +37,11 @@ const propTypes = { /** All report actions for all reports */ /** Object of report actions for this report */ - allReportActions: PropTypes.objectOf( - PropTypes.arrayOf( - PropTypes.shape({ - error: PropTypes.string, - message: PropTypes.arrayOf( - PropTypes.shape({ - moderationDecision: PropTypes.shape({ - decision: PropTypes.string, - }), - }), - ), - }), - ), - ), + // eslint-disable-next-line react/forbid-prop-types + allReportActions: PropTypes.object, + + // eslint-disable-next-line react/forbid-prop-types + allTransactions: PropTypes.object, /** Whether the reports are loading. When false it means they are ready to be used. */ isLoadingApp: PropTypes.bool, @@ -105,12 +98,14 @@ const defaultProps = { policyMembers: {}, transactionViolations: {}, allReportActions: {}, + allTransactions: {}, ...withCurrentUserPersonalDetailsDefaultProps, }; function SidebarLinksData({ isFocused, allReportActions, + allTransactions, betas, chatReports, currentReportID, @@ -128,12 +123,30 @@ function SidebarLinksData({ const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); const prevPriorityMode = usePrevious(priorityMode); + const {canUseViolations} = usePermissions(); const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, currentUserPersonalDetails.accountID); // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => Policy.openWorkspace(activeWorkspaceID, policyMemberAccountIDs), [activeWorkspaceID]); + const reportIDsWithErrors = useMemo(() => { + const reportKeys = _.keys(chatReports); + return _.reduce( + reportKeys, + (errorsMap, reportKey) => { + const report = chatReports[reportKey]; + const allReportsActions = allReportActions[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS)]; + const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions, allTransactions) || {}; + if (_.size(errors) === 0) { + return errorsMap; + } + return {...errorsMap, [reportKey.replace(ONYXKEYS.COLLECTION.REPORT, '')]: errors}; + }, + {}, + ); + }, [allReportActions, allTransactions, chatReports]); + const reportIDsRef = useRef(null); const isLoading = isLoadingApp; const optionListItems = useMemo(() => { @@ -147,6 +160,8 @@ function SidebarLinksData({ transactionViolations, activeWorkspaceID, policyMemberAccountIDs, + reportIDsWithErrors, + canUseViolations, ); if (deepEqual(reportIDsRef.current, reportIDs)) { @@ -160,7 +175,21 @@ function SidebarLinksData({ reportIDsRef.current = reportIDs; } return reportIDsRef.current || []; - }, [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, isLoading, network.isOffline, prevPriorityMode]); + }, [ + chatReports, + betas, + policies, + priorityMode, + allReportActions, + transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + reportIDsWithErrors, + canUseViolations, + isLoading, + network.isOffline, + prevPriorityMode, + ]); // We need to make sure the current report is in the list of reports, but we do not want // to have to re-generate the list every time the currentReportID changes. To do that @@ -179,10 +208,25 @@ function SidebarLinksData({ transactionViolations, activeWorkspaceID, policyMemberAccountIDs, + reportIDsWithErrors, + canUseViolations, ); } return optionListItems; - }, [currentReportID, optionListItems, chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs]); + }, [ + currentReportID, + optionListItems, + chatReports, + betas, + policies, + priorityMode, + allReportActions, + transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + reportIDsWithErrors, + canUseViolations, + ]); const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; @@ -204,6 +248,7 @@ function SidebarLinksData({ isLoading={isLoading} optionListItems={optionListItemsWithCurrentReport} activeWorkspaceID={activeWorkspaceID} + reportIDsWithErrors={reportIDsWithErrors} /> ); @@ -227,6 +272,7 @@ const chatReportSelector = (report) => isPinned: report.isPinned, isHidden: report.isHidden, notificationPreference: report.notificationPreference, + errors: report.errors, errorFields: { addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom, }, @@ -248,6 +294,9 @@ const chatReportSelector = (report) => reportName: report.reportName, policyName: report.policyName, oldPolicyName: report.oldPolicyName, + isPolicyExpenseChat: report.isPolicyExpenseChat, + isOwnPolicyExpenseChat: report.isOwnPolicyExpenseChat, + isCancelledIOU: report.isCancelledIOU, // Other less obvious properites considered for sorting: ownerAccountID: report.ownerAccountID, currency: report.currency, @@ -265,7 +314,7 @@ const chatReportSelector = (report) => */ const reportActionsSelector = (reportActions) => reportActions && - lodashMap(reportActions, (reportAction) => { + lodashMapValues(reportActions, (reportAction) => { const {reportActionID, parentReportActionID, actionName, errors = [], originalMessage} = reportAction; const decision = lodashGet(reportAction, 'message[0].moderationDecision.decision'); @@ -294,6 +343,24 @@ const policySelector = (policy) => avatar: policy.avatar, }; +/** + * @param {Object} [transaction] + * @returns {Object|undefined} + */ +const transactionSelector = (transaction) => + transaction && { + reportID: transaction.reportID, + iouRequestType: transaction.iouRequestType, + comment: transaction.comment, + receipt: transaction.receipt, + merchant: transaction.merchant, + modifiedMerchant: transaction.modifiedMerchant, + amount: transaction.amount, + modifiedAmount: transaction.modifiedAmount, + created: transaction.created, + modifiedCreated: transaction.modifiedCreated, + }; + export default compose( withCurrentReportID, withCurrentUserPersonalDetails, @@ -321,6 +388,11 @@ export default compose( selector: reportActionsSelector, initialValue: {}, }, + allTransactions: { + key: ONYXKEYS.COLLECTION.TRANSACTION, + selector: transactionSelector, + initialValue: {}, + }, policies: { key: ONYXKEYS.COLLECTION.POLICY, selector: policySelector, diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 3aa65331b9c2..2b2bdbc6b57a 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -1,13 +1,15 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import {measureFunction} from 'reassure'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetails, TransactionViolation} from '@src/types/onyx'; +import type {PersonalDetails, ReportActions, TransactionViolation} from '@src/types/onyx'; import type Policy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type ReportAction from '@src/types/onyx/ReportAction'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import createCollection from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; @@ -61,11 +63,11 @@ describe('SidebarUtils', () => { await measureFunction(() => SidebarUtils.getOptionData({ report, - reportActions, personalDetails, preferredLocale, policy, parentReportAction, + reportErrors: undefined, hasViolations: false, }), ); @@ -85,8 +87,8 @@ describe('SidebarUtils', () => { const allReportActions = Object.fromEntries( Object.keys(reportActions).map((key) => [ key, - [ - { + { + [reportActions[key].reportActionID]: { errors: reportActions[key].errors ?? [], message: [ { @@ -96,11 +98,35 @@ describe('SidebarUtils', () => { }, ], }, - ], + }, ]), - ) as unknown as OnyxCollection; + ) as unknown as OnyxCollection; + + const reportKeys = Object.keys(allReports); + const reportIDsWithErrors = reportKeys.reduce((errorsMap, reportKey) => { + const report = allReports[reportKey]; + const allReportsActions = allReportActions?.[reportKey.replace('report_', 'reportActions_')] ?? null; + const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions) || {}; + if (isEmptyObject(errors)) { + return errorsMap; + } + return {...errorsMap, [reportKey.replace('report_', '')]: errors}; + }, {}); await waitForBatchedUpdates(); - await measureFunction(() => SidebarUtils.getOrderedReportIDs(currentReportId, allReports, betas, policies, CONST.PRIORITY_MODE.DEFAULT, allReportActions, transactionViolations)); + await measureFunction(() => + SidebarUtils.getOrderedReportIDs( + currentReportId, + allReports, + betas, + policies, + CONST.PRIORITY_MODE.DEFAULT, + allReportActions, + transactionViolations, + undefined, + undefined, + reportIDsWithErrors, + ), + ); }); });