diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 356d18a04f4d..1838ae63dd3e 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -23,7 +23,7 @@ import getPlatform from '@libs/getPlatform'; import Log from '@libs/Log'; import {getIOUReportIDOfLastAction, getLastMessageTextForReport, hasReportErrors} from '@libs/OptionsListUtils'; import {getOneTransactionThreadReportID, getOriginalMessage, getSortedReportActionsForDisplay, isMoneyRequestAction} from '@libs/ReportActionsUtils'; -import {canUserPerformWriteAction, requiresAttentionFromCurrentUser} from '@libs/ReportUtils'; +import {canUserPerformWriteAction, getReportAttributes} from '@libs/ReportUtils'; import isProductTrainingElementDismissed from '@libs/TooltipUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -76,14 +76,12 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio if (hasReportErrors(report, itemReportActions)) { return true; } - const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`]; - const itemParentReportAction = report?.parentReportActionID ? itemParentReportActions?.[report?.parentReportActionID] : undefined; - const hasGBR = requiresAttentionFromCurrentUser(report, itemParentReportAction); + const hasGBR = getReportAttributes(report.reportID, reportAttributes)?.requiresAttention; return hasGBR; }); return firstReportWithGBRorRBR?.reportID; - }, [isGBRorRBRTooltipDismissed, data, reportActions]); + }, [isGBRorRBRTooltipDismissed, data, reportActions, reportAttributes]); // When the first item renders we want to call the onFirstItemRendered callback. // At this point in time we know that the list is actually displaying items. diff --git a/src/components/Navigation/NavigationTabBar/index.tsx b/src/components/Navigation/NavigationTabBar/index.tsx index 10003bbf5600..f607b8c9a3a4 100644 --- a/src/components/Navigation/NavigationTabBar/index.tsx +++ b/src/components/Navigation/NavigationTabBar/index.tsx @@ -53,7 +53,7 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar const {activeWorkspaceID} = useActiveWorkspace(); const {orderedReports} = useSidebarOrderedReports(); const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false}); - const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true}); + const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports, canBeMissing: true}); const {shouldUseNarrowLayout} = useResponsiveLayout(); const [chatTabBrickRoad, setChatTabBrickRoad] = useState(undefined); const platform = getPlatform(); diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index 400e878780c3..52af06aafe48 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -51,14 +51,15 @@ function SidebarOrderedReportsContextProvider({ */ currentReportIDForTests, }: SidebarOrderedReportsContextProviderProps) { - const [priorityMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE, {initialValue: CONST.PRIORITY_MODE.DEFAULT}); - const [chatReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); - const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: (c) => mapOnyxCollectionItems(c, policySelector)}); - const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); - const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS); - const [reportsDrafts] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {initialValue: {}}); + const [priorityMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE, {initialValue: CONST.PRIORITY_MODE.DEFAULT, canBeMissing: true}); + const [chatReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); + const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: (c) => mapOnyxCollectionItems(c, policySelector), canBeMissing: true}); + const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); + const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {canBeMissing: true}); + const [reportsDrafts] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {initialValue: {}, canBeMissing: true}); const draftAmount = Object.keys(reportsDrafts ?? {}).length; - const [betas] = useOnyx(ONYXKEYS.BETAS); + const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true}); + const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports, canBeMissing: true}); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {accountID} = useCurrentUserPersonalDetails(); @@ -80,10 +81,11 @@ function SidebarOrderedReportsContextProvider({ activeWorkspaceID, policyMemberAccountIDs, reportNameValuePairs, + reportAttributes, ), // we need reports draft in deps array to reload the list when a draft is added or removed // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - [chatReports, betas, policies, priorityMode, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, draftAmount, reportNameValuePairs], + [chatReports, betas, policies, priorityMode, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, draftAmount, reportNameValuePairs, reportAttributes], ); const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2c582e6ba802..35eed9157655 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -707,7 +707,6 @@ type OptionData = { alternateText?: string; allReportErrors?: Errors; brickRoadIndicator?: ValueOf | '' | null; - shouldShowGreenDot?: boolean; tooltipText?: string | null; alternateTextMaxLines?: number; boldStyle?: boolean; @@ -1059,14 +1058,14 @@ Onyx.connect({ callback: (value) => (activePolicyID = value), }); -let reportAttributes: ReportAttributesDerivedValue['reports']; +let reportAttributesDerivedValue: ReportAttributesDerivedValue['reports']; Onyx.connect({ key: ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, callback: (value) => { if (!value) { return; } - reportAttributes = value.reports; + reportAttributesDerivedValue = value.reports; }, }); @@ -4647,11 +4646,11 @@ function getReportName( parentReportActionParam?: OnyxInputOrEntry, personalDetails?: Partial, invoiceReceiverPolicy?: OnyxEntry, - reportAttributesParam?: ReportAttributesDerivedValue['reports'], + reportAttributes?: ReportAttributesDerivedValue['reports'], ): string { // Check if we can use report name in derived values - only when we have report but no other params const canUseDerivedValue = report && policy === undefined && parentReportActionParam === undefined && personalDetails === undefined && invoiceReceiverPolicy === undefined; - const attributes = reportAttributesParam ?? reportAttributes; + const attributes = reportAttributes ?? reportAttributesDerivedValue; const derivedNameExists = report && !!attributes?.[report.reportID]?.reportName; if (canUseDerivedValue && derivedNameExists) { return attributes[report.reportID].reportName; @@ -10754,6 +10753,15 @@ function getReportPersonalDetailsParticipants(report: Report, personalDetailsPar }; } +function getReportAttributes(reportID: string | undefined, reportAttributes?: ReportAttributesDerivedValue['reports']) { + const attributes = reportAttributes ?? reportAttributesDerivedValue; + + if (!reportID || !attributes?.[reportID]) { + return; + } + return attributes[reportID]; +} + export { addDomainToShortMention, completeShortMention, @@ -11130,6 +11138,7 @@ export { getReportPersonalDetailsParticipants, isAllowedToSubmitDraftExpenseReport, isWorkspaceEligibleForReportChange, + getReportAttributes, }; export type { diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index ef3837444eee..8cc24868d11a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -5,7 +5,7 @@ import type {ValueOf} from 'type-fest'; import type {PartialPolicyForSidebar} from '@hooks/useSidebarOrderedReports'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetails, PersonalDetailsList, ReportActions, ReportNameValuePairs, TransactionViolation} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, ReportActions, ReportAttributesDerivedValue, ReportNameValuePairs, TransactionViolation} from '@src/types/onyx'; import type Beta from '@src/types/onyx/Beta'; import type {ReportAttributes} from '@src/types/onyx/DerivedValues'; import type Policy from '@src/types/onyx/Policy'; @@ -76,6 +76,7 @@ import { getIcons, getParticipantsAccountIDsForDisplay, getPolicyName, + getReportAttributes, getReportDescription, getReportName, getReportNameValuePairs, @@ -111,7 +112,6 @@ import { isThread, isUnread, isUnreadWithMention, - requiresAttentionFromCurrentUser, shouldDisplayViolationsRBRInLHN, shouldReportBeInOptionList, shouldReportShowSubscript, @@ -202,6 +202,7 @@ function getOrderedReportIDs( currentPolicyID = '', policyMemberAccountIDs: number[] = [], reportNameValuePairs?: OnyxCollection, + reportAttributes?: ReportAttributesDerivedValue['reports'], ): string[] { Performance.markStart(CONST.TIMING.GET_ORDERED_REPORT_IDS); const isInFocusMode = priorityMode === CONST.PRIORITY_MODE.GSD; @@ -244,7 +245,7 @@ function getOrderedReportIDs( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing report.isPinned || (!isInFocusMode && isArchivedReport(reportNameValuePairs)) || - requiresAttentionFromCurrentUser(report, parentReportAction); + getReportAttributes(report.reportID, reportAttributes)?.requiresAttention; if (isHidden && !shouldOverrideHidden) { return; } @@ -292,9 +293,8 @@ function getOrderedReportIDs( }; const isPinned = report?.isPinned ?? false; - const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); const rNVPs = reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`]; - if (isPinned || requiresAttentionFromCurrentUser(report, reportAction)) { + if (isPinned || getReportAttributes(report?.reportID, reportAttributes)?.requiresAttention) { pinnedAndGBRReports.push(miniReport); } else if (report?.hasErrorsOtherThanFailedReceipt) { errorReports.push(miniReport); @@ -473,7 +473,6 @@ function getOptionData({ isAllowedToComment: true, isDeletedParentAction: false, isConciergeChat: false, - shouldShowGreenDot: false, }; const participantAccountIDs = getParticipantsAccountIDsForDisplay(report); @@ -520,7 +519,6 @@ function getOptionData({ result.hasParentAccess = report.hasParentAccess; result.isConciergeChat = isConciergeChatReport(report); result.participants = report.participants; - result.shouldShowGreenDot = result.brickRoadIndicator !== CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && requiresAttentionFromCurrentUser(report, parentReportAction); const isExpense = isExpenseReport(report); const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat || isExpense; diff --git a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts index 5b1ac21088c6..b9a6d3d3df54 100644 --- a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts +++ b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts @@ -73,13 +73,13 @@ export default createOnyxDerivedValueConfig({ dataToIterate = prepareReportKeys(updates); recentlyUpdated = updates; } else if (!!transactionsUpdates || !!transactionViolationsUpdates) { - let transactionReportIds: string[] = []; + let transactionReportIDs: string[] = []; if (transactionsUpdates) { - transactionReportIds = Object.values(transactionsUpdates).map((transaction) => `${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); + transactionReportIDs = Object.values(transactionsUpdates).map((transaction) => `${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); } // if transactions are updated, they might not be directly related to the reports yet (e.g. transaction is optimistically created) // so we use report keys that were updated before to recompute the reports - const recentReportKeys = prepareReportKeys([...recentlyUpdated, ...transactionReportIds]); + const recentReportKeys = prepareReportKeys([...recentlyUpdated, ...transactionReportIDs]); dataToIterate = recentReportKeys; } } @@ -112,6 +112,7 @@ export default createOnyxDerivedValueConfig({ acc[report.reportID] = { reportName: generateReportName(report), brickRoadStatus, + requiresAttention, }; return acc; diff --git a/src/types/onyx/DerivedValues.ts b/src/types/onyx/DerivedValues.ts index fd380284d5c9..6502f4d96b41 100644 --- a/src/types/onyx/DerivedValues.ts +++ b/src/types/onyx/DerivedValues.ts @@ -13,6 +13,10 @@ type ReportAttributes = { * The status of the brick road. */ brickRoadStatus: ValueOf | undefined; + /** + * Whether the report requires attention from current user. + */ + requiresAttention: boolean; }; /** diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index d661c13ddbb2..e0a6da42d84a 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -4,6 +4,7 @@ import Onyx from 'react-native-onyx'; import {addComment} from '@libs/actions/Report'; import DateUtils from '@libs/DateUtils'; import {translateLocal} from '@libs/Localize'; +import initOnyxDerivedValues from '@userActions/OnyxDerived'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; @@ -29,12 +30,13 @@ jest.mock('@react-navigation/native', () => ({ jest.mock('@components/ConfirmedRoute.tsx'); describe('Sidebar', () => { - beforeAll(() => + beforeAll(() => { Onyx.init({ keys: ONYXKEYS, evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], - }), - ); + }); + initOnyxDerivedValues(); + }); beforeEach(() => { // Wrap Onyx each onyx action with waitForBatchedUpdates