From b69416dfd43fac9ef60d37d5a5dd7f054384e524 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 7 May 2025 10:26:23 +0200 Subject: [PATCH 01/11] migrate requiresAttentionFromCurrentUser to a derived value --- .../LHNOptionsList/LHNOptionsList.tsx | 8 +- src/libs/SidebarUtils.ts | 21 ++++-- src/libs/WorkspacesSettingsUtils.ts | 75 +++---------------- .../OnyxDerived/configs/reportAttributes.ts | 1 + src/pages/WorkspaceSwitcherPage/index.tsx | 2 +- src/types/onyx/DerivedValues.ts | 4 + 6 files changed, 33 insertions(+), 78 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index b5eba4588064..19a88620ce75 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} from '@libs/ReportUtils'; import isProductTrainingElementDismissed from '@libs/TooltipUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -77,12 +77,10 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio if (hasReportErrors(itemFullReport, itemReportActions)) { return true; } - const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`]; - const itemParentReportAction = itemFullReport?.parentReportActionID ? itemParentReportActions?.[itemFullReport?.parentReportActionID] : undefined; - const hasGBR = requiresAttentionFromCurrentUser(itemFullReport, itemParentReportAction); + const hasGBR = reportAttributes?.[reportID]?.requiresAttention; return hasGBR; }); - }, [isGBRorRBRTooltipDismissed, data, reportActions, reports]); + }, [isGBRorRBRTooltipDismissed, data, reports, 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/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 01fab708ec58..15fce3fe19d9 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/useSidebarOrderedReportIDs'; 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'; @@ -110,7 +110,6 @@ import { isThread, isUnread, isUnreadWithMention, - requiresAttentionFromCurrentUser, shouldDisplayViolationsRBRInLHN, shouldReportBeInOptionList, shouldReportShowSubscript, @@ -164,6 +163,17 @@ Onyx.connect({ }, }); +let reportAttributesDerivedValue: ReportAttributesDerivedValue['reports'] | undefined; +Onyx.connect({ + key: ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, + callback: (value) => { + if (!value) { + return; + } + reportAttributesDerivedValue = value?.reports; + }, +}); + function compareStringDates(a: string, b: string): 0 | 1 | -1 { if (a < b) { return -1; @@ -243,7 +253,7 @@ function getOrderedReportIDs( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing report.isPinned || (!isInFocusMode && isArchivedReport(reportNameValuePairs)) || - requiresAttentionFromCurrentUser(report, parentReportAction); + reportAttributesDerivedValue?.[report.reportID]?.requiresAttention; if (isHidden && !shouldOverrideHidden) { return; } @@ -291,9 +301,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 || reportAttributesDerivedValue?.[report?.reportID]?.requiresAttention) { pinnedAndGBRReports.push(miniReport); } else if (report?.hasErrorsOtherThanFailedReceipt) { errorReports.push(miniReport); @@ -519,7 +528,7 @@ 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); + result.shouldShowGreenDot = result.brickRoadIndicator !== CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && reportAttributesDerivedValue?.[report?.reportID]?.requiresAttention; const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat || isExpenseReport(report); const subtitle = getChatRoomSubtitle(report); diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index 0ab5050c540d..7d2758835f98 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -5,14 +5,14 @@ import type {LocaleContextProps} from '@components/LocaleContextProvider'; 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 {Policy, ReimbursementAccount, Report, ReportActions, ReportAttributesDerivedValue} from '@src/types/onyx'; import type {PolicyConnectionSyncProgress, Unit} from '@src/types/onyx/Policy'; import {isConnectionInProgress} from './actions/connections'; import {shouldShowQBOReimbursableExportDestinationAccountError} from './actions/connections/QuickbooksOnline'; import {convertToDisplayString} from './CurrencyUtils'; import {isPolicyAdmin, shouldShowCustomUnitsError, shouldShowEmployeeListError, shouldShowPolicyError, shouldShowSyncError, shouldShowTaxRateError} from './PolicyUtils'; import {getOneTransactionThreadReportID} from './ReportActionsUtils'; -import {getAllReportErrors, hasReportViolations, isReportOwner, isUnread, isUnreadWithMention, requiresAttentionFromCurrentUser, shouldDisplayViolationsRBRInLHN} from './ReportUtils'; +import {isUnread} from './ReportUtils'; type CheckingMethod = () => boolean; @@ -27,38 +27,14 @@ Onyx.connect({ }, }); -let allReportActions: OnyxCollection; +let reportAttributes: ReportAttributesDerivedValue['reports'] | undefined; Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - waitForCollectionCallback: true, - callback: (actions) => { - if (!actions) { - return; - } - allReportActions = actions; - }, -}); - -let reportsCollection: OnyxCollection; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallback: true, - callback: (value) => { - reportsCollection = value; - }, -}); - -let allTransactionViolations: NonNullable> = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - waitForCollectionCallback: true, + key: ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, callback: (value) => { if (!value) { - allTransactionViolations = {}; return; } - - allTransactionViolations = value; + reportAttributes = value?.reports; }, }); @@ -66,41 +42,8 @@ Onyx.connect({ * @param altReportActions Replaces (local) allReportActions used within (local) function getWorkspacesBrickRoads * @returns BrickRoad for the policy passed as a param and optionally actionsByReport (if passed) */ -const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection): BrickRoad => { - const reportActions = (altReportActions ?? allReportActions)?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {}; - const reportErrors = getAllReportErrors(report, reportActions); - const oneTransactionThreadReportID = getOneTransactionThreadReportID(report.reportID, reportActions); - const oneTransactionThreadReport = reportsCollection?.[`${ONYXKEYS.COLLECTION.REPORT}${oneTransactionThreadReportID}`]; - let doesReportContainErrors = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; - - if (!doesReportContainErrors) { - const shouldDisplayViolations = shouldDisplayViolationsRBRInLHN(report, allTransactionViolations); - const shouldDisplayReportViolations = isReportOwner(report) && hasReportViolations(report.reportID); - const hasViolations = shouldDisplayViolations || shouldDisplayReportViolations; - if (hasViolations) { - doesReportContainErrors = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; - } - } - - if (oneTransactionThreadReportID && !doesReportContainErrors) { - if (shouldDisplayViolationsRBRInLHN(oneTransactionThreadReport, allTransactionViolations)) { - doesReportContainErrors = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; - } - } - - if (doesReportContainErrors) { - return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; - } - - // To determine if the report requires attention from the current user, we need to load the parent report action - let itemParentReportAction: OnyxEntry; - if (report.parentReportID) { - const itemParentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`] ?? {}; - itemParentReportAction = report.parentReportActionID ? itemParentReportActions[report.parentReportActionID] : undefined; - } - const reportOption = {...report, isUnread: isUnread(report, oneTransactionThreadReport), isUnreadWithMention: isUnreadWithMention(report)}; - const shouldShowGreenDotIndicator = requiresAttentionFromCurrentUser(reportOption, itemParentReportAction); - return shouldShowGreenDotIndicator ? CONST.BRICK_ROAD_INDICATOR_STATUS.INFO : undefined; +const getBrickRoadForPolicy = (report: Report): BrickRoad => { + return reportAttributes?.[report.reportID]?.brickRoadStatus; }; function hasGlobalWorkspaceSettingsRBR(policies: OnyxCollection, allConnectionProgresses: OnyxCollection) { @@ -175,7 +118,7 @@ function getChatTabBrickRoad(policyID: string | undefined, orderedReports: Array /** * @returns a map where the keys are policyIDs and the values are BrickRoads for each policy */ -function getWorkspacesBrickRoads(reports: OnyxCollection, policies: OnyxCollection, reportActions: OnyxCollection): Record { +function getWorkspacesBrickRoads(reports: OnyxCollection, policies: OnyxCollection): Record { if (!reports) { return {}; } @@ -198,7 +141,7 @@ function getWorkspacesBrickRoads(reports: OnyxCollection, policies: Onyx if (!report || workspacesBrickRoadsMap[policyID] === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR) { return; } - const workspaceBrickRoad = getBrickRoadForPolicy(report, reportActions); + const workspaceBrickRoad = getBrickRoadForPolicy(report); if (!workspaceBrickRoad && !!workspacesBrickRoadsMap[policyID]) { return; diff --git a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts index 06ff411fb7ac..fac0ba605156 100644 --- a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts +++ b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts @@ -108,6 +108,7 @@ export default createOnyxDerivedValueConfig({ acc[report.reportID] = { reportName: generateReportName(report), brickRoadStatus, + requiresAttention, }; return acc; diff --git a/src/pages/WorkspaceSwitcherPage/index.tsx b/src/pages/WorkspaceSwitcherPage/index.tsx index 6bbcee7b5129..b3a98f17c2d4 100644 --- a/src/pages/WorkspaceSwitcherPage/index.tsx +++ b/src/pages/WorkspaceSwitcherPage/index.tsx @@ -34,7 +34,7 @@ function WorkspaceSwitcherPage() { const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email}); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); - const brickRoadsForPolicies = useMemo(() => getWorkspacesBrickRoads(reports, policies, reportActions), [reports, policies, reportActions]); + const brickRoadsForPolicies = useMemo(() => getWorkspacesBrickRoads(reports, policies), [reports, policies]); const unreadStatusesForPolicies = useMemo(() => getWorkspacesUnreadStatuses(reports, reportActions), [reports, reportActions]); const shouldShowLoadingIndicator = isLoadingApp && !isOffline; 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; }; /** From 49526e3c704772fe347e26f2271c88de9da85efa Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 7 May 2025 15:55:08 +0200 Subject: [PATCH 02/11] fix typecheck --- tests/unit/WorkspaceSettingsUtilsTest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts index 088a97e2ca20..de077f7e0b6f 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.ts +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -40,7 +40,7 @@ describe('WorkspacesSettingsUtils', () => { await waitForBatchedUpdates(); // When calling getBrickRoadForPolicy with a report and report actions - const result = getBrickRoadForPolicy(report as Report, reportActions as OnyxCollection); + const result = getBrickRoadForPolicy(report as Report); // The result should be 'error' because there is at least one IOU action associated with a transaction that has a violation. expect(result).toBe('error'); @@ -62,7 +62,7 @@ describe('WorkspacesSettingsUtils', () => { await waitForBatchedUpdates(); // When calling getBrickRoadForPolicy with a report and report actions - const result = getBrickRoadForPolicy(report as Report, reportActions as OnyxCollection); + const result = getBrickRoadForPolicy(report as Report); // Then the result should be 'undefined' since no IOU action is linked to a transaction with a violation. expect(result).toBe(undefined); From 81d1056d4de856b881a6f5fdd7f72c50f0a4b3f0 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Thu, 8 May 2025 08:38:12 +0200 Subject: [PATCH 03/11] fix lint changed --- src/pages/WorkspaceSwitcherPage/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/WorkspaceSwitcherPage/index.tsx b/src/pages/WorkspaceSwitcherPage/index.tsx index b3a98f17c2d4..5ce164f6d71d 100644 --- a/src/pages/WorkspaceSwitcherPage/index.tsx +++ b/src/pages/WorkspaceSwitcherPage/index.tsx @@ -29,11 +29,11 @@ function WorkspaceSwitcherPage() { const {translate} = useLocalize(); const {activeWorkspaceID} = useActiveWorkspace(); - const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); - const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); - const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY); - const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email}); - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); + const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {canBeMissing: true}); + const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); + const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email, canBeMissing: true}); + const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: true}); const brickRoadsForPolicies = useMemo(() => getWorkspacesBrickRoads(reports, policies), [reports, policies]); const unreadStatusesForPolicies = useMemo(() => getWorkspacesUnreadStatuses(reports, reportActions), [reports, reportActions]); const shouldShowLoadingIndicator = isLoadingApp && !isOffline; From 38e9a46e0e8180a82913bfbacbc5b7047854ba17 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Thu, 8 May 2025 09:39:50 +0200 Subject: [PATCH 04/11] fix SidebarOrderTest --- src/hooks/useSidebarOrderedReportIDs.tsx | 4 +++- src/libs/SidebarUtils.ts | 5 +++-- tests/unit/SidebarOrderTest.ts | 8 +++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/hooks/useSidebarOrderedReportIDs.tsx b/src/hooks/useSidebarOrderedReportIDs.tsx index 8f9e7b85305b..32b8de795f6c 100644 --- a/src/hooks/useSidebarOrderedReportIDs.tsx +++ b/src/hooks/useSidebarOrderedReportIDs.tsx @@ -59,6 +59,7 @@ function SidebarOrderedReportIDsContextProvider({ const [reportsDrafts] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {initialValue: {}}); const draftAmount = Object.keys(reportsDrafts ?? {}).length; const [betas] = useOnyx(ONYXKEYS.BETAS); + const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports}); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {accountID} = useCurrentUserPersonalDetails(); @@ -80,10 +81,11 @@ function SidebarOrderedReportIDsContextProvider({ 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/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 036c68a2dfff..f5f6d3969bc8 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -211,6 +211,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; @@ -253,7 +254,7 @@ function getOrderedReportIDs( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing report.isPinned || (!isInFocusMode && isArchivedReport(reportNameValuePairs)) || - reportAttributesDerivedValue?.[report.reportID]?.requiresAttention; + reportAttributes?.[report.reportID]?.requiresAttention; if (isHidden && !shouldOverrideHidden) { return; } @@ -302,7 +303,7 @@ function getOrderedReportIDs( const isPinned = report?.isPinned ?? false; const rNVPs = reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`]; - if (isPinned || reportAttributesDerivedValue?.[report?.reportID]?.requiresAttention) { + if (isPinned || reportAttributes?.[report?.reportID]?.requiresAttention) { pinnedAndGBRReports.push(miniReport); } else if (report?.hasErrorsOtherThanFailedReceipt) { errorReports.push(miniReport); 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 From 29b960e675f1e016a3ec4d6f61088251572e2707 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Thu, 8 May 2025 09:54:42 +0200 Subject: [PATCH 05/11] fix tests --- src/components/Navigation/NavigationTabBar/index.tsx | 6 +++--- src/libs/actions/OnyxDerived/configs/reportAttributes.ts | 6 +++++- tests/ui/BottomTabBarTest.tsx | 2 ++ tests/unit/WorkspaceSettingsUtilsTest.ts | 4 +++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/Navigation/NavigationTabBar/index.tsx b/src/components/Navigation/NavigationTabBar/index.tsx index 84b1e644a5ff..f5d91e5b05e7 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 {orderedReportIDs} = useSidebarOrderedReportIDs(); const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false}); - const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {canBeMissing: true}); + const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports}); const [reports = []] = useOnyx(ONYXKEYS.COLLECTION.REPORT, { selector: (values) => orderedReportIDs.map((reportID) => values?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]), canBeMissing: true, @@ -74,8 +74,8 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar useEffect(() => { setChatTabBrickRoad(getChatTabBrickRoad(activeWorkspaceID, reports)); // We need to get a new brick road state when report actions are updated, otherwise we'll be showing an outdated brick road. - // That's why reportActions is added as a dependency here - }, [activeWorkspaceID, reports, reportActions]); + // That's why reportAttributes is added as a dependency here + }, [activeWorkspaceID, reports, reportAttributes]); const navigateToChats = useCallback(() => { if (selectedTab === NAVIGATION_TABS.HOME) { diff --git a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts index fac0ba605156..e9532c173059 100644 --- a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts +++ b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts @@ -73,9 +73,13 @@ export default createOnyxDerivedValueConfig({ dataToIterate = prepareReportKeys(updates); recentlyUpdated = updates; } else if (!!transactionsUpdates || !!transactionViolationsUpdates) { + let transactionReportIds: string[] = []; + if (transactionsUpdates) { + 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); + const recentReportKeys = prepareReportKeys([...recentlyUpdated, ...transactionReportIds]); dataToIterate = recentReportKeys; } } diff --git a/tests/ui/BottomTabBarTest.tsx b/tests/ui/BottomTabBarTest.tsx index 9d0d85e513c7..c02a975ce107 100644 --- a/tests/ui/BottomTabBarTest.tsx +++ b/tests/ui/BottomTabBarTest.tsx @@ -8,6 +8,7 @@ import NavigationTabBar from '@components/Navigation/NavigationTabBar'; import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS'; import OnyxProvider from '@components/OnyxProvider'; import {SidebarOrderedReportIDsContextProvider} from '@hooks/useSidebarOrderedReportIDs'; +import initOnyxDerivedValues from '@userActions/OnyxDerived'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -16,6 +17,7 @@ jest.mock('@src/hooks/useRootNavigationState'); describe('NavigationTabBar', () => { beforeAll(() => { Onyx.init({keys: ONYXKEYS}); + initOnyxDerivedValues(); }); beforeEach(() => { Onyx.clear(); diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts index de077f7e0b6f..731e49deec74 100644 --- a/tests/unit/WorkspaceSettingsUtilsTest.ts +++ b/tests/unit/WorkspaceSettingsUtilsTest.ts @@ -1,6 +1,7 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import {getBrickRoadForPolicy} from '@libs/WorkspacesSettingsUtils'; +import initOnyxDerivedValues from '@userActions/OnyxDerived'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report, ReportActions, Transaction, TransactionViolations} from '@src/types/onyx'; import type {ReportCollectionDataSet} from '@src/types/onyx/Report'; @@ -13,6 +14,7 @@ describe('WorkspacesSettingsUtils', () => { Onyx.init({ keys: ONYXKEYS, }); + initOnyxDerivedValues(); }); beforeEach(() => { @@ -39,7 +41,7 @@ describe('WorkspacesSettingsUtils', () => { await waitForBatchedUpdates(); - // When calling getBrickRoadForPolicy with a report and report actions + // When calling getBrickRoadForPolicy with a report const result = getBrickRoadForPolicy(report as Report); // The result should be 'error' because there is at least one IOU action associated with a transaction that has a violation. From 21cef0a8b18db40dc7fdb06534043319bcd31c14 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Thu, 8 May 2025 10:15:33 +0200 Subject: [PATCH 06/11] fix lint changed --- .../Navigation/NavigationTabBar/index.tsx | 2 +- src/hooks/useSidebarOrderedReportIDs.tsx | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/Navigation/NavigationTabBar/index.tsx b/src/components/Navigation/NavigationTabBar/index.tsx index f5d91e5b05e7..f6d62a87eab4 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 {orderedReportIDs} = useSidebarOrderedReportIDs(); const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false}); - const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports}); + const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports, canBeMissing: true}); const [reports = []] = useOnyx(ONYXKEYS.COLLECTION.REPORT, { selector: (values) => orderedReportIDs.map((reportID) => values?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]), canBeMissing: true, diff --git a/src/hooks/useSidebarOrderedReportIDs.tsx b/src/hooks/useSidebarOrderedReportIDs.tsx index 32b8de795f6c..c974b7f515ef 100644 --- a/src/hooks/useSidebarOrderedReportIDs.tsx +++ b/src/hooks/useSidebarOrderedReportIDs.tsx @@ -51,15 +51,15 @@ function SidebarOrderedReportIDsContextProvider({ */ currentReportIDForTests, }: SidebarOrderedReportIDsContextProviderProps) { - 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 [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports}); + 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(); From 407bd0e15ec9668419a0c87f302ce31facd29803 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Thu, 8 May 2025 13:38:38 +0200 Subject: [PATCH 07/11] remove unnecessary param --- src/libs/ReportUtils.ts | 1 - src/libs/SidebarUtils.ts | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4186ce34ea3e..3174bfa917dc 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -693,7 +693,6 @@ type OptionData = { alternateText?: string; allReportErrors?: Errors; brickRoadIndicator?: ValueOf | '' | null; - shouldShowGreenDot?: boolean; tooltipText?: string | null; alternateTextMaxLines?: number; boldStyle?: boolean; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index f5f6d3969bc8..0d439bd03489 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -163,17 +163,6 @@ Onyx.connect({ }, }); -let reportAttributesDerivedValue: ReportAttributesDerivedValue['reports'] | undefined; -Onyx.connect({ - key: ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, - callback: (value) => { - if (!value) { - return; - } - reportAttributesDerivedValue = value?.reports; - }, -}); - function compareStringDates(a: string, b: string): 0 | 1 | -1 { if (a < b) { return -1; @@ -482,7 +471,6 @@ function getOptionData({ isAllowedToComment: true, isDeletedParentAction: false, isConciergeChat: false, - shouldShowGreenDot: false, }; const participantAccountIDs = getParticipantsAccountIDsForDisplay(report); @@ -529,7 +517,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 && reportAttributesDerivedValue?.[report?.reportID]?.requiresAttention; const isExpense = isExpenseReport(report); const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat || isExpense; From 68688b385edbc04e953d24add4f7ba9c7e67524a Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 12 May 2025 09:13:32 +0200 Subject: [PATCH 08/11] update variable name --- Mobile-Expensify | 2 +- src/libs/actions/OnyxDerived/configs/reportAttributes.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 2992f02e5f3c..4ef2265121da 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 2992f02e5f3c31ddca5edd51bcebe4e54041c8a4 +Subproject commit 4ef2265121da93ba077f3d04155695d51b23ee12 diff --git a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts index e9532c173059..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; } } From 96eb9fd8e690f160f793da5fa6b4b0bb98b96e30 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 12 May 2025 11:21:28 +0200 Subject: [PATCH 09/11] use getReportAttributes function --- .../LHNOptionsList/LHNOptionsList.tsx | 4 ++-- src/libs/ReportUtils.ts | 19 +++++++++++++++---- src/libs/SidebarUtils.ts | 5 +++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 19a88620ce75..80301861cef0 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} from '@libs/ReportUtils'; +import {canUserPerformWriteAction, getReportAttributes} from '@libs/ReportUtils'; import isProductTrainingElementDismissed from '@libs/TooltipUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -77,7 +77,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio if (hasReportErrors(itemFullReport, itemReportActions)) { return true; } - const hasGBR = reportAttributes?.[reportID]?.requiresAttention; + const hasGBR = getReportAttributes(reportID, reportAttributes).requiresAttention; return hasGBR; }); }, [isGBRorRBRTooltipDismissed, data, reports, reportActions, reportAttributes]); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a8d7b5e051ea..a4d4b4bfc2b6 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -54,6 +54,7 @@ import type { TransactionViolation, UserWallet, } from '@src/types/onyx'; +import type {ReportAttributes} from '@src/types/onyx/DerivedValues'; import type {Attendee, Participant} from '@src/types/onyx/IOU'; import type {SelectedParticipant} from '@src/types/onyx/NewGroupChatDraft'; import type {OriginalMessageExportedToIntegration} from '@src/types/onyx/OldDotAction'; @@ -1051,14 +1052,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; }, }); @@ -4640,11 +4641,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; @@ -10700,6 +10701,15 @@ function getReportPersonalDetailsParticipants(report: Report, personalDetailsPar }; } +function getReportAttributes(reportID: string | undefined, reportAttributes?: ReportAttributesDerivedValue['reports']) { + const attributes = reportAttributes ?? reportAttributesDerivedValue; + + if (!reportID || !attributes?.[reportID]) { + return {} as ReportAttributes; + } + return attributes[reportID]; +} + export { addDomainToShortMention, completeShortMention, @@ -11074,6 +11084,7 @@ export { getReportPersonalDetailsParticipants, isAllowedToSubmitDraftExpenseReport, isWorkspaceEligibleForReportChange, + getReportAttributes, }; export type { diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 0d439bd03489..a599e85bf2e4 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -75,6 +75,7 @@ import { getIcons, getParticipantsAccountIDsForDisplay, getPolicyName, + getReportAttributes, getReportDescription, getReportName, getReportNameValuePairs, @@ -243,7 +244,7 @@ function getOrderedReportIDs( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing report.isPinned || (!isInFocusMode && isArchivedReport(reportNameValuePairs)) || - reportAttributes?.[report.reportID]?.requiresAttention; + getReportAttributes(report.reportID, reportAttributes)?.requiresAttention; if (isHidden && !shouldOverrideHidden) { return; } @@ -292,7 +293,7 @@ function getOrderedReportIDs( const isPinned = report?.isPinned ?? false; const rNVPs = reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`]; - if (isPinned || reportAttributes?.[report?.reportID]?.requiresAttention) { + if (isPinned || getReportAttributes(report?.reportID, reportAttributes)?.requiresAttention) { pinnedAndGBRReports.push(miniReport); } else if (report?.hasErrorsOtherThanFailedReceipt) { errorReports.push(miniReport); From e201d75aff16a3527def56439a158bb15cf8ef76 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 12 May 2025 11:22:00 +0200 Subject: [PATCH 10/11] update submodule --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 4ef2265121da..2992f02e5f3c 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 4ef2265121da93ba077f3d04155695d51b23ee12 +Subproject commit 2992f02e5f3c31ddca5edd51bcebe4e54041c8a4 From bf3e2b7fc9d205d8c99a4274d2a4d35895a63f79 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Tue, 13 May 2025 10:34:37 +0200 Subject: [PATCH 11/11] update getReportAttributes function --- src/components/LHNOptionsList/LHNOptionsList.tsx | 2 +- src/libs/ReportUtils.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 80301861cef0..081114bdb0f6 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -77,7 +77,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio if (hasReportErrors(itemFullReport, itemReportActions)) { return true; } - const hasGBR = getReportAttributes(reportID, reportAttributes).requiresAttention; + const hasGBR = getReportAttributes(reportID, reportAttributes)?.requiresAttention; return hasGBR; }); }, [isGBRorRBRTooltipDismissed, data, reports, reportActions, reportAttributes]); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a4d4b4bfc2b6..f2e107b13912 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -54,7 +54,6 @@ import type { TransactionViolation, UserWallet, } from '@src/types/onyx'; -import type {ReportAttributes} from '@src/types/onyx/DerivedValues'; import type {Attendee, Participant} from '@src/types/onyx/IOU'; import type {SelectedParticipant} from '@src/types/onyx/NewGroupChatDraft'; import type {OriginalMessageExportedToIntegration} from '@src/types/onyx/OldDotAction'; @@ -10705,7 +10704,7 @@ function getReportAttributes(reportID: string | undefined, reportAttributes?: Re const attributes = reportAttributes ?? reportAttributesDerivedValue; if (!reportID || !attributes?.[reportID]) { - return {} as ReportAttributes; + return; } return attributes[reportID]; }