From 3088672dda44f8eb7173c0545ef1c1adfebd4498 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 13 Feb 2024 18:44:45 +0500 Subject: [PATCH 001/306] perf: use context for shared reports access --- src/App.tsx | 4 + .../LHNOptionsList/LHNOptionsList.tsx | 6 +- src/components/LHNOptionsList/types.ts | 3 - src/hooks/useOrderedReportIDs.tsx | 145 +++++++++++ src/hooks/useReports.tsx | 44 ++++ .../AppNavigator/ReportScreenIDSetter.ts | 13 +- src/libs/SidebarUtils.ts | 57 ++--- src/pages/home/sidebar/SidebarLinksData.js | 228 +----------------- src/pages/workspace/WorkspacesListPage.tsx | 12 +- src/types/onyx/PriorityMode.ts | 6 + 10 files changed, 228 insertions(+), 290 deletions(-) create mode 100644 src/hooks/useOrderedReportIDs.tsx create mode 100644 src/hooks/useReports.tsx create mode 100644 src/types/onyx/PriorityMode.ts diff --git a/src/App.tsx b/src/App.tsx index 7c1ead1d86d3..712b82c21291 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -26,6 +26,8 @@ import {KeyboardStateProvider} from './components/withKeyboardState'; import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; +import {OrderedReportIDsContextProvider} from './hooks/useOrderedReportIDs'; +import {ReportsContextProvider} from './hooks/useReports'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import * as Session from './libs/actions/Session'; import * as Environment from './libs/Environment/Environment'; @@ -79,6 +81,8 @@ function App({url}: AppProps) { EnvironmentProvider, CustomStatusBarAndBackgroundContextProvider, ActiveWorkspaceContextProvider, + ReportsContextProvider, + OrderedReportIDsContextProvider, ]} > diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index a55d329f39ee..5ab105eda8df 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -5,6 +5,7 @@ import {StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; import usePermissions from '@hooks/usePermissions'; +import {useReports} from '@hooks/useReports'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import variables from '@styles/variables'; @@ -22,7 +23,6 @@ function LHNOptionsList({ onSelectRow, optionMode, shouldDisableFocusOptions = false, - reports = {}, reportActions = {}, policy = {}, preferredLocale = CONST.LOCALES.DEFAULT, @@ -33,6 +33,7 @@ function LHNOptionsList({ transactionViolations = {}, onFirstItemRendered = () => {}, }: LHNOptionsListProps) { + const reports = useReports(); const styles = useThemeStyles(); const {canUseViolations} = usePermissions(); @@ -134,9 +135,6 @@ LHNOptionsList.displayName = 'LHNOptionsList'; export default withCurrentReportID( withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, reportActions: { key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, }, diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 58bea97f04c9..c58770c8383f 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -15,9 +15,6 @@ type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ policy: OnyxCollection; - /** All reports shared with the user */ - reports: OnyxCollection; - /** Array of report actions for this report */ reportActions: OnyxCollection; diff --git a/src/hooks/useOrderedReportIDs.tsx b/src/hooks/useOrderedReportIDs.tsx new file mode 100644 index 000000000000..3217830054de --- /dev/null +++ b/src/hooks/useOrderedReportIDs.tsx @@ -0,0 +1,145 @@ +import React, {createContext, useContext, useMemo} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import {getCurrentUserAccountID} from '@libs/actions/Report'; +import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; +import SidebarUtils from '@libs/SidebarUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Beta, Policy, PolicyMembers, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type PriorityMode from '@src/types/onyx/PriorityMode'; +import useActiveWorkspace from './useActiveWorkspace'; +import useCurrentReportID from './useCurrentReportID'; +import {useReports} from './useReports'; + +type OnyxProps = { + betas: OnyxEntry; + policies: OnyxCollection; + allReportActions: OnyxCollection; + transactionViolations: OnyxCollection; + policyMembers: OnyxCollection; + priorityMode: OnyxEntry; +}; + +type WithOrderedReportIDsContextProviderProps = OnyxProps & { + children: React.ReactNode; +}; + +const OrderedReportIDsContext = createContext({}); + +function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextProviderProps) { + const chatReports = useReports(); + const currentReportIDValue = useCurrentReportID(); + const {activeWorkspaceID} = useActiveWorkspace(); + + const policyMemberAccountIDs = useMemo( + () => getPolicyMembersByIdWithoutCurrentUser(props.policyMembers, activeWorkspaceID, getCurrentUserAccountID()), + [activeWorkspaceID, props.policyMembers], + ); + + const optionListItems = useMemo( + () => + SidebarUtils.getOrderedReportIDs( + null, + chatReports, + props.betas ?? [], + props.policies, + props.priorityMode, + props.allReportActions, + props.transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + ), + [chatReports, props.betas, props.policies, props.priorityMode, props.allReportActions, props.transactionViolations, activeWorkspaceID, policyMemberAccountIDs], + ); + + // 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 + // we first generate the list as if there was no current report, then here we check if + // the current report is missing from the list, which should very rarely happen. In this + // case we re-generate the list a 2nd time with the current report included. + const optionListItemsWithCurrentReport = useMemo(() => { + if (currentReportIDValue?.currentReportID && !optionListItems.includes(currentReportIDValue.currentReportID)) { + return SidebarUtils.getOrderedReportIDs( + currentReportIDValue.currentReportID, + chatReports, + props.betas ?? [], + props.policies, + props.priorityMode, + props.allReportActions, + props.transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + ); + } + return optionListItems; + }, [ + activeWorkspaceID, + chatReports, + currentReportIDValue?.currentReportID, + optionListItems, + policyMemberAccountIDs, + props.allReportActions, + props.betas, + props.policies, + props.priorityMode, + props.transactionViolations, + ]); + + return {props.children}; +} + +const reportActionsSelector = (reportActions: OnyxEntry) => { + if (!reportActions) { + return []; + } + + return Object.values(reportActions).map((reportAction) => { + const {reportActionID, actionName, originalMessage} = reportAction ?? {}; + const decision = reportAction?.message?.[0]?.moderationDecision?.decision; + return { + reportActionID, + actionName, + originalMessage, + message: [ + { + moderationDecision: {decision}, + }, + ], + }; + }); +}; + +const OrderedReportIDsContextProvider = withOnyx({ + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + initialValue: CONST.PRIORITY_MODE.DEFAULT, + }, + betas: { + key: ONYXKEYS.BETAS, + initialValue: [], + }, + allReportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + // @ts-expect-error Need some help in determining the correct type for this selector + selector: (actions) => reportActionsSelector(actions), + initialValue: {}, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + initialValue: {}, + }, + policyMembers: { + key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + initialValue: {}, + }, +})(WithOrderedReportIDsContextProvider); + +function useOrderedReportIDs() { + return useContext(OrderedReportIDsContext); +} + +export {OrderedReportIDsContextProvider, OrderedReportIDsContext, useOrderedReportIDs}; diff --git a/src/hooks/useReports.tsx b/src/hooks/useReports.tsx new file mode 100644 index 000000000000..c4082149cbf4 --- /dev/null +++ b/src/hooks/useReports.tsx @@ -0,0 +1,44 @@ +import React, {createContext, useContext, useEffect, useMemo, useState} from 'react'; +import Onyx from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report} from '@src/types/onyx'; + +type Reports = OnyxCollection; +type ReportsContextValue = Reports; + +type ReportsContextProviderProps = { + children: React.ReactNode; +}; + +const ReportsContext = createContext(null); + +function ReportsContextProvider(props: ReportsContextProviderProps) { + const [reports, setReports] = useState(null); + + useEffect(() => { + // eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs + const connID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (val) => { + setReports(val); + }, + }); + return () => { + Onyx.disconnect(connID); + }; + }, []); + + const contextValue = useMemo(() => reports ?? {}, [reports]); + + return {props.children}; +} + +function useReports() { + return useContext(ReportsContext); +} + +ReportsContextProvider.displayName = 'ReportsContextProvider'; + +export {ReportsContextProvider, ReportsContext, useReports}; diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index b4bb56262860..83df18d5492d 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -3,6 +3,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import usePermissions from '@hooks/usePermissions'; +import {useReports} from '@hooks/useReports'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; @@ -11,9 +12,6 @@ import type {Policy, PolicyMembers, Report, ReportMetadata} from '@src/types/ony import type {ReportScreenWrapperProps} from './ReportScreenWrapper'; type ReportScreenIDSetterComponentProps = { - /** Available reports that would be displayed in this navigator */ - reports: OnyxCollection; - /** The policies which the user has access to */ policies: OnyxCollection; @@ -59,9 +57,10 @@ const getLastAccessedReportID = ( }; // This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params -function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) { +function ReportScreenIDSetter({route, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) { const {canUseDefaultRooms} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); + const reports = useReports(); useEffect(() => { // Don't update if there is a reportID in the params already @@ -83,7 +82,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav !canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, - !!reports?.params?.openOnAdminRoom, + !!route?.params?.openOnAdminRoom, reportMetadata, activeWorkspaceID, policyMemberAccountIDs, @@ -106,10 +105,6 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav ReportScreenIDSetter.displayName = 'ReportScreenIDSetter'; export default withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - allowStaleData: true, - }, policies: { key: ONYXKEYS.COLLECTION.POLICY, allowStaleData: true, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2347f5b9f5c5..e51276c5c28a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -2,12 +2,12 @@ import Str from 'expensify-common/lib/str'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; 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 Policy from '@src/types/onyx/Policy'; +import type PriorityMode from '@src/types/onyx/PriorityMode'; import type Report from '@src/types/onyx/Report'; import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; @@ -78,59 +78,23 @@ function setIsSidebarLoadedReady() { resolveSidebarIsReadyPromise(); } -// Define a cache object to store the memoized results -const reportIDsCache = new Map(); - -// Function to set a key-value pair while maintaining the maximum key limit -function setWithLimit(map: Map, key: TKey, value: TValue) { - if (map.size >= 5) { - // If the map has reached its limit, remove the first (oldest) key-value pair - const firstKey = map.keys().next().value; - map.delete(firstKey); - } - map.set(key, value); -} - -// Variable to verify if ONYX actions are loaded -let hasInitialReportActions = false; - /** * @returns An array of reportIDs sorted in the proper order */ function getOrderedReportIDs( currentReportId: string | null, - allReports: Record, + allReports: OnyxCollection, betas: Beta[], - policies: Record, - priorityMode: ValueOf, + policies: OnyxCollection, + priorityMode: OnyxEntry, allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], ): string[] { - const currentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]; - let reportActionCount = currentReportActions?.length ?? 0; - reportActionCount = Math.max(reportActionCount, 1); - - // Generate a unique cache key based on the function arguments - const cachedReportsKey = JSON.stringify( - [currentReportId, allReports, betas, policies, priorityMode, reportActionCount, currentPolicyID, policyMemberAccountIDs], - // Exclude some properties not to overwhelm a cached key value with huge data, which we don't need to store in a cacheKey - (key, value: unknown) => (['participantAccountIDs', 'participants', 'lastMessageText', 'visibleChatMemberAccountIDs'].includes(key) ? undefined : value), - ); - - // Check if the result is already in the cache - const cachedIDs = reportIDsCache.get(cachedReportsKey); - if (cachedIDs && hasInitialReportActions) { - return cachedIDs; - } - - // This is needed to prevent caching when Onyx is empty for a second render - hasInitialReportActions = Object.values(lastReportActions).length > 0; - const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; - const allReportsDictValues = Object.values(allReports); + const allReportsDictValues = Object.values(allReports ?? {}); // Filter out all the reports that shouldn't be displayed let reportsToDisplay = allReportsDictValues.filter((report) => { @@ -173,10 +137,18 @@ function getOrderedReportIDs( const archivedReports: Report[] = []; if (currentPolicyID || policyMemberAccountIDs.length > 0) { - reportsToDisplay = reportsToDisplay.filter((report) => ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID)); + reportsToDisplay = reportsToDisplay.filter((report) => { + if (!report) { + return false; + } + return ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID); + }); } // There are a few properties that need to be calculated for the report which are used when sorting reports. reportsToDisplay.forEach((report) => { + if (!report) { + return; + } // Normally, the spread operator would be used here to clone the report and prevent the need to reassign the params. // 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. @@ -219,7 +191,6 @@ 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); - setWithLimit(reportIDsCache, cachedReportsKey, LHNReports); return LHNReports; } diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 3bd538e8beab..42681fea451b 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -1,11 +1,8 @@ import {deepEqual} from 'fast-equals'; -import lodashGet from 'lodash/get'; -import lodashMap from 'lodash/map'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import networkPropTypes from '@components/networkPropTypes'; import {withNetwork} from '@components/OnyxProvider'; import withCurrentReportID from '@components/withCurrentReportID'; @@ -13,13 +10,11 @@ import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalD import withNavigationFocus from '@components/withNavigationFocus'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; +import {useOrderedReportIDs} from '@hooks/useOrderedReportIDs'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import SidebarUtils from '@libs/SidebarUtils'; -import reportPropTypes from '@pages/reportPropTypes'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -28,107 +23,30 @@ import SidebarLinks, {basePropTypes} from './SidebarLinks'; const propTypes = { ...basePropTypes, - /* Onyx Props */ - /** List of reports */ - chatReports: PropTypes.objectOf(reportPropTypes), - - /** 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, - }), - }), - ), - }), - ), - ), - /** Whether the reports are loading. When false it means they are ready to be used. */ isLoadingApp: PropTypes.bool, /** The chat priority mode */ priorityMode: PropTypes.string, - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - network: networkPropTypes.isRequired, - /** The policies which the user has access to */ - // eslint-disable-next-line react/forbid-prop-types - policies: PropTypes.object, - - // eslint-disable-next-line react/forbid-prop-types - policyMembers: PropTypes.object, - /** Session info for the currently logged in user. */ session: PropTypes.shape({ /** Currently logged in user accountID */ accountID: PropTypes.number, }), - /** All of the transaction violations */ - transactionViolations: PropTypes.shape({ - violations: PropTypes.arrayOf( - PropTypes.shape({ - /** The transaction ID */ - transactionID: PropTypes.number, - - /** The transaction violation type */ - type: PropTypes.string, - - /** The transaction violation message */ - message: PropTypes.string, - - /** The transaction violation data */ - data: PropTypes.shape({ - /** The transaction violation data field */ - field: PropTypes.string, - - /** The transaction violation data value */ - value: PropTypes.string, - }), - }), - ), - }), }; const defaultProps = { - chatReports: {}, - allReportActions: {}, isLoadingApp: true, priorityMode: CONST.PRIORITY_MODE.DEFAULT, - betas: [], - policies: {}, - policyMembers: {}, session: { accountID: '', }, - transactionViolations: {}, }; -function SidebarLinksData({ - isFocused, - allReportActions, - betas, - chatReports, - currentReportID, - insets, - isLoadingApp, - onLinkClick, - policies, - priorityMode, - network, - policyMembers, - session: {accountID}, - transactionViolations, -}) { +function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, session: {accountID}}) { const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); @@ -141,19 +59,8 @@ function SidebarLinksData({ const reportIDsRef = useRef(null); const isLoading = isLoadingApp; + const reportIDs = useOrderedReportIDs(); const optionListItems = useMemo(() => { - const reportIDs = SidebarUtils.getOrderedReportIDs( - null, - chatReports, - betas, - policies, - priorityMode, - allReportActions, - transactionViolations, - activeWorkspaceID, - policyMemberAccountIDs, - ); - if (deepEqual(reportIDsRef.current, reportIDs)) { return reportIDsRef.current; } @@ -165,29 +72,7 @@ function SidebarLinksData({ reportIDsRef.current = reportIDs; } return reportIDsRef.current || []; - }, [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, 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 - // we first generate the list as if there was no current report, then here we check if - // the current report is missing from the list, which should very rarely happen. In this - // case we re-generate the list a 2nd time with the current report included. - const optionListItemsWithCurrentReport = useMemo(() => { - if (currentReportID && !_.contains(optionListItems, currentReportID)) { - return SidebarUtils.getOrderedReportIDs( - currentReportID, - chatReports, - betas, - policies, - priorityMode, - allReportActions, - transactionViolations, - activeWorkspaceID, - policyMemberAccountIDs, - ); - } - return optionListItems; - }, [currentReportID, optionListItems, chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs]); + }, [reportIDs, isLoading, network.isOffline, prevPriorityMode, priorityMode]); const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; @@ -207,8 +92,8 @@ function SidebarLinksData({ // Data props: isActiveReport={isActiveReport} isLoading={isLoading} - optionListItems={optionListItemsWithCurrentReport} activeWorkspaceID={activeWorkspaceID} + optionListItems={optionListItems} /> ); @@ -218,97 +103,12 @@ SidebarLinksData.propTypes = propTypes; SidebarLinksData.defaultProps = defaultProps; SidebarLinksData.displayName = 'SidebarLinksData'; -/** - * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering - * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. - * @param {Object} [report] - * @returns {Object|undefined} - */ -const chatReportSelector = (report) => - report && { - reportID: report.reportID, - participantAccountIDs: report.participantAccountIDs, - hasDraft: report.hasDraft, - isPinned: report.isPinned, - isHidden: report.isHidden, - notificationPreference: report.notificationPreference, - errorFields: { - addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom, - }, - lastMessageText: report.lastMessageText, - lastVisibleActionCreated: report.lastVisibleActionCreated, - iouReportID: report.iouReportID, - total: report.total, - nonReimbursableTotal: report.nonReimbursableTotal, - hasOutstandingChildRequest: report.hasOutstandingChildRequest, - isWaitingOnBankAccount: report.isWaitingOnBankAccount, - statusNum: report.statusNum, - stateNum: report.stateNum, - chatType: report.chatType, - type: report.type, - policyID: report.policyID, - visibility: report.visibility, - lastReadTime: report.lastReadTime, - // Needed for name sorting: - reportName: report.reportName, - policyName: report.policyName, - oldPolicyName: report.oldPolicyName, - // Other less obvious properites considered for sorting: - ownerAccountID: report.ownerAccountID, - currency: report.currency, - managerID: report.managerID, - // Other important less obivous properties for filtering: - parentReportActionID: report.parentReportActionID, - parentReportID: report.parentReportID, - isDeletedParentAction: report.isDeletedParentAction, - isUnreadWithMention: ReportUtils.isUnreadWithMention(report), - }; - -/** - * @param {Object} [reportActions] - * @returns {Object|undefined} - */ -const reportActionsSelector = (reportActions) => - reportActions && - lodashMap(reportActions, (reportAction) => { - const {reportActionID, parentReportActionID, actionName, errors = []} = reportAction; - const decision = lodashGet(reportAction, 'message[0].moderationDecision.decision'); - - return { - reportActionID, - parentReportActionID, - actionName, - errors, - message: [ - { - moderationDecision: {decision}, - }, - ], - }; - }); - -/** - * @param {Object} [policy] - * @returns {Object|undefined} - */ -const policySelector = (policy) => - policy && { - type: policy.type, - name: policy.name, - avatar: policy.avatar, - }; - export default compose( withCurrentReportID, withCurrentUserPersonalDetails, withNavigationFocus, withNetwork(), withOnyx({ - chatReports: { - key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector, - initialValue: {}, - }, isLoadingApp: { key: ONYXKEYS.IS_LOADING_APP, }, @@ -316,26 +116,8 @@ export default compose( key: ONYXKEYS.NVP_PRIORITY_MODE, initialValue: CONST.PRIORITY_MODE.DEFAULT, }, - betas: { - key: ONYXKEYS.BETAS, - initialValue: [], - }, - allReportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - selector: reportActionsSelector, - initialValue: {}, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, - initialValue: {}, - }, policyMembers: { key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - initialValue: {}, - }, }), )(SidebarLinksData); diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index aa4f1df1ba7a..5efc94ba5974 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -20,6 +20,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import {useReports} from '@hooks/useReports'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -33,7 +34,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; -import type {PolicyMembers, Policy as PolicyType, ReimbursementAccount, Report} from '@src/types/onyx'; +import type {PolicyMembers, Policy as PolicyType, ReimbursementAccount} from '@src/types/onyx'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; @@ -72,9 +73,6 @@ type WorkspaceListPageOnyxProps = { /** A collection of objects for all policies which key policy member objects by accountIDs */ allPolicyMembers: OnyxCollection; - - /** All reports shared with the user (coming from Onyx) */ - reports: OnyxCollection; }; type WorkspaceListPageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceListPageOnyxProps; @@ -110,7 +108,8 @@ function dismissWorkspaceError(policyID: string, pendingAction: OnyxCommon.Pendi throw new Error('Not implemented'); } -function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, reports}: WorkspaceListPageProps) { +function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount}: WorkspaceListPageProps) { + const reports = useReports(); const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -407,8 +406,5 @@ export default withPolicyAndFullscreenLoading( reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, })(WorkspacesListPage), ); diff --git a/src/types/onyx/PriorityMode.ts b/src/types/onyx/PriorityMode.ts new file mode 100644 index 000000000000..5fef300ed014 --- /dev/null +++ b/src/types/onyx/PriorityMode.ts @@ -0,0 +1,6 @@ +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; + +type PriorityMode = ValueOf; + +export default PriorityMode; From 6921051449b209820347247511a943d8b4624ffb Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 15 Feb 2024 19:19:58 +0500 Subject: [PATCH 002/306] perf: improve renderItem and use FlatList --- .../LHNOptionsList/LHNOptionsList.tsx | 114 +++--------------- .../LHNOptionsList/OptionRowLHNData.tsx | 51 +------- src/components/LHNOptionsList/types.ts | 3 +- src/components/withCurrentReportID.tsx | 10 +- src/hooks/useOrderedReportIDs.tsx | 38 +++++- src/libs/SidebarUtils.ts | 40 +++++- src/pages/home/sidebar/SidebarLinks.js | 4 +- src/pages/home/sidebar/SidebarLinksData.js | 2 +- src/pages/workspace/WorkspacesListPage.tsx | 4 +- 9 files changed, 111 insertions(+), 155 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index e0a869f5222a..c70e355c46c5 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,41 +1,27 @@ -import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; -import React, {memo, useCallback} from 'react'; -import {StyleSheet, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import withCurrentReportID from '@components/withCurrentReportID'; -import usePermissions from '@hooks/usePermissions'; -import {useReports} from '@hooks/useReports'; +import React, {useCallback, memo} from 'react'; +import {FlatList, StyleSheet, View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import variables from '@styles/variables'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import OptionRowLHNData from './OptionRowLHNData'; -import type {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; +import type {LHNOptionsListProps, RenderItemProps} from './types'; +import { OrderedReports } from '@libs/SidebarUtils'; +import CONST from '@src/CONST'; +import variables from '@styles/variables'; +import { FlashList } from '@shopify/flash-list'; -const keyExtractor = (item: string) => `report_${item}`; +const keyExtractor = (item: OrderedReports) => `report_${item?.reportID}`; function LHNOptionsList({ style, contentContainerStyles, data, onSelectRow, - optionMode, shouldDisableFocusOptions = false, - reportActions = {}, - policy = {}, - preferredLocale = CONST.LOCALES.DEFAULT, - personalDetails = {}, - transactions = {}, currentReportID = '', - draftComments = {}, - transactionViolations = {}, + optionMode, onFirstItemRendered = () => {}, }: LHNOptionsListProps) { - const reports = useReports(); const styles = useThemeStyles(); - const {canUseViolations} = usePermissions(); // 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. @@ -53,69 +39,29 @@ function LHNOptionsList({ * Function which renders a row in the list */ const renderItem = useCallback( - ({item: reportID}: RenderItemProps): ReactElement => { - const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; - const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; - const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; - const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; - const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; - const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : ''; - const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; - const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; - const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions); - const lastReportAction = sortedReportActions[0]; - - // Get the transaction for the last report action - let lastReportActionTransactionID = ''; - - if (lastReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { - lastReportActionTransactionID = lastReportAction.originalMessage?.IOUTransactionID ?? ''; - } - const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`] ?? {}; + ({item}: RenderItemProps): ReactElement => { return ( ); }, [ currentReportID, - draftComments, onSelectRow, - optionMode, - personalDetails, - policy, - preferredLocale, - reportActions, - reports, - shouldDisableFocusOptions, - transactions, - transactionViolations, - canUseViolations, onLayoutItem, ], ); return ( - @@ -133,30 +79,6 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; -export default withCurrentReportID( - withOnyx({ - reportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - }, - policy: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - transactions: { - key: ONYXKEYS.COLLECTION.TRANSACTION, - }, - draftComments: { - key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - }, - })(memo(LHNOptionsList)), -); +export default memo(LHNOptionsList); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index a18d5a8ec1ec..622d7b9d9123 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -16,61 +16,12 @@ import type {OptionRowLHNDataProps} from './types'; */ function OptionRowLHNData({ isFocused = false, - fullReport, - reportActions, - personalDetails = {}, - preferredLocale = CONST.LOCALES.DEFAULT, comment, - policy, - receiptTransactions, - parentReportAction, - transaction, - lastReportActionTransaction = {}, - transactionViolations, - canUseViolations, + optionItem, ...propsToForward }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; - const optionItemRef = useRef(); - - const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(fullReport, transactionViolations, parentReportAction ?? null); - - const optionItem = useMemo(() => { - // 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, - hasViolations: !!hasViolations, - }); - if (deepEqual(item, optionItemRef.current)) { - return optionItemRef.current; - } - - optionItemRef.current = item; - - return item; - // Listen parentReportAction to update title of thread report when parentReportAction changed - // Listen to transaction to update title of transaction report when transaction changed - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - fullReport, - lastReportActionTransaction, - reportActions, - personalDetails, - preferredLocale, - policy, - parentReportAction, - transaction, - transactionViolations, - canUseViolations, - receiptTransactions, - ]); - useEffect(() => { if (!optionItem || !!optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { return; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index c58770c8383f..4c79536571bf 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -8,6 +8,7 @@ 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 {EmptyObject} from '@src/types/utils/EmptyObject'; +import { OrderedReports } from '@libs/SidebarUtils'; type OptionMode = ValueOf; @@ -134,6 +135,6 @@ type OptionRowLHNProps = { onLayout?: (event: LayoutChangeEvent) => void; }; -type RenderItemProps = {item: string}; +type RenderItemProps = {item: OrderedReports}; export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps, RenderItemProps}; diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index a452e7565b4e..cc49c44e0e77 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -39,7 +39,15 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro */ const updateCurrentReportID = useCallback( (state: NavigationState) => { - setCurrentReportID(Navigation.getTopmostReportId(state) ?? ''); + const reportID = Navigation.getTopmostReportId(state) ?? ''; + /** + * This is to make sure we don't set the undefined as reportID when + * switching between chat list and settings->workspaces tab. + * and doing so avoid unnecessary re-render of `useOrderedReportIDs`. + */ + if (reportID) { + setCurrentReportID(reportID); + } }, [setCurrentReportID], ); diff --git a/src/hooks/useOrderedReportIDs.tsx b/src/hooks/useOrderedReportIDs.tsx index 3217830054de..01dab5a3231a 100644 --- a/src/hooks/useOrderedReportIDs.tsx +++ b/src/hooks/useOrderedReportIDs.tsx @@ -1,6 +1,7 @@ import React, {createContext, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import {usePersonalDetails} from '@components/OnyxProvider'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import SidebarUtils from '@libs/SidebarUtils'; @@ -10,6 +11,7 @@ import type {Beta, Policy, PolicyMembers, ReportAction, ReportActions, Transacti import type PriorityMode from '@src/types/onyx/PriorityMode'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; +import usePermissions from './usePermissions'; import {useReports} from './useReports'; type OnyxProps = { @@ -31,6 +33,8 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP const chatReports = useReports(); const currentReportIDValue = useCurrentReportID(); const {activeWorkspaceID} = useActiveWorkspace(); + const personalDetails = usePersonalDetails(); + const {canUseViolations} = usePermissions(); const policyMemberAccountIDs = useMemo( () => getPolicyMembersByIdWithoutCurrentUser(props.policyMembers, activeWorkspaceID, getCurrentUserAccountID()), @@ -49,8 +53,25 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP props.transactionViolations, activeWorkspaceID, policyMemberAccountIDs, + personalDetails, + props.preferredLocale, + canUseViolations, + props.draftComments, ), - [chatReports, props.betas, props.policies, props.priorityMode, props.allReportActions, props.transactionViolations, activeWorkspaceID, policyMemberAccountIDs], + [ + chatReports, + props.betas, + props.policies, + props.priorityMode, + props.allReportActions, + props.transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + personalDetails, + props.preferredLocale, + canUseViolations, + props.draftComments, + ], ); // We need to make sure the current report is in the list of reports, but we do not want @@ -70,6 +91,10 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP props.transactionViolations, activeWorkspaceID, policyMemberAccountIDs, + personalDetails, + props.preferredLocale, + canUseViolations, + props.draftComments, ); } return optionListItems; @@ -84,6 +109,10 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP props.policies, props.priorityMode, props.transactionViolations, + personalDetails, + props.preferredLocale, + canUseViolations, + props.draftComments, ]); return {props.children}; @@ -136,6 +165,13 @@ const OrderedReportIDsContextProvider = withOnyx, currentPolicyID = '', policyMemberAccountIDs: number[] = [], -): string[] { + personalDetails: OnyxEntry, + preferredLocale: DeepValueOf, + canUseViolations: boolean, + draftComments: OnyxCollection, +): OrderedReports[] { const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; const allReportsDictValues = Object.values(allReports ?? {}); @@ -190,7 +200,33 @@ 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 = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => { + const itemFullReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`] ?? null; + const itemReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`] ?? null; + const itemParentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; + const itemPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; + const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report?.reportID}`] ?? ''; + + const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(itemFullReport, transactionViolations, itemParentReportAction ?? null); + + const item = getOptionData({ + report: itemFullReport, + reportActions: itemReportActions, + personalDetails, + preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, + policy: itemPolicy, + parentReportAction: itemParentReportAction, + hasViolations: !!hasViolations, + }); + + return { + reportID: report.reportID, + optionItem: item, + comment: itemComment, + } + }); + return LHNReports; } diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index e78e656e0b7e..4c05c3d44ae8 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -1,7 +1,7 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {memo, useCallback, useEffect, useMemo, useRef} from 'react'; import {InteractionManager, StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -179,5 +179,5 @@ export default withOnyx({ activePolicy: { key: ({activeWorkspaceID}) => `${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID}`, }, -})(SidebarLinks); +})(memo(SidebarLinks)); export {basePropTypes}; diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 42681fea451b..87006d3debe2 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -46,7 +46,7 @@ const defaultProps = { }, }; -function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, session: {accountID}}) { +function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, session: {accountID}}) { const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index d778648ea998..968148a8becb 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -107,6 +107,8 @@ function dismissWorkspaceError(policyID: string, pendingAction: OnyxCommon.Pendi throw new Error('Not implemented'); } +const stickyHeaderIndices = [0]; + function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount}: WorkspaceListPageProps) { const reports = useReports(); const theme = useTheme(); @@ -375,7 +377,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount}: data={workspaces} renderItem={getMenuItem} ListHeaderComponent={listHeaderComponent} - stickyHeaderIndices={[0]} + stickyHeaderIndices={stickyHeaderIndices} /> Date: Mon, 26 Feb 2024 17:58:22 +0100 Subject: [PATCH 003/306] refactor: revert changes to withCurrentReportID --- src/components/withCurrentReportID.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index cc49c44e0e77..a452e7565b4e 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -39,15 +39,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro */ const updateCurrentReportID = useCallback( (state: NavigationState) => { - const reportID = Navigation.getTopmostReportId(state) ?? ''; - /** - * This is to make sure we don't set the undefined as reportID when - * switching between chat list and settings->workspaces tab. - * and doing so avoid unnecessary re-render of `useOrderedReportIDs`. - */ - if (reportID) { - setCurrentReportID(reportID); - } + setCurrentReportID(Navigation.getTopmostReportId(state) ?? ''); }, [setCurrentReportID], ); From 9ae8701c7ed4a5c98e69d689e63c4ac19694ebf2 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 26 Feb 2024 18:48:44 +0100 Subject: [PATCH 004/306] refactor: remove useReports context & hooks --- src/App.tsx | 2 - src/hooks/useReports.tsx | 44 ------------------- .../AppNavigator/ReportScreenIDSetter.ts | 13 ++++-- src/pages/workspace/WorkspacesListPage.tsx | 10 +++-- 4 files changed, 16 insertions(+), 53 deletions(-) delete mode 100644 src/hooks/useReports.tsx diff --git a/src/App.tsx b/src/App.tsx index 7cbf3478d16e..eb1750a7fe5f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -30,7 +30,6 @@ import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; import {OrderedReportIDsContextProvider} from './hooks/useOrderedReportIDs'; -import {ReportsContextProvider} from './hooks/useReports'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import InitialUrlContext from './libs/InitialUrlContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; @@ -77,7 +76,6 @@ function App({url}: AppProps) { CustomStatusBarAndBackgroundContextProvider, ActiveElementRoleProvider, ActiveWorkspaceContextProvider, - ReportsContextProvider, OrderedReportIDsContextProvider, PlaybackContextProvider, VolumeContextProvider, diff --git a/src/hooks/useReports.tsx b/src/hooks/useReports.tsx deleted file mode 100644 index c4082149cbf4..000000000000 --- a/src/hooks/useReports.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, {createContext, useContext, useEffect, useMemo, useState} from 'react'; -import Onyx from 'react-native-onyx'; -import type {OnyxCollection} from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report} from '@src/types/onyx'; - -type Reports = OnyxCollection; -type ReportsContextValue = Reports; - -type ReportsContextProviderProps = { - children: React.ReactNode; -}; - -const ReportsContext = createContext(null); - -function ReportsContextProvider(props: ReportsContextProviderProps) { - const [reports, setReports] = useState(null); - - useEffect(() => { - // eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs - const connID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallback: true, - callback: (val) => { - setReports(val); - }, - }); - return () => { - Onyx.disconnect(connID); - }; - }, []); - - const contextValue = useMemo(() => reports ?? {}, [reports]); - - return {props.children}; -} - -function useReports() { - return useContext(ReportsContext); -} - -ReportsContextProvider.displayName = 'ReportsContextProvider'; - -export {ReportsContextProvider, ReportsContext, useReports}; diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index 297313e513f6..529f0f3d31a7 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -3,7 +3,6 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import usePermissions from '@hooks/usePermissions'; -import {useReports} from '@hooks/useReports'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -11,6 +10,9 @@ import type {Policy, PolicyMembers, Report, ReportMetadata} from '@src/types/ony import type {ReportScreenWrapperProps} from './ReportScreenWrapper'; type ReportScreenIDSetterComponentProps = { + /** Available reports that would be displayed in this navigator */ + reports: OnyxCollection; + /** The policies which the user has access to */ policies: OnyxCollection; @@ -56,10 +58,9 @@ const getLastAccessedReportID = ( }; // This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params -function ReportScreenIDSetter({route, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) { +function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) { const {canUseDefaultRooms} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); - const reports = useReports(); useEffect(() => { // Don't update if there is a reportID in the params already @@ -80,7 +81,7 @@ function ReportScreenIDSetter({route, policies, policyMembers = {}, navigation, !canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, - !!route?.params?.openOnAdminRoom, + !!reports?.params?.openOnAdminRoom, reportMetadata, activeWorkspaceID, policyMemberAccountIDs, @@ -101,6 +102,10 @@ function ReportScreenIDSetter({route, policies, policyMembers = {}, navigation, ReportScreenIDSetter.displayName = 'ReportScreenIDSetter'; export default withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + allowStaleData: true, + }, policies: { key: ONYXKEYS.COLLECTION.POLICY, allowStaleData: true, diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index f646366360eb..a51efd608444 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -20,7 +20,6 @@ import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import {useReports} from '@hooks/useReports'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -73,6 +72,9 @@ type WorkspaceListPageOnyxProps = { /** A collection of objects for all policies which key policy member objects by accountIDs */ allPolicyMembers: OnyxCollection; + + /** All reports shared with the user (coming from Onyx) */ + reports: OnyxCollection; }; type WorkspaceListPageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceListPageOnyxProps; @@ -110,8 +112,7 @@ function dismissWorkspaceError(policyID: string, pendingAction: OnyxCommon.Pendi const stickyHeaderIndices = [0]; -function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount}: WorkspaceListPageProps) { - const reports = useReports(); +function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, reports}: WorkspaceListPageProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -409,5 +410,8 @@ export default withPolicyAndFullscreenLoading( reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, })(WorkspacesListPage), ); From b613e974a2f8e8b7c07e893e64bc6fdf377d08d1 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 15:54:37 +0100 Subject: [PATCH 005/306] refactor: use reports from onyx in useOrderedReportIDs --- src/hooks/useOrderedReportIDs.tsx | 69 +++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/src/hooks/useOrderedReportIDs.tsx b/src/hooks/useOrderedReportIDs.tsx index 01dab5a3231a..6b3ebd8d9e28 100644 --- a/src/hooks/useOrderedReportIDs.tsx +++ b/src/hooks/useOrderedReportIDs.tsx @@ -4,23 +4,26 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {usePersonalDetails} from '@components/OnyxProvider'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Policy, PolicyMembers, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type {Beta, Locale, Policy, PolicyMembers, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; import type PriorityMode from '@src/types/onyx/PriorityMode'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; import usePermissions from './usePermissions'; -import {useReports} from './useReports'; type OnyxProps = { + chatReports: OnyxCollection; betas: OnyxEntry; policies: OnyxCollection; allReportActions: OnyxCollection; transactionViolations: OnyxCollection; policyMembers: OnyxCollection; priorityMode: OnyxEntry; + preferredLocale: OnyxEntry; + draftComments: OnyxCollection; }; type WithOrderedReportIDsContextProviderProps = OnyxProps & { @@ -30,7 +33,6 @@ type WithOrderedReportIDsContextProviderProps = OnyxProps & { const OrderedReportIDsContext = createContext({}); function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextProviderProps) { - const chatReports = useReports(); const currentReportIDValue = useCurrentReportID(); const {activeWorkspaceID} = useActiveWorkspace(); const personalDetails = usePersonalDetails(); @@ -45,7 +47,7 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP () => SidebarUtils.getOrderedReportIDs( null, - chatReports, + props.chatReports, props.betas ?? [], props.policies, props.priorityMode, @@ -59,7 +61,7 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP props.draftComments, ), [ - chatReports, + props.chatReports, props.betas, props.policies, props.priorityMode, @@ -83,7 +85,7 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP if (currentReportIDValue?.currentReportID && !optionListItems.includes(currentReportIDValue.currentReportID)) { return SidebarUtils.getOrderedReportIDs( currentReportIDValue.currentReportID, - chatReports, + props.chatReports, props.betas ?? [], props.policies, props.priorityMode, @@ -100,7 +102,7 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP return optionListItems; }, [ activeWorkspaceID, - chatReports, + props.chatReports, currentReportIDValue?.currentReportID, optionListItems, policyMemberAccountIDs, @@ -118,6 +120,52 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP return {props.children}; } +/** + * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering + * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. + * @param {Object} [report] + * @returns {Object|undefined} + */ +const chatReportSelector = (report) => + report && { + reportID: report.reportID, + participantAccountIDs: report.participantAccountIDs, + hasDraft: report.hasDraft, + isPinned: report.isPinned, + isHidden: report.isHidden, + notificationPreference: report.notificationPreference, + errorFields: { + addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom, + }, + lastMessageText: report.lastMessageText, + lastVisibleActionCreated: report.lastVisibleActionCreated, + iouReportID: report.iouReportID, + total: report.total, + nonReimbursableTotal: report.nonReimbursableTotal, + hasOutstandingChildRequest: report.hasOutstandingChildRequest, + isWaitingOnBankAccount: report.isWaitingOnBankAccount, + statusNum: report.statusNum, + stateNum: report.stateNum, + chatType: report.chatType, + type: report.type, + policyID: report.policyID, + visibility: report.visibility, + lastReadTime: report.lastReadTime, + // Needed for name sorting: + reportName: report.reportName, + policyName: report.policyName, + oldPolicyName: report.oldPolicyName, + // Other less obvious properites considered for sorting: + ownerAccountID: report.ownerAccountID, + currency: report.currency, + managerID: report.managerID, + // Other important less obivous properties for filtering: + parentReportActionID: report.parentReportActionID, + parentReportID: report.parentReportID, + isDeletedParentAction: report.isDeletedParentAction, + isUnreadWithMention: ReportUtils.isUnreadWithMention(report), + }; + const reportActionsSelector = (reportActions: OnyxEntry) => { if (!reportActions) { return []; @@ -140,6 +188,11 @@ const reportActionsSelector = (reportActions: OnyxEntry) => { }; const OrderedReportIDsContextProvider = withOnyx({ + chatReports: { + key: ONYXKEYS.COLLECTION.REPORT, + selector: chatReportSelector, + initialValue: {}, + }, priorityMode: { key: ONYXKEYS.NVP_PRIORITY_MODE, initialValue: CONST.PRIORITY_MODE.DEFAULT, @@ -151,7 +204,7 @@ const OrderedReportIDsContextProvider = withOnyx reportActionsSelector(actions), + selector: reportActionsSelector, initialValue: {}, }, policies: { From 7f15fbc34bdc512c6c72fe8b15232dc1ba53e756 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 17:32:07 +0100 Subject: [PATCH 006/306] refactor: move creating orderedReport objects from getOrderedReportIds to the context itself --- .../LHNOptionsList/LHNOptionsList.tsx | 48 +++--- src/hooks/useOrderedReportIDs.tsx | 140 ++++++++---------- src/libs/SidebarUtils.ts | 46 +----- src/pages/home/sidebar/SidebarLinksData.js | 18 ++- 4 files changed, 100 insertions(+), 152 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index c70e355c46c5..8b18630a9646 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,13 +1,13 @@ +import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; -import React, {useCallback, memo} from 'react'; -import {FlatList, StyleSheet, View} from 'react-native'; +import React, {memo, useCallback} from 'react'; +import {StyleSheet, View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; +import {OrderedReports} from '@libs/SidebarUtils'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; import OptionRowLHNData from './OptionRowLHNData'; import type {LHNOptionsListProps, RenderItemProps} from './types'; -import { OrderedReports } from '@libs/SidebarUtils'; -import CONST from '@src/CONST'; -import variables from '@styles/variables'; -import { FlashList } from '@shopify/flash-list'; const keyExtractor = (item: OrderedReports) => `report_${item?.reportID}`; @@ -39,29 +39,23 @@ function LHNOptionsList({ * Function which renders a row in the list */ const renderItem = useCallback( - ({item}: RenderItemProps): ReactElement => { - - return ( - - ); - }, - [ - currentReportID, - onSelectRow, - onLayoutItem, - ], + ({item}: RenderItemProps): ReactElement => ( + + ), + [shouldDisableFocusOptions, currentReportID, onSelectRow, onLayoutItem, optionMode], ); return ( - diff --git a/src/hooks/useOrderedReportIDs.tsx b/src/hooks/useOrderedReportIDs.tsx index 6b3ebd8d9e28..8f071f036fc7 100644 --- a/src/hooks/useOrderedReportIDs.tsx +++ b/src/hooks/useOrderedReportIDs.tsx @@ -1,4 +1,4 @@ -import React, {createContext, useContext, useMemo} from 'react'; +import React, {createContext, useCallback, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {usePersonalDetails} from '@components/OnyxProvider'; @@ -32,101 +32,90 @@ type WithOrderedReportIDsContextProviderProps = OnyxProps & { const OrderedReportIDsContext = createContext({}); -function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextProviderProps) { +function WithOrderedReportIDsContextProvider({ + children, + chatReports, + betas, + policies, + allReportActions, + transactionViolations, + policyMembers, + priorityMode, + preferredLocale, + draftComments, +}: WithOrderedReportIDsContextProviderProps) { const currentReportIDValue = useCurrentReportID(); - const {activeWorkspaceID} = useActiveWorkspace(); const personalDetails = usePersonalDetails(); const {canUseViolations} = usePermissions(); + const {activeWorkspaceID} = useActiveWorkspace(); - const policyMemberAccountIDs = useMemo( - () => getPolicyMembersByIdWithoutCurrentUser(props.policyMembers, activeWorkspaceID, getCurrentUserAccountID()), - [activeWorkspaceID, props.policyMembers], - ); + const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, getCurrentUserAccountID()), [activeWorkspaceID, policyMembers]); - const optionListItems = useMemo( - () => + const getOrderedReportIDs = useCallback( + (currentReportID?: string) => SidebarUtils.getOrderedReportIDs( - null, - props.chatReports, - props.betas ?? [], - props.policies, - props.priorityMode, - props.allReportActions, - props.transactionViolations, + currentReportID ?? null, + chatReports, + betas ?? [], + policies, + priorityMode, + allReportActions, + transactionViolations, activeWorkspaceID, policyMemberAccountIDs, - personalDetails, - props.preferredLocale, - canUseViolations, - props.draftComments, ), - [ - props.chatReports, - props.betas, - props.policies, - props.priorityMode, - props.allReportActions, - props.transactionViolations, - activeWorkspaceID, - policyMemberAccountIDs, - personalDetails, - props.preferredLocale, - canUseViolations, - props.draftComments, - ], + [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs], ); + const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); + // 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 // we first generate the list as if there was no current report, then here we check if // the current report is missing from the list, which should very rarely happen. In this // case we re-generate the list a 2nd time with the current report included. - const optionListItemsWithCurrentReport = useMemo(() => { - if (currentReportIDValue?.currentReportID && !optionListItems.includes(currentReportIDValue.currentReportID)) { - return SidebarUtils.getOrderedReportIDs( - currentReportIDValue.currentReportID, - props.chatReports, - props.betas ?? [], - props.policies, - props.priorityMode, - props.allReportActions, - props.transactionViolations, - activeWorkspaceID, - policyMemberAccountIDs, - personalDetails, - props.preferredLocale, - canUseViolations, - props.draftComments, - ); + const orderedReportIDsWithCurrentReport = useMemo(() => { + if (currentReportIDValue?.currentReportID && !orderedReportIDs.includes(currentReportIDValue.currentReportID)) { + return getOrderedReportIDs(currentReportIDValue.currentReportID); } - return optionListItems; - }, [ - activeWorkspaceID, - props.chatReports, - currentReportIDValue?.currentReportID, - optionListItems, - policyMemberAccountIDs, - props.allReportActions, - props.betas, - props.policies, - props.priorityMode, - props.transactionViolations, - personalDetails, - props.preferredLocale, - canUseViolations, - props.draftComments, - ]); - - return {props.children}; + return orderedReportIDs; + }, [getOrderedReportIDs, currentReportIDValue?.currentReportID, orderedReportIDs]); + + const orderedReportListItems = useMemo( + () => + orderedReportIDsWithCurrentReport.map((reportID) => { + const itemFullReport = chatReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const itemReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; + const itemParentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; + const itemPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; + const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; + + const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(itemFullReport, transactionViolations, itemParentReportAction ?? null); + + const item = SidebarUtils.getOptionData({ + report: itemFullReport, + reportActions: itemReportActions, + personalDetails, + preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, + policy: itemPolicy, + parentReportAction: itemParentReportAction, + hasViolations: !!hasViolations, + }); + + return {reportID, optionItem: item, comment: itemComment}; + }), + [orderedReportIDsWithCurrentReport, canUseViolations, personalDetails, draftComments, preferredLocale, chatReports, allReportActions, policies, transactionViolations], + ); + + return {children}; } /** * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. - * @param {Object} [report] - * @returns {Object|undefined} */ -const chatReportSelector = (report) => +const chatReportSelector = (report: OnyxEntry) => report && { reportID: report.reportID, participantAccountIDs: report.participantAccountIDs, @@ -134,9 +123,7 @@ const chatReportSelector = (report) => isPinned: report.isPinned, isHidden: report.isHidden, notificationPreference: report.notificationPreference, - errorFields: { - addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom, - }, + errorFields: {addWorkspaceRoom: report.errorFields?.addWorkspaceRoom}, lastMessageText: report.lastMessageText, lastVisibleActionCreated: report.lastVisibleActionCreated, iouReportID: report.iouReportID, @@ -188,6 +175,7 @@ const reportActionsSelector = (reportActions: OnyxEntry) => { }; const OrderedReportIDsContextProvider = withOnyx({ + // @ts-expect-error Need some help in determining the correct type for this selector chatReports: { key: ONYXKEYS.COLLECTION.REPORT, selector: chatReportSelector, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index f99384487650..f36becf40715 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -59,30 +59,20 @@ function compareStringDates(a: string, b: string): 0 | 1 | -1 { return 0; } -export type OrderedReports = { - reportID: string; - optionItem: ReportUtils.OptionData | undefined; - comment: string; -}; - /** * @returns An array of reportIDs sorted in the proper order */ function getOrderedReportIDs( currentReportId: string | null, allReports: OnyxCollection, - betas: Beta[], + betas: OnyxEntry, policies: OnyxCollection, priorityMode: OnyxEntry, allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], - personalDetails: OnyxEntry, - preferredLocale: DeepValueOf, - canUseViolations: boolean, - draftComments: OnyxCollection, -): OrderedReports[] { +): string[] { const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; const allReportsDictValues = Object.values(allReports ?? {}); @@ -93,12 +83,12 @@ function getOrderedReportIDs( 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); + !!betas && betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); return ReportUtils.shouldReportBeInOptionList({ report, currentReportId: currentReportId ?? '', isInGSDMode, - betas, + betas: betas ?? [], policies, excludeEmptyChats: true, doesReportHaveViolations, @@ -178,33 +168,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) => { - const itemFullReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`] ?? null; - const itemReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`] ?? null; - const itemParentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; - const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; - const itemPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; - const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report?.reportID}`] ?? ''; - - const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(itemFullReport, transactionViolations, itemParentReportAction ?? null); - - const item = getOptionData({ - report: itemFullReport, - reportActions: itemReportActions, - personalDetails, - preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, - policy: itemPolicy, - parentReportAction: itemParentReportAction, - hasViolations: !!hasViolations, - }); - - return { - reportID: report.reportID, - optionItem: item, - comment: itemComment, - }; - }); - + const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); return LHNReports; } diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 6d63a8a44fe1..4966a2414e97 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -51,22 +51,24 @@ function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onL // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => Policy.openWorkspace(activeWorkspaceID, policyMemberAccountIDs), [activeWorkspaceID]); - const reportIDsRef = useRef(null); + const orderedReportListItemsRef = useRef(null); const isLoading = isLoadingApp; - const reportIDs = useOrderedReportIDs(); + const orderedReportListItems = useOrderedReportIDs(); + const optionListItems = useMemo(() => { - if (deepEqual(reportIDsRef.current, reportIDs)) { - return reportIDsRef.current; + // this can be very heavy because we are no longer comapring the IDS but the whole objects for the list + if (deepEqual(orderedReportListItemsRef.current, orderedReportListItems)) { + return orderedReportListItemsRef.current; } // 1. We need to update existing reports only once while loading because they are updated several times during loading and causes this regression: https://github.com/Expensify/App/issues/24596#issuecomment-1681679531 // 2. If the user is offline, we need to update the reports unconditionally, since the loading of report data might be stuck in this case. // 3. Changing priority mode to Most Recent will call OpenApp. If there is an existing reports and the priority mode is updated, we want to immediately update the list instead of waiting the OpenApp request to complete - if (!isLoading || !reportIDsRef.current || network.isOffline || (reportIDsRef.current && prevPriorityMode !== priorityMode)) { - reportIDsRef.current = reportIDs; + if (!isLoading || !orderedReportListItemsRef.current || network.isOffline || (orderedReportListItemsRef.current && prevPriorityMode !== priorityMode)) { + orderedReportListItemsRef.current = orderedReportListItems; } - return reportIDsRef.current || []; - }, [reportIDs, isLoading, network.isOffline, prevPriorityMode, priorityMode]); + return orderedReportListItemsRef.current || []; + }, [orderedReportListItems, isLoading, network.isOffline, prevPriorityMode, priorityMode]); const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; From 7fa44fc71ce4eb20d70b4da92094b9a5b6d50394 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 17:34:55 +0100 Subject: [PATCH 007/306] Revert "refactor: revert changes to withCurrentReportID" This reverts commit dc88950ac4dbc552e4ca8b705ae4faff05dd70da. --- src/components/withCurrentReportID.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index a452e7565b4e..cc49c44e0e77 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -39,7 +39,15 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro */ const updateCurrentReportID = useCallback( (state: NavigationState) => { - setCurrentReportID(Navigation.getTopmostReportId(state) ?? ''); + const reportID = Navigation.getTopmostReportId(state) ?? ''; + /** + * This is to make sure we don't set the undefined as reportID when + * switching between chat list and settings->workspaces tab. + * and doing so avoid unnecessary re-render of `useOrderedReportIDs`. + */ + if (reportID) { + setCurrentReportID(reportID); + } }, [setCurrentReportID], ); From ae12b008886272f08668a089342bc9bacbcfb831 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 17:47:45 +0100 Subject: [PATCH 008/306] refactor: remove comment in SidebarLinksData --- src/pages/home/sidebar/SidebarLinksData.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 4966a2414e97..5cedc9bbb7fd 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -56,7 +56,6 @@ function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onL const orderedReportListItems = useOrderedReportIDs(); const optionListItems = useMemo(() => { - // this can be very heavy because we are no longer comapring the IDS but the whole objects for the list if (deepEqual(orderedReportListItemsRef.current, orderedReportListItems)) { return orderedReportListItemsRef.current; } From c87f579bda3c94452f6b093edfbc159996a7eb30 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 18:00:43 +0100 Subject: [PATCH 009/306] refactor: rename useOrderedReportIDs to useOrderedReportListItems --- src/App.tsx | 4 ++-- src/components/withCurrentReportID.tsx | 2 +- ...tIDs.tsx => useOrderedReportListItems.tsx} | 20 +++++++++---------- src/pages/home/sidebar/SidebarLinksData.js | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) rename src/hooks/{useOrderedReportIDs.tsx => useOrderedReportListItems.tsx} (92%) diff --git a/src/App.tsx b/src/App.tsx index eb1750a7fe5f..5670859e8908 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,7 +29,7 @@ import {KeyboardStateProvider} from './components/withKeyboardState'; import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; -import {OrderedReportIDsContextProvider} from './hooks/useOrderedReportIDs'; +import {OrderedReportListItemsContextProvider} from './hooks/useOrderedReportListItems'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import InitialUrlContext from './libs/InitialUrlContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; @@ -76,7 +76,7 @@ function App({url}: AppProps) { CustomStatusBarAndBackgroundContextProvider, ActiveElementRoleProvider, ActiveWorkspaceContextProvider, - OrderedReportIDsContextProvider, + OrderedReportListItemsContextProvider, PlaybackContextProvider, VolumeContextProvider, VideoPopoverMenuContextProvider, diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index cc49c44e0e77..d5c5a6896a9c 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -43,7 +43,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro /** * This is to make sure we don't set the undefined as reportID when * switching between chat list and settings->workspaces tab. - * and doing so avoid unnecessary re-render of `useOrderedReportIDs`. + * and doing so avoid unnecessary re-render of `useOrderedReportListItems`. */ if (reportID) { setCurrentReportID(reportID); diff --git a/src/hooks/useOrderedReportIDs.tsx b/src/hooks/useOrderedReportListItems.tsx similarity index 92% rename from src/hooks/useOrderedReportIDs.tsx rename to src/hooks/useOrderedReportListItems.tsx index 8f071f036fc7..643fcf053012 100644 --- a/src/hooks/useOrderedReportIDs.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -26,13 +26,13 @@ type OnyxProps = { draftComments: OnyxCollection; }; -type WithOrderedReportIDsContextProviderProps = OnyxProps & { +type WithOrderedReportListItemsContextProviderProps = OnyxProps & { children: React.ReactNode; }; -const OrderedReportIDsContext = createContext({}); +const OrderedReportListItemsContext = createContext({}); -function WithOrderedReportIDsContextProvider({ +function WithOrderedReportListItemsContextProvider({ children, chatReports, betas, @@ -43,7 +43,7 @@ function WithOrderedReportIDsContextProvider({ priorityMode, preferredLocale, draftComments, -}: WithOrderedReportIDsContextProviderProps) { +}: WithOrderedReportListItemsContextProviderProps) { const currentReportIDValue = useCurrentReportID(); const personalDetails = usePersonalDetails(); const {canUseViolations} = usePermissions(); @@ -108,7 +108,7 @@ function WithOrderedReportIDsContextProvider({ [orderedReportIDsWithCurrentReport, canUseViolations, personalDetails, draftComments, preferredLocale, chatReports, allReportActions, policies, transactionViolations], ); - return {children}; + return {children}; } /** @@ -174,7 +174,7 @@ const reportActionsSelector = (reportActions: OnyxEntry) => { }); }; -const OrderedReportIDsContextProvider = withOnyx({ +const OrderedReportListItemsContextProvider = withOnyx({ // @ts-expect-error Need some help in determining the correct type for this selector chatReports: { key: ONYXKEYS.COLLECTION.REPORT, @@ -213,10 +213,10 @@ const OrderedReportIDsContextProvider = withOnyx { if (deepEqual(orderedReportListItemsRef.current, orderedReportListItems)) { From df1069c1a1dc695f8faa0dcfd748541bbe1ea8bb Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 18:05:35 +0100 Subject: [PATCH 010/306] perf: use memo for extraData in LHNOptionList --- src/components/LHNOptionsList/LHNOptionsList.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 8b18630a9646..8301bc034a74 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,6 +1,6 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; -import React, {memo, useCallback} from 'react'; +import React, {memo, useCallback, useMemo} from 'react'; import {StyleSheet, View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import {OrderedReports} from '@libs/SidebarUtils'; @@ -53,6 +53,8 @@ function LHNOptionsList({ [shouldDisableFocusOptions, currentReportID, onSelectRow, onLayoutItem, optionMode], ); + const extraData = useMemo(() => [currentReportID], [currentReportID]); + return ( From 1d294bcd188dd768d7c917c48ba19f54bf5a0275 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 19:14:59 +0100 Subject: [PATCH 011/306] fix: typescript fixes --- .../LHNOptionsList/LHNOptionsList.tsx | 5 +- .../LHNOptionsList/OptionRowLHNData.tsx | 14 +--- src/components/LHNOptionsList/types.ts | 74 ++++--------------- src/hooks/useOrderedReportListItems.tsx | 3 +- src/libs/SidebarUtils.ts | 3 +- src/pages/home/sidebar/SidebarLinks.js | 2 +- src/pages/home/sidebar/SidebarLinksData.js | 4 + src/types/onyx/index.ts | 2 + 8 files changed, 27 insertions(+), 80 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 8301bc034a74..1884997a58fe 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -3,13 +3,12 @@ import type {ReactElement} from 'react'; import React, {memo, useCallback, useMemo} from 'react'; import {StyleSheet, View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; -import {OrderedReports} from '@libs/SidebarUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import OptionRowLHNData from './OptionRowLHNData'; -import type {LHNOptionsListProps, RenderItemProps} from './types'; +import type {LHNOptionsListProps, OptionListItem, RenderItemProps} from './types'; -const keyExtractor = (item: OrderedReports) => `report_${item?.reportID}`; +const keyExtractor = (item: OptionListItem) => `report_${item.reportID}`; function LHNOptionsList({ style, diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 622d7b9d9123..bc1a06ce5f67 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -1,10 +1,5 @@ -import {deepEqual} from 'fast-equals'; -import React, {useEffect, useMemo, useRef} from 'react'; -import * as ReportUtils from '@libs/ReportUtils'; -import SidebarUtils from '@libs/SidebarUtils'; +import React, {useEffect} from 'react'; import * as Report from '@userActions/Report'; -import CONST from '@src/CONST'; -import type {OptionData} from '@src/libs/ReportUtils'; import OptionRowLHN from './OptionRowLHN'; import type {OptionRowLHNDataProps} from './types'; @@ -14,12 +9,7 @@ import type {OptionRowLHNDataProps} from './types'; * The OptionRowLHN component is memoized, so it will only * re-render if the data really changed. */ -function OptionRowLHNData({ - isFocused = false, - comment, - optionItem, - ...propsToForward -}: OptionRowLHNDataProps) { +function OptionRowLHNData({isFocused = false, comment, optionItem, ...propsToForward}: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; useEffect(() => { diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 4c79536571bf..3f72748ab8a8 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,38 +1,22 @@ import type {ContentStyle} from '@shopify/flash-list'; import type {RefObject} from 'react'; import type {LayoutChangeEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; 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 {EmptyObject} from '@src/types/utils/EmptyObject'; -import { OrderedReports } from '@libs/SidebarUtils'; type OptionMode = ValueOf; -type LHNOptionsListOnyxProps = { - /** The policy which the user has access to and which the report could be tied to */ - policy: OnyxCollection; - - /** Array of report actions for this report */ - reportActions: OnyxCollection; - - /** Indicates which locale the user currently has selected */ - preferredLocale: OnyxEntry; - - /** List of users' personal details */ - personalDetails: OnyxEntry; - - /** The transaction from the parent report action */ - transactions: OnyxCollection; +type OptionListItem = { + /** The reportID of the report */ + reportID: string; - /** List of draft comments */ - draftComments: OnyxCollection; + /** The item that should be rendered */ + optionItem: OptionData | undefined; - /** The list of transaction violations */ - transactionViolations: OnyxCollection; + /** Comment added to report */ + comment: string; }; type CustomLHNOptionsListProps = { @@ -43,7 +27,7 @@ type CustomLHNOptionsListProps = { contentContainerStyles?: StyleProp; /** Sections for the section list */ - data: string[]; + data: OptionListItem[]; /** Callback to fire when a row is selected */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; @@ -58,51 +42,21 @@ type CustomLHNOptionsListProps = { onFirstItemRendered: () => void; }; -type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps; +type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue; type OptionRowLHNDataProps = { /** Whether row should be focused */ isFocused?: boolean; - /** List of users' personal details */ - personalDetails?: PersonalDetailsList; - - /** The preferred language for the app */ - preferredLocale?: OnyxEntry; - - /** The full data of the report */ - fullReport: OnyxEntry; - - /** The policy which the user has access to and which the report could be tied to */ - policy?: OnyxEntry; - - /** The action from the parent report */ - parentReportAction?: OnyxEntry; - - /** The transaction from the parent report action */ - transaction: OnyxEntry; - - /** The transaction linked to the report's last action */ - lastReportActionTransaction?: OnyxEntry; - /** Comment added to report */ comment: string; - /** The receipt transaction from the parent report action */ - receiptTransactions: OnyxCollection; + /** The item that should be rendered */ + optionItem: OptionData | undefined; /** The reportID of the report */ reportID: string; - /** Array of report actions for this report */ - reportActions: OnyxEntry; - - /** List of transaction violation */ - transactionViolations: OnyxCollection; - - /** Whether the user can use violations */ - canUseViolations: boolean | undefined; - /** Toggle between compact and default view */ viewMode?: OptionMode; @@ -130,11 +84,11 @@ type OptionRowLHNProps = { style?: StyleProp; /** The item that should be rendered */ - optionItem?: OptionData; + optionItem: OptionData | undefined; onLayout?: (event: LayoutChangeEvent) => void; }; -type RenderItemProps = {item: OrderedReports}; +type RenderItemProps = {item: OptionListItem}; -export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps, RenderItemProps}; +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, OptionListItem, RenderItemProps}; diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index 643fcf053012..970f306c3a80 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -8,8 +8,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Locale, Policy, PolicyMembers, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; -import type PriorityMode from '@src/types/onyx/PriorityMode'; +import type {Beta, Locale, Policy, PolicyMembers, PriorityMode, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; import usePermissions from './usePermissions'; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 777a61c70733..2e929639270e 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -4,12 +4,11 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetails, PersonalDetailsList, TransactionViolation} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, ReportActions, TransactionViolation} from '@src/types/onyx'; import type Beta from '@src/types/onyx/Beta'; import type Policy from '@src/types/onyx/Policy'; import type PriorityMode from '@src/types/onyx/PriorityMode'; import type Report from '@src/types/onyx/Report'; -import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; import * as CollectionUtils from './CollectionUtils'; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 8cda70d2486a..4c9803e257bc 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -33,7 +33,7 @@ const basePropTypes = { const propTypes = { ...basePropTypes, - optionListItems: PropTypes.arrayOf(PropTypes.string).isRequired, + optionListItems: PropTypes.arrayOf(PropTypes.object).isRequired, isLoading: PropTypes.bool.isRequired, diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 91de05450aae..088899425a0b 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -31,12 +31,16 @@ const propTypes = { network: networkPropTypes.isRequired, + // eslint-disable-next-line react/forbid-prop-types + policyMembers: PropTypes.object, + ...withCurrentUserPersonalDetailsPropTypes, }; const defaultProps = { isLoadingApp: true, priorityMode: CONST.PRIORITY_MODE.DEFAULT, + policyMembers: {}, ...withCurrentUserPersonalDetailsDefaultProps, }; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 6846fc302639..f6147a27a49b 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -37,6 +37,7 @@ import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type {PolicyReportField, PolicyReportFields} from './PolicyReportField'; import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; +import type PriorityMode from './PriorityMode'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; @@ -111,6 +112,7 @@ export type { PolicyTag, PolicyTags, PolicyTagList, + PriorityMode, PrivatePersonalDetails, RecentWaypoint, RecentlyUsedCategories, From 7dfba4379e6bc48ef256c6363743ea13ad65b614 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 20:08:53 +0100 Subject: [PATCH 012/306] fix: update reportActionsSelector to match the one from SidebarLinksData --- src/hooks/useOrderedReportListItems.tsx | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index 970f306c3a80..fa2d18684464 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -1,3 +1,5 @@ +import lodashGet from 'lodash/get'; +import lodashMap from 'lodash/map'; import React, {createContext, useCallback, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; @@ -152,29 +154,27 @@ const chatReportSelector = (report: OnyxEntry) => isUnreadWithMention: ReportUtils.isUnreadWithMention(report), }; -const reportActionsSelector = (reportActions: OnyxEntry) => { - if (!reportActions) { - return []; - } +const reportActionsSelector = (reportActions: OnyxEntry) => + reportActions && + lodashMap(reportActions, (reportAction) => { + const {reportActionID, parentReportActionID, actionName, errors = [], originalMessage} = reportAction; + const decision = lodashGet(reportAction, 'message[0].moderationDecision.decision'); - return Object.values(reportActions).map((reportAction) => { - const {reportActionID, actionName, originalMessage} = reportAction ?? {}; - const decision = reportAction?.message?.[0]?.moderationDecision?.decision; return { reportActionID, + parentReportActionID, actionName, - originalMessage, + errors, message: [ { moderationDecision: {decision}, }, ], + originalMessage, }; }); -}; const OrderedReportListItemsContextProvider = withOnyx({ - // @ts-expect-error Need some help in determining the correct type for this selector chatReports: { key: ONYXKEYS.COLLECTION.REPORT, selector: chatReportSelector, @@ -190,7 +190,6 @@ const OrderedReportListItemsContextProvider = withOnyx Date: Thu, 29 Feb 2024 18:01:07 +0500 Subject: [PATCH 013/306] fix: typescript issues --- src/hooks/useOrderedReportListItems.tsx | 70 +------------------------ src/libs/SidebarUtils.ts | 4 +- 2 files changed, 4 insertions(+), 70 deletions(-) diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index fa2d18684464..d368abd897af 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -1,9 +1,7 @@ -import lodashGet from 'lodash/get'; -import lodashMap from 'lodash/map'; import React, {createContext, useCallback, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {usePersonalDetails} from '@components/OnyxProvider'; +import {usePersonalDetails, useReport} from '@components/OnyxProvider'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -19,7 +17,7 @@ type OnyxProps = { chatReports: OnyxCollection; betas: OnyxEntry; policies: OnyxCollection; - allReportActions: OnyxCollection; + allReportActions: OnyxCollection; transactionViolations: OnyxCollection; policyMembers: OnyxCollection; priorityMode: OnyxEntry; @@ -112,72 +110,9 @@ function WithOrderedReportListItemsContextProvider({ return {children}; } -/** - * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering - * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. - */ -const chatReportSelector = (report: OnyxEntry) => - report && { - reportID: report.reportID, - participantAccountIDs: report.participantAccountIDs, - hasDraft: report.hasDraft, - isPinned: report.isPinned, - isHidden: report.isHidden, - notificationPreference: report.notificationPreference, - errorFields: {addWorkspaceRoom: report.errorFields?.addWorkspaceRoom}, - lastMessageText: report.lastMessageText, - lastVisibleActionCreated: report.lastVisibleActionCreated, - iouReportID: report.iouReportID, - total: report.total, - nonReimbursableTotal: report.nonReimbursableTotal, - hasOutstandingChildRequest: report.hasOutstandingChildRequest, - isWaitingOnBankAccount: report.isWaitingOnBankAccount, - statusNum: report.statusNum, - stateNum: report.stateNum, - chatType: report.chatType, - type: report.type, - policyID: report.policyID, - visibility: report.visibility, - lastReadTime: report.lastReadTime, - // Needed for name sorting: - reportName: report.reportName, - policyName: report.policyName, - oldPolicyName: report.oldPolicyName, - // Other less obvious properites considered for sorting: - ownerAccountID: report.ownerAccountID, - currency: report.currency, - managerID: report.managerID, - // Other important less obivous properties for filtering: - parentReportActionID: report.parentReportActionID, - parentReportID: report.parentReportID, - isDeletedParentAction: report.isDeletedParentAction, - isUnreadWithMention: ReportUtils.isUnreadWithMention(report), - }; - -const reportActionsSelector = (reportActions: OnyxEntry) => - reportActions && - lodashMap(reportActions, (reportAction) => { - const {reportActionID, parentReportActionID, actionName, errors = [], originalMessage} = reportAction; - const decision = lodashGet(reportAction, 'message[0].moderationDecision.decision'); - - return { - reportActionID, - parentReportActionID, - actionName, - errors, - message: [ - { - moderationDecision: {decision}, - }, - ], - originalMessage, - }; - }); - const OrderedReportListItemsContextProvider = withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector, initialValue: {}, }, priorityMode: { @@ -190,7 +125,6 @@ const OrderedReportListItemsContextProvider = withOnyx, policies: OnyxCollection, priorityMode: OnyxEntry, - allReportActions: OnyxCollection, + allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], @@ -80,7 +80,7 @@ function getOrderedReportIDs( 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 parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '']; const doesReportHaveViolations = !!betas && betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); return ReportUtils.shouldReportBeInOptionList({ From 6739b587e93144f2dfa7a2250ac15e3b11488289 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 29 Feb 2024 18:01:30 +0500 Subject: [PATCH 014/306] test: fix reassure failing test --- tests/perf-test/SidebarUtils.perf-test.ts | 26 ++++++--------------- tests/utils/LHNTestUtils.tsx | 3 ++- tests/utils/collections/createCollection.ts | 21 +++++++++++++++++ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 3aa65331b9c2..6fb63878a832 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -8,7 +8,7 @@ import type {PersonalDetails, 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 createCollection from '../utils/collections/createCollection'; +import createCollection, {createNestedCollection} from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; import createRandomReportAction from '../utils/collections/reportActions'; @@ -27,6 +27,12 @@ const reportActions = createCollection( (index) => createRandomReportAction(index), ); +const allReportActions = createNestedCollection( + (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, + (item) => `${item.reportActionID}`, + (index) => createRandomReportAction(index), +); + const personalDetails = createCollection( (item) => item.accountID, (index) => createPersonalDetails(index), @@ -82,24 +88,6 @@ describe('SidebarUtils', () => { (index) => createRandomPolicy(index), ); - const allReportActions = Object.fromEntries( - Object.keys(reportActions).map((key) => [ - key, - [ - { - errors: reportActions[key].errors ?? [], - message: [ - { - moderationDecision: { - decision: reportActions[key].message?.[0]?.moderationDecision?.decision, - }, - }, - ], - }, - ], - ]), - ) as unknown as OnyxCollection; - await waitForBatchedUpdates(); await measureFunction(() => SidebarUtils.getOrderedReportIDs(currentReportId, allReports, betas, policies, CONST.PRIORITY_MODE.DEFAULT, allReportActions, transactionViolations)); }); diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 80f28002f975..a10dbbdc17f8 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -10,6 +10,7 @@ import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxProvider from '@components/OnyxProvider'; import {CurrentReportIDContextProvider} from '@components/withCurrentReportID'; import {EnvironmentProvider} from '@components/withEnvironment'; +import {OrderedReportListItemsContextProvider} from '@hooks/useOrderedReportListItems'; import DateUtils from '@libs/DateUtils'; import ReportActionItemSingle from '@pages/home/report/ReportActionItemSingle'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; @@ -280,7 +281,7 @@ function getFakeAdvancedReportAction(actionName: ActionName = 'IOU', actor = 'em function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { return ( - + {}} diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index 848ef8f81f47..e62d9769bc53 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -9,3 +9,24 @@ export default function createCollection(createKey: (item: T, index: number) return map; } + +export function createNestedCollection( + createParentKey: (item: T, index: number) => string | number, + createKey: (item: T, index: number) => string | number, + createItem: (index: number) => T, + length = 500, +): Record> { + const map: Record> = {}; + + for (let i = 0; i < length; i++) { + const item = createItem(i); + const itemKey = createKey(item, i); + const itemParentKey = createParentKey(item, i); + map[itemParentKey] = { + ...map[itemParentKey], + [itemKey]: item, + }; + } + + return map; +} From 92fae0c08c3c3ca517fb57313f95e4fd2e77abf0 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 29 Feb 2024 18:02:17 +0500 Subject: [PATCH 015/306] revert: move option item data calculation to the renderItem component --- .../LHNOptionsList/LHNOptionsList.tsx | 117 +++++++++++++++--- .../LHNOptionsList/OptionRowLHNData.tsx | 63 +++++++++- src/components/LHNOptionsList/types.ts | 74 +++++++++-- src/hooks/useOrderedReportListItems.tsx | 47 +------ 4 files changed, 226 insertions(+), 75 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 1884997a58fe..5569e53942aa 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -2,13 +2,18 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; import React, {memo, useCallback, useMemo} from 'react'; import {StyleSheet, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import withCurrentReportID from '@components/withCurrentReportID'; +import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import OptionRowLHNData from './OptionRowLHNData'; -import type {LHNOptionsListProps, OptionListItem, RenderItemProps} from './types'; +import type {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; -const keyExtractor = (item: OptionListItem) => `report_${item.reportID}`; +const keyExtractor = (item: string) => `report_${item}`; function LHNOptionsList({ style, @@ -18,9 +23,18 @@ function LHNOptionsList({ shouldDisableFocusOptions = false, currentReportID = '', optionMode, + reports = {}, + reportActions = {}, + policy = {}, + preferredLocale = CONST.LOCALES.DEFAULT, + personalDetails = {}, + transactions = {}, + draftComments = {}, + transactionViolations = {}, onFirstItemRendered = () => {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); + const {canUseViolations} = usePermissions(); // 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. @@ -38,18 +52,64 @@ function LHNOptionsList({ * Function which renders a row in the list */ const renderItem = useCallback( - ({item}: RenderItemProps): ReactElement => ( - - ), - [shouldDisableFocusOptions, currentReportID, onSelectRow, onLayoutItem, optionMode], + ({item: reportID}: RenderItemProps): ReactElement => { + const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; + const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; + const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; + const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : ''; + const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; + const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; + const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions); + const lastReportAction = sortedReportActions[0]; + + // Get the transaction for the last report action + let lastReportActionTransactionID = ''; + + if (lastReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { + lastReportActionTransactionID = lastReportAction.originalMessage?.IOUTransactionID ?? ''; + } + const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`] ?? {}; + + return ( + + ); + }, + [ + currentReportID, + draftComments, + onSelectRow, + optionMode, + personalDetails, + policy, + preferredLocale, + reportActions, + reports, + shouldDisableFocusOptions, + transactions, + transactionViolations, + canUseViolations, + onLayoutItem, + ], ); const extraData = useMemo(() => [currentReportID], [currentReportID]); @@ -74,6 +134,33 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; -export default memo(LHNOptionsList); +export default withCurrentReportID( + withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + reportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + }, + policy: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + preferredLocale: { + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + transactions: { + key: ONYXKEYS.COLLECTION.TRANSACTION, + }, + draftComments: { + key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, + }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + }, + })(memo(LHNOptionsList)), +); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index bc1a06ce5f67..a18d5a8ec1ec 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -1,5 +1,10 @@ -import React, {useEffect} from 'react'; +import {deepEqual} from 'fast-equals'; +import React, {useEffect, useMemo, useRef} from 'react'; +import * as ReportUtils from '@libs/ReportUtils'; +import SidebarUtils from '@libs/SidebarUtils'; import * as Report from '@userActions/Report'; +import CONST from '@src/CONST'; +import type {OptionData} from '@src/libs/ReportUtils'; import OptionRowLHN from './OptionRowLHN'; import type {OptionRowLHNDataProps} from './types'; @@ -9,9 +14,63 @@ import type {OptionRowLHNDataProps} from './types'; * The OptionRowLHN component is memoized, so it will only * re-render if the data really changed. */ -function OptionRowLHNData({isFocused = false, comment, optionItem, ...propsToForward}: OptionRowLHNDataProps) { +function OptionRowLHNData({ + isFocused = false, + fullReport, + reportActions, + personalDetails = {}, + preferredLocale = CONST.LOCALES.DEFAULT, + comment, + policy, + receiptTransactions, + parentReportAction, + transaction, + lastReportActionTransaction = {}, + transactionViolations, + canUseViolations, + ...propsToForward +}: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; + const optionItemRef = useRef(); + + const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(fullReport, transactionViolations, parentReportAction ?? null); + + const optionItem = useMemo(() => { + // 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, + hasViolations: !!hasViolations, + }); + if (deepEqual(item, optionItemRef.current)) { + return optionItemRef.current; + } + + optionItemRef.current = item; + + return item; + // Listen parentReportAction to update title of thread report when parentReportAction changed + // Listen to transaction to update title of transaction report when transaction changed + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + fullReport, + lastReportActionTransaction, + reportActions, + personalDetails, + preferredLocale, + policy, + parentReportAction, + transaction, + transactionViolations, + canUseViolations, + receiptTransactions, + ]); + useEffect(() => { if (!optionItem || !!optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { return; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 3f72748ab8a8..e09e5cb6c8b5 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,22 +1,40 @@ import type {ContentStyle} from '@shopify/flash-list'; import type {RefObject} from 'react'; import type {LayoutChangeEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; 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 {EmptyObject} from '@src/types/utils/EmptyObject'; type OptionMode = ValueOf; -type OptionListItem = { - /** The reportID of the report */ - reportID: string; +type LHNOptionsListOnyxProps = { + /** The policy which the user has access to and which the report could be tied to */ + policy: OnyxCollection; - /** The item that should be rendered */ - optionItem: OptionData | undefined; + /** All reports shared with the user */ + reports: OnyxCollection; - /** Comment added to report */ - comment: string; + /** Array of report actions for this report */ + reportActions: OnyxCollection; + + /** Indicates which locale the user currently has selected */ + preferredLocale: OnyxEntry; + + /** List of users' personal details */ + personalDetails: OnyxEntry; + + /** The transaction from the parent report action */ + transactions: OnyxCollection; + + /** List of draft comments */ + draftComments: OnyxCollection; + + /** The list of transaction violations */ + transactionViolations: OnyxCollection; }; type CustomLHNOptionsListProps = { @@ -27,7 +45,7 @@ type CustomLHNOptionsListProps = { contentContainerStyles?: StyleProp; /** Sections for the section list */ - data: OptionListItem[]; + data: string[]; /** Callback to fire when a row is selected */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; @@ -42,21 +60,51 @@ type CustomLHNOptionsListProps = { onFirstItemRendered: () => void; }; -type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue; +type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps; type OptionRowLHNDataProps = { /** Whether row should be focused */ isFocused?: boolean; + /** List of users' personal details */ + personalDetails?: PersonalDetailsList; + + /** The preferred language for the app */ + preferredLocale?: OnyxEntry; + + /** The full data of the report */ + fullReport: OnyxEntry; + + /** The policy which the user has access to and which the report could be tied to */ + policy?: OnyxEntry; + + /** The action from the parent report */ + parentReportAction?: OnyxEntry; + + /** The transaction from the parent report action */ + transaction: OnyxEntry; + + /** The transaction linked to the report's last action */ + lastReportActionTransaction?: OnyxEntry; + /** Comment added to report */ comment: string; - /** The item that should be rendered */ - optionItem: OptionData | undefined; + /** The receipt transaction from the parent report action */ + receiptTransactions: OnyxCollection; /** The reportID of the report */ reportID: string; + /** Array of report actions for this report */ + reportActions: OnyxEntry; + + /** List of transaction violation */ + transactionViolations: OnyxCollection; + + /** Whether the user can use violations */ + canUseViolations: boolean | undefined; + /** Toggle between compact and default view */ viewMode?: OptionMode; @@ -89,6 +137,6 @@ type OptionRowLHNProps = { onLayout?: (event: LayoutChangeEvent) => void; }; -type RenderItemProps = {item: OptionListItem}; +type RenderItemProps = {item: string}; -export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, OptionListItem, RenderItemProps}; +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps, RenderItemProps}; diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index d368abd897af..f9aaeaf400c1 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -1,17 +1,14 @@ import React, {createContext, useCallback, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {usePersonalDetails, useReport} from '@components/OnyxProvider'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Locale, Policy, PolicyMembers, PriorityMode, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; -import usePermissions from './usePermissions'; type OnyxProps = { chatReports: OnyxCollection; @@ -21,8 +18,6 @@ type OnyxProps = { transactionViolations: OnyxCollection; policyMembers: OnyxCollection; priorityMode: OnyxEntry; - preferredLocale: OnyxEntry; - draftComments: OnyxCollection; }; type WithOrderedReportListItemsContextProviderProps = OnyxProps & { @@ -40,12 +35,8 @@ function WithOrderedReportListItemsContextProvider({ transactionViolations, policyMembers, priorityMode, - preferredLocale, - draftComments, }: WithOrderedReportListItemsContextProviderProps) { const currentReportIDValue = useCurrentReportID(); - const personalDetails = usePersonalDetails(); - const {canUseViolations} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, getCurrentUserAccountID()), [activeWorkspaceID, policyMembers]); @@ -80,34 +71,7 @@ function WithOrderedReportListItemsContextProvider({ return orderedReportIDs; }, [getOrderedReportIDs, currentReportIDValue?.currentReportID, orderedReportIDs]); - const orderedReportListItems = useMemo( - () => - orderedReportIDsWithCurrentReport.map((reportID) => { - const itemFullReport = chatReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; - const itemReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; - const itemParentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; - const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; - const itemPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; - const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; - - const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(itemFullReport, transactionViolations, itemParentReportAction ?? null); - - const item = SidebarUtils.getOptionData({ - report: itemFullReport, - reportActions: itemReportActions, - personalDetails, - preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, - policy: itemPolicy, - parentReportAction: itemParentReportAction, - hasViolations: !!hasViolations, - }); - - return {reportID, optionItem: item, comment: itemComment}; - }), - [orderedReportIDsWithCurrentReport, canUseViolations, personalDetails, draftComments, preferredLocale, chatReports, allReportActions, policies, transactionViolations], - ); - - return {children}; + return {children}; } const OrderedReportListItemsContextProvider = withOnyx({ @@ -138,13 +102,6 @@ const OrderedReportListItemsContextProvider = withOnyx Date: Thu, 29 Feb 2024 19:48:44 +0500 Subject: [PATCH 016/306] fix: linting --- src/hooks/useOrderedReportListItems.tsx | 2 +- src/pages/home/sidebar/SidebarLinks.js | 2 +- tests/utils/collections/createCollection.ts | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index f9aaeaf400c1..bcd22320d1ca 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -6,7 +6,7 @@ import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportActions, TransactionViolation} from '@src/types/onyx'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 4c9803e257bc..8cda70d2486a 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -33,7 +33,7 @@ const basePropTypes = { const propTypes = { ...basePropTypes, - optionListItems: PropTypes.arrayOf(PropTypes.object).isRequired, + optionListItems: PropTypes.arrayOf(PropTypes.string).isRequired, isLoading: PropTypes.bool.isRequired, diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index e62d9769bc53..bcc37c301279 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -10,7 +10,7 @@ export default function createCollection(createKey: (item: T, index: number) return map; } -export function createNestedCollection( +function createNestedCollection( createParentKey: (item: T, index: number) => string | number, createKey: (item: T, index: number) => string | number, createItem: (index: number) => T, @@ -30,3 +30,7 @@ export function createNestedCollection( return map; } + +export { + createNestedCollection, +} \ No newline at end of file From e735658f12826766791c2bfa1d3bb7b82b4559d6 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 1 Mar 2024 13:30:58 +0500 Subject: [PATCH 017/306] test: fix failing test --- .../LHNOptionsList/LHNOptionsList.tsx | 2 +- src/components/withCurrentReportID.tsx | 2 +- src/hooks/useOrderedReportListItems.tsx | 18 +++++++-- tests/unit/SidebarOrderTest.ts | 6 +-- tests/utils/LHNTestUtils.tsx | 37 ++++++++++++------- 5 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 5569e53942aa..d5b422122144 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -112,7 +112,7 @@ function LHNOptionsList({ ], ); - const extraData = useMemo(() => [currentReportID], [currentReportID]); + const extraData = useMemo(() => [reportActions, reports, policy, personalDetails], [reportActions, reports, policy, personalDetails]); return ( diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index d5c5a6896a9c..0d052f759f81 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -45,7 +45,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro * switching between chat list and settings->workspaces tab. * and doing so avoid unnecessary re-render of `useOrderedReportListItems`. */ - if (reportID) { + if (reportID !== undefined) { setCurrentReportID(reportID); } }, diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index bcd22320d1ca..b160c372679a 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -22,6 +22,7 @@ type OnyxProps = { type WithOrderedReportListItemsContextProviderProps = OnyxProps & { children: React.ReactNode; + currentReportIDForTests?: string; }; const OrderedReportListItemsContext = createContext({}); @@ -35,8 +36,19 @@ function WithOrderedReportListItemsContextProvider({ transactionViolations, policyMembers, priorityMode, + /** + * Only required to make unit tests work, since we + * explicitly pass the currentReportID in LHNTestUtils + * to SidebarLinksData, so this context doesn't have an + * access to currentReportID in that case. + * + * So this is a work around to have currentReportID available + * only in testing environment. + */ + currentReportIDForTests, }: WithOrderedReportListItemsContextProviderProps) { const currentReportIDValue = useCurrentReportID(); + const derivedCurrentReportID = currentReportIDForTests ?? currentReportIDValue?.currentReportID; const {activeWorkspaceID} = useActiveWorkspace(); const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, getCurrentUserAccountID()), [activeWorkspaceID, policyMembers]); @@ -65,11 +77,11 @@ function WithOrderedReportListItemsContextProvider({ // the current report is missing from the list, which should very rarely happen. In this // case we re-generate the list a 2nd time with the current report included. const orderedReportIDsWithCurrentReport = useMemo(() => { - if (currentReportIDValue?.currentReportID && !orderedReportIDs.includes(currentReportIDValue.currentReportID)) { - return getOrderedReportIDs(currentReportIDValue.currentReportID); + if (derivedCurrentReportID && !orderedReportIDs.includes(derivedCurrentReportID)) { + return getOrderedReportIDs(derivedCurrentReportID); } return orderedReportIDs; - }, [getOrderedReportIDs, currentReportIDValue?.currentReportID, orderedReportIDs]); + }, [getOrderedReportIDs, derivedCurrentReportID, orderedReportIDs]); return {children}; } diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index 27da8348f43d..10d30e4c6dc8 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -298,7 +298,7 @@ describe('Sidebar', () => { Onyx.multiSet({ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, + [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, }), ) @@ -362,7 +362,7 @@ describe('Sidebar', () => { Onyx.multiSet({ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, + [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, }), ) @@ -429,7 +429,7 @@ describe('Sidebar', () => { Onyx.multiSet({ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, + [ONYXKEYS.IS_LOADING_APP]: false, [`${ONYXKEYS.COLLECTION.POLICY}${fakeReport.policyID}`]: fakePolicy, ...reportCollectionDataSet, }), diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index a10dbbdc17f8..c8c3f145d951 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -281,19 +281,30 @@ function getFakeAdvancedReportAction(actionName: ActionName = 'IOU', actor = 'em function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { return ( - - {}} - insets={{ - top: 0, - left: 0, - right: 0, - bottom: 0, - }} - isSmallScreenWidth={false} - currentReportID={currentReportID} - /> + + {/* + * Only required to make unit tests work, since we + * explicitly pass the currentReportID in LHNTestUtils + * to SidebarLinksData, so this context doesn't have an + * access to currentReportID in that case. + * + * So this is a work around to have currentReportID available + * only in testing environment. + * */} + + {}} + insets={{ + top: 0, + left: 0, + right: 0, + bottom: 0, + }} + isSmallScreenWidth={false} + currentReportID={currentReportID} + /> + ); } From 94fcc48f89f95ba50fc22a20ac04183dbd25cb8a Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 1 Mar 2024 13:31:12 +0500 Subject: [PATCH 018/306] fix: prettier --- tests/utils/collections/createCollection.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index bcc37c301279..4a2a1fa4eb78 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -31,6 +31,4 @@ function createNestedCollection( return map; } -export { - createNestedCollection, -} \ No newline at end of file +export {createNestedCollection}; From e0673e0baf75e6610c39c234cd758e8958b6157c Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 5 Mar 2024 14:50:54 +0500 Subject: [PATCH 019/306] refactor: rename to useReportIDs --- src/App.tsx | 4 +- ...edReportListItems.tsx => useReportIDs.tsx} | 66 +++++++++++-------- src/pages/home/sidebar/SidebarLinksData.js | 4 +- tests/utils/LHNTestUtils.tsx | 6 +- 4 files changed, 46 insertions(+), 34 deletions(-) rename src/hooks/{useOrderedReportListItems.tsx => useReportIDs.tsx} (72%) diff --git a/src/App.tsx b/src/App.tsx index 3a294757e149..6cfc2587074b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,7 +29,7 @@ import {KeyboardStateProvider} from './components/withKeyboardState'; import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; -import {OrderedReportListItemsContextProvider} from './hooks/useOrderedReportListItems'; +import {ReportIDsContextProvider} from './hooks/useReportIDs'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import InitialUrlContext from './libs/InitialUrlContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; @@ -76,7 +76,7 @@ function App({url}: AppProps) { CustomStatusBarAndBackgroundContextProvider, ActiveElementRoleProvider, ActiveWorkspaceContextProvider, - OrderedReportListItemsContextProvider, + ReportIDsContextProvider, PlaybackContextProvider, VolumeContextProvider, VideoPopoverMenuContextProvider, diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useReportIDs.tsx similarity index 72% rename from src/hooks/useOrderedReportListItems.tsx rename to src/hooks/useReportIDs.tsx index 4580df82ed34..651e21f08b24 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useReportIDs.tsx @@ -1,7 +1,7 @@ +import _ from 'lodash'; import React, {createContext, useCallback, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import _ from 'underscore'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; @@ -9,6 +9,7 @@ import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx'; +import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; import usePermissions from './usePermissions'; @@ -24,14 +25,22 @@ type OnyxProps = { allTransactions: OnyxCollection; }; -type WithOrderedReportListItemsContextProviderProps = OnyxProps & { +type WithReportIDsContextProviderProps = OnyxProps & { children: React.ReactNode; currentReportIDForTests?: string; }; -const OrderedReportListItemsContext = createContext({}); +type ReportIDsContextValue = { + orderedReportIDs: string[]; + reportIDsWithErrors: Record; +}; + +const ReportIDsContext = createContext({ + orderedReportIDs: [], + reportIDsWithErrors: {}, +}); -function WithOrderedReportListItemsContextProvider({ +function WithReportIDsContextProvider({ children, chatReports, betas, @@ -51,7 +60,7 @@ function WithOrderedReportListItemsContextProvider({ * only in testing environment. */ currentReportIDForTests, -}: WithOrderedReportListItemsContextProviderProps) { +}: WithReportIDsContextProviderProps) { const currentReportIDValue = useCurrentReportID(); const derivedCurrentReportID = currentReportIDForTests ?? currentReportIDValue?.currentReportID; const {activeWorkspaceID} = useActiveWorkspace(); @@ -59,22 +68,25 @@ function WithOrderedReportListItemsContextProvider({ const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, getCurrentUserAccountID()), [activeWorkspaceID, policyMembers]); - const chatReportsKeys = useMemo(() => _.keys(chatReports), [chatReports]); - const reportIDsWithErrors = useMemo(() => { - return _.reduce( - chatReportsKeys, - (errorsMap, reportKey) => { - const report = chatReports && chatReports[reportKey]; - const allReportsActions = allReportActions && 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}; - }, - {}, - ); - }, [chatReportsKeys, allReportActions, allTransactions, chatReports]); + const chatReportsKeys = useMemo(() => Object.keys(chatReports ?? {}), [chatReports]); + // eslint-disable-next-line you-dont-need-lodash-underscore/reduce + const reportIDsWithErrors = useMemo( + () => + _.reduce( + chatReportsKeys, + (errorsMap, reportKey) => { + const report = chatReports?.[reportKey] ?? null; + const allReportsActions = allReportActions?.[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS)] ?? null; + const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions, allTransactions) || {}; + if (Object.values(errors).length === 0) { + return errorsMap; + } + return {...errorsMap, [reportKey.replace(ONYXKEYS.COLLECTION.REPORT, '')]: errors}; + }, + {}, + ), + [chatReportsKeys, allReportActions, allTransactions, chatReports], + ); const getOrderedReportIDs = useCallback( (currentReportID?: string) => @@ -116,10 +128,10 @@ function WithOrderedReportListItemsContextProvider({ [orderedReportIDsWithCurrentReport, reportIDsWithErrors], ); - return {children}; + return {children}; } -const OrderedReportListItemsContextProvider = withOnyx({ +const ReportIDsContextProvider = withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, initialValue: {}, @@ -151,10 +163,10 @@ const OrderedReportListItemsContextProvider = withOnyx { if (deepEqual(orderedReportIDsRef.current, orderedReportIDs)) { diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index c8c3f145d951..04344ba71184 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -10,7 +10,7 @@ import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxProvider from '@components/OnyxProvider'; import {CurrentReportIDContextProvider} from '@components/withCurrentReportID'; import {EnvironmentProvider} from '@components/withEnvironment'; -import {OrderedReportListItemsContextProvider} from '@hooks/useOrderedReportListItems'; +import {ReportIDsContextProvider} from '@hooks/useReportIDs'; import DateUtils from '@libs/DateUtils'; import ReportActionItemSingle from '@pages/home/report/ReportActionItemSingle'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; @@ -291,7 +291,7 @@ function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { * So this is a work around to have currentReportID available * only in testing environment. * */} - + {}} @@ -304,7 +304,7 @@ function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { isSmallScreenWidth={false} currentReportID={currentReportID} /> - + ); } From 30ca3030ee04390772e1a03bc69305fbf4160600 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 5 Mar 2024 14:51:43 +0500 Subject: [PATCH 020/306] refactor: don't set currentReportID if it's on workspaces screen --- src/components/withCurrentReportID.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index 0d052f759f81..54cdc84f127a 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -4,6 +4,7 @@ import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {createContext, forwardRef, useCallback, useMemo, useState} from 'react'; import getComponentDisplayName from '@libs/getComponentDisplayName'; import Navigation from '@libs/Navigation/Navigation'; +import SCREENS from '@src/SCREENS'; type CurrentReportIDContextValue = { updateCurrentReportID: (state: NavigationState) => void; @@ -40,14 +41,17 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro const updateCurrentReportID = useCallback( (state: NavigationState) => { const reportID = Navigation.getTopmostReportId(state) ?? ''; + /** * This is to make sure we don't set the undefined as reportID when * switching between chat list and settings->workspaces tab. - * and doing so avoid unnecessary re-render of `useOrderedReportListItems`. + * and doing so avoid unnecessary re-render of `useReportIDs`. */ - if (reportID !== undefined) { - setCurrentReportID(reportID); + const params = state.routes[state.index].params; + if (params && 'screen' in params && params.screen === SCREENS.SETTINGS.WORKSPACES) { + return; } + setCurrentReportID(reportID); }, [setCurrentReportID], ); From 121b5378606b353ecc014df082c140dfe5737d4f Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 5 Mar 2024 14:51:53 +0500 Subject: [PATCH 021/306] refactor: remove dead code --- tests/perf-test/SidebarUtils.perf-test.ts | 8 +------ tests/utils/collections/createCollection.ts | 23 --------------------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 8672d7c0cafe..2b2bdbc6b57a 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -10,7 +10,7 @@ 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, {createNestedCollection} from '../utils/collections/createCollection'; +import createCollection from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; import createRandomReportAction from '../utils/collections/reportActions'; @@ -29,12 +29,6 @@ const reportActions = createCollection( (index) => createRandomReportAction(index), ); -// const allReportActions = createNestedCollection( -// (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, -// (item) => `${item.reportActionID}`, -// (index) => createRandomReportAction(index), -// ); - const personalDetails = createCollection( (item) => item.accountID, (index) => createPersonalDetails(index), diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index 4a2a1fa4eb78..848ef8f81f47 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -9,26 +9,3 @@ export default function createCollection(createKey: (item: T, index: number) return map; } - -function createNestedCollection( - createParentKey: (item: T, index: number) => string | number, - createKey: (item: T, index: number) => string | number, - createItem: (index: number) => T, - length = 500, -): Record> { - const map: Record> = {}; - - for (let i = 0; i < length; i++) { - const item = createItem(i); - const itemKey = createKey(item, i); - const itemParentKey = createParentKey(item, i); - map[itemParentKey] = { - ...map[itemParentKey], - [itemKey]: item, - }; - } - - return map; -} - -export {createNestedCollection}; From 9e41603f9da5a3eb38873a8ab24476c2eadacdaf Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 5 Mar 2024 15:13:57 +0500 Subject: [PATCH 022/306] fix: linting --- src/hooks/useReportIDs.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index 651e21f08b24..2e7c63182646 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -67,11 +67,10 @@ function WithReportIDsContextProvider({ const {canUseViolations} = usePermissions(); const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, getCurrentUserAccountID()), [activeWorkspaceID, policyMembers]); - const chatReportsKeys = useMemo(() => Object.keys(chatReports ?? {}), [chatReports]); - // eslint-disable-next-line you-dont-need-lodash-underscore/reduce const reportIDsWithErrors = useMemo( () => + // eslint-disable-next-line you-dont-need-lodash-underscore/reduce _.reduce( chatReportsKeys, (errorsMap, reportKey) => { From 4a3284161b02ea616cc40a1bc741221672e6e777 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 5 Mar 2024 15:17:33 +0500 Subject: [PATCH 023/306] refactor: remove irrelevant changes --- src/components/LHNOptionsList/LHNOptionsList.tsx | 4 ++-- src/components/LHNOptionsList/types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 31af022a12aa..be8ce677b641 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -20,15 +20,15 @@ function LHNOptionsList({ contentContainerStyles, data, onSelectRow, - shouldDisableFocusOptions = false, - currentReportID = '', optionMode, + shouldDisableFocusOptions = false, reports = {}, reportActions = {}, policy = {}, preferredLocale = CONST.LOCALES.DEFAULT, personalDetails = {}, transactions = {}, + currentReportID = '', draftComments = {}, transactionViolations = {}, onFirstItemRendered = () => {}, diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 0d1bda775255..c122ab018392 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -139,7 +139,7 @@ type OptionRowLHNProps = { style?: StyleProp; /** The item that should be rendered */ - optionItem: OptionData | undefined; + optionItem?: OptionData; onLayout?: (event: LayoutChangeEvent) => void; }; From 581d09d7a3dec1f1c301c7dccfba4877e60cb7c4 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 11 Mar 2024 11:33:46 +0500 Subject: [PATCH 024/306] fix: comments --- src/components/withCurrentReportID.tsx | 2 +- src/hooks/useReportIDs.tsx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index 54cdc84f127a..bb3283a21d25 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -45,7 +45,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro /** * This is to make sure we don't set the undefined as reportID when * switching between chat list and settings->workspaces tab. - * and doing so avoid unnecessary re-render of `useReportIDs`. + * and doing so avoids an unnecessary re-render of `useReportIDs`. */ const params = state.routes[state.index].params; if (params && 'screen' in params && params.screen === SCREENS.SETTINGS.WORKSPACES) { diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index 2e7c63182646..547975ae1cd0 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -56,8 +56,7 @@ function WithReportIDsContextProvider({ * to SidebarLinksData, so this context doesn't have an * access to currentReportID in that case. * - * So this is a work around to have currentReportID available - * only in testing environment. + * This is a workaround to have currentReportID available in testing environment. */ currentReportIDForTests, }: WithReportIDsContextProviderProps) { @@ -109,7 +108,7 @@ function WithReportIDsContextProvider({ // 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 - // we first generate the list as if there was no current report, then here we check if + // we first generate the list as if there was no current report, then we check if // the current report is missing from the list, which should very rarely happen. In this // case we re-generate the list a 2nd time with the current report included. const orderedReportIDsWithCurrentReport = useMemo(() => { From fffe41ac7ed346c542da74cbe7952e581be0eb1c Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 11 Mar 2024 11:58:40 +0500 Subject: [PATCH 025/306] fix: safely check the nested properties --- src/components/withCurrentReportID.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index bb3283a21d25..22f68de9f57a 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -47,7 +47,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro * switching between chat list and settings->workspaces tab. * and doing so avoids an unnecessary re-render of `useReportIDs`. */ - const params = state.routes[state.index].params; + const params = state?.routes?.[state.index]?.params; if (params && 'screen' in params && params.screen === SCREENS.SETTINGS.WORKSPACES) { return; } From 6b41bf7819af297e0c3b7b32ced187b119c68193 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 11 Mar 2024 12:11:17 +0500 Subject: [PATCH 026/306] feat: add comments for extraData prop --- src/components/LHNOptionsList/LHNOptionsList.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index be8ce677b641..d141a5bbb3f4 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -129,6 +129,12 @@ function LHNOptionsList({ keyExtractor={keyExtractor} renderItem={renderItem} estimatedItemSize={optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight} + // Previously, we were passing `extraData={[currentReportID]}`, which upon every render, was causing the + // re-render because of the new array reference. FlashList's children actually don't depend on the + // `currentReportID` prop but they depend on the `reportActions`, `reports`, `policy`, `personalDetails`. + // Previously it was working for us because of the new array reference. Even if you only pass an empty + // array, it will still work because of the new reference. But it's better to pass the actual dependencies + // to avoid unnecessary re-renders. extraData={extraData} showsVerticalScrollIndicator={false} /> From 64127110f19b7df5f74d3a6cb207303fd190cd4d Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 13 Mar 2024 16:44:48 +0500 Subject: [PATCH 027/306] fix: reassure tests --- tests/perf-test/SidebarUtils.perf-test.ts | 25 +++++---------------- tests/utils/collections/createCollection.ts | 25 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 8566abb97c7f..a30c298f7471 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -9,7 +9,7 @@ import type {PersonalDetails, 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 createCollection from '../utils/collections/createCollection'; +import createCollection, {createNestedCollection} from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; import createRandomReportAction, {getRandomDate} from '../utils/collections/reportActions'; @@ -51,24 +51,11 @@ const policies = createCollection( const mockedBetas = Object.values(CONST.BETAS); -const allReportActions = Object.fromEntries( - Object.keys(reportActions).map((key) => [ - `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${key}`, - [ - { - errors: reportActions[key].errors ?? [], - message: [ - { - moderationDecision: { - decision: reportActions[key].message?.[0]?.moderationDecision?.decision, - }, - }, - ], - reportActionID: reportActions[key].reportActionID, - }, - ], - ]), -) as unknown as OnyxCollection; +const allReportActions = createNestedCollection( + (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, + (item) => `${item.reportActionID}`, + (index) => createRandomReportAction(index), +); const currentReportId = '1'; const transactionViolations = {} as OnyxCollection; diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index 848ef8f81f47..ddb0b68742a4 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -9,3 +9,28 @@ export default function createCollection(createKey: (item: T, index: number) return map; } + +function createNestedCollection( + createParentKey: (item: T, index: number) => string | number, + createKey: (item: T, index: number) => string | number, + createItem: (index: number) => T, + length = 500, +): Record> { + const map: Record> = {}; + + for (let i = 0; i < length; i++) { + const item = createItem(i); + const itemKey = createKey(item, i); + const itemParentKey = createParentKey(item, i); + map[itemParentKey] = { + ...map[itemParentKey], + [itemKey]: item, + }; + } + + return map; +} + +export { + createNestedCollection, +}; \ No newline at end of file From e3477b2e751fe03702029de13aa8cee9f0ef68c6 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 13 Mar 2024 16:51:56 +0500 Subject: [PATCH 028/306] fix: apply prettier --- tests/utils/collections/createCollection.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index ddb0b68742a4..4a2a1fa4eb78 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -31,6 +31,4 @@ function createNestedCollection( return map; } -export { - createNestedCollection, -}; \ No newline at end of file +export {createNestedCollection}; From 30b319f5e5b6ec940e9101bf11fcb5bfc28eef77 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 14 Mar 2024 13:41:19 +0500 Subject: [PATCH 029/306] perf: check for the settings tab existence in screen params and early return --- src/components/withCurrentReportID.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index 22f68de9f57a..55b542ccacb7 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -4,7 +4,6 @@ import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {createContext, forwardRef, useCallback, useMemo, useState} from 'react'; import getComponentDisplayName from '@libs/getComponentDisplayName'; import Navigation from '@libs/Navigation/Navigation'; -import SCREENS from '@src/SCREENS'; type CurrentReportIDContextValue = { updateCurrentReportID: (state: NavigationState) => void; @@ -44,11 +43,14 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro /** * This is to make sure we don't set the undefined as reportID when - * switching between chat list and settings->workspaces tab. - * and doing so avoids an unnecessary re-render of `useReportIDs`. + * switching between chat list and settings tab. The settings tab + * includes multiple screens and we don't want to set the reportID + * to falsy value when switching between them. + * + * Doing so avoids an unnecessary re-render of `useReportIDs`. */ const params = state?.routes?.[state.index]?.params; - if (params && 'screen' in params && params.screen === SCREENS.SETTINGS.WORKSPACES) { + if (params && 'screen' in params && typeof params.screen === 'string' && params.screen.indexOf('Settings_') !== -1) { return; } setCurrentReportID(reportID); From 8dba89bbcad90b6e13b9f55732376d8fe24fee4a Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 15 Mar 2024 13:29:59 +0500 Subject: [PATCH 030/306] feat: add selector from SidebarLinksData --- src/hooks/useReportIDs.tsx | 114 +++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 11 deletions(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index 918a459e300a..f9b59be8e14c 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -3,21 +3,37 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; +import type {PolicySelector} from '@pages/home/sidebar/SidebarLinksData'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Message} from '@src/types/onyx/ReportAction'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; type OnyxProps = { - chatReports: OnyxCollection; - betas: OnyxEntry; - policies: OnyxCollection; - allReportActions: OnyxCollection; - transactionViolations: OnyxCollection; - policyMembers: OnyxCollection; - priorityMode: PriorityMode; + /** List of reports */ + chatReports: OnyxCollection; + + /** Beta features list */ + betas: OnyxEntry; + + /** The policies which the user has access to */ + policies: OnyxCollection; + + /** All report actions for all reports */ + allReportActions: OnyxCollection; + + /** All of the transaction violations */ + transactionViolations: OnyxCollection; + + /** All policy members */ + policyMembers: OnyxCollection; + + /** The chat priority mode */ + priorityMode: OnyxTypes.PriorityMode; }; type WithReportIDsContextProviderProps = OnyxProps & { @@ -63,10 +79,10 @@ function WithReportIDsContextProvider({ SidebarUtils.getOrderedReportIDs( currentReportID ?? null, chatReports, - betas ?? [], - policies, + betas, + policies as OnyxCollection, priorityMode, - allReportActions, + allReportActions as OnyxCollection, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -98,10 +114,84 @@ function WithReportIDsContextProvider({ return {children}; } +type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean}; +type ReportActionsSelector = Array>; + +/** + * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering + * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. + */ +const chatReportSelector = (report: OnyxEntry): ChatReportSelector => + (report && { + reportID: report.reportID, + participantAccountIDs: report.participantAccountIDs, + hasDraft: report.hasDraft, + isPinned: report.isPinned, + isHidden: report.isHidden, + notificationPreference: report.notificationPreference, + errorFields: { + addWorkspaceRoom: report.errorFields?.addWorkspaceRoom, + }, + lastMessageText: report.lastMessageText, + lastVisibleActionCreated: report.lastVisibleActionCreated, + iouReportID: report.iouReportID, + total: report.total, + nonReimbursableTotal: report.nonReimbursableTotal, + hasOutstandingChildRequest: report.hasOutstandingChildRequest, + isWaitingOnBankAccount: report.isWaitingOnBankAccount, + statusNum: report.statusNum, + stateNum: report.stateNum, + chatType: report.chatType, + type: report.type, + policyID: report.policyID, + visibility: report.visibility, + lastReadTime: report.lastReadTime, + // Needed for name sorting: + reportName: report.reportName, + policyName: report.policyName, + oldPolicyName: report.oldPolicyName, + // Other less obvious properites considered for sorting: + ownerAccountID: report.ownerAccountID, + currency: report.currency, + managerID: report.managerID, + // Other important less obivous properties for filtering: + parentReportActionID: report.parentReportActionID, + parentReportID: report.parentReportID, + isDeletedParentAction: report.isDeletedParentAction, + isUnreadWithMention: ReportUtils.isUnreadWithMention(report), + }) as ChatReportSelector; + +const reportActionsSelector = (reportActions: OnyxEntry): ReportActionsSelector => + (reportActions && + Object.values(reportActions).map((reportAction) => { + const {reportActionID, actionName, errors = [], originalMessage} = reportAction; + const decision = reportAction.message?.[0].moderationDecision?.decision; + + return { + reportActionID, + actionName, + errors, + message: [ + { + moderationDecision: {decision}, + }, + ] as Message[], + originalMessage, + }; + })) as ReportActionsSelector; + +const policySelector = (policy: OnyxEntry): PolicySelector => + (policy && { + type: policy.type, + name: policy.name, + avatar: policy.avatar, + }) as PolicySelector; + const ReportIDsContextProvider = withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, initialValue: {}, + selector: chatReportSelector, }, priorityMode: { key: ONYXKEYS.NVP_PRIORITY_MODE, @@ -114,10 +204,12 @@ const ReportIDsContextProvider = withOnyx Date: Fri, 15 Mar 2024 18:40:10 +0500 Subject: [PATCH 031/306] fix: reassure tests --- tests/perf-test/SidebarUtils.perf-test.ts | 26 +++++---------------- tests/utils/collections/createCollection.ts | 23 ++++++++++++++++++ 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 8566abb97c7f..4bd04e823f63 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -9,7 +9,7 @@ import type {PersonalDetails, 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 createCollection from '../utils/collections/createCollection'; +import createCollection, {createNestedCollection} from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; import createRandomReportAction, {getRandomDate} from '../utils/collections/reportActions'; @@ -51,25 +51,11 @@ const policies = createCollection( const mockedBetas = Object.values(CONST.BETAS); -const allReportActions = Object.fromEntries( - Object.keys(reportActions).map((key) => [ - `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${key}`, - [ - { - errors: reportActions[key].errors ?? [], - message: [ - { - moderationDecision: { - decision: reportActions[key].message?.[0]?.moderationDecision?.decision, - }, - }, - ], - reportActionID: reportActions[key].reportActionID, - }, - ], - ]), -) as unknown as OnyxCollection; - +const allReportActions = createNestedCollection( + (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, + (item) => `${item.reportActionID}`, + (index) => createRandomReportAction(index), +); const currentReportId = '1'; const transactionViolations = {} as OnyxCollection; diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index 848ef8f81f47..4a2a1fa4eb78 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -9,3 +9,26 @@ export default function createCollection(createKey: (item: T, index: number) return map; } + +function createNestedCollection( + createParentKey: (item: T, index: number) => string | number, + createKey: (item: T, index: number) => string | number, + createItem: (index: number) => T, + length = 500, +): Record> { + const map: Record> = {}; + + for (let i = 0; i < length; i++) { + const item = createItem(i); + const itemKey = createKey(item, i); + const itemParentKey = createParentKey(item, i); + map[itemParentKey] = { + ...map[itemParentKey], + [itemKey]: item, + }; + } + + return map; +} + +export {createNestedCollection}; From 3557027d7ccadd1dfa89cb3d2042e1c5ae927523 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 15 Mar 2024 18:40:36 +0500 Subject: [PATCH 032/306] fix: resolve merge conflicts --- src/hooks/useReportIDs.tsx | 114 ++++--------------------------------- 1 file changed, 11 insertions(+), 103 deletions(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index f9b59be8e14c..422febfca140 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -3,37 +3,21 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; -import type {PolicySelector} from '@pages/home/sidebar/SidebarLinksData'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type * as OnyxTypes from '@src/types/onyx'; -import type {Message} from '@src/types/onyx/ReportAction'; +import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportActions, TransactionViolation} from '@src/types/onyx'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; type OnyxProps = { - /** List of reports */ - chatReports: OnyxCollection; - - /** Beta features list */ - betas: OnyxEntry; - - /** The policies which the user has access to */ - policies: OnyxCollection; - - /** All report actions for all reports */ - allReportActions: OnyxCollection; - - /** All of the transaction violations */ - transactionViolations: OnyxCollection; - - /** All policy members */ - policyMembers: OnyxCollection; - - /** The chat priority mode */ - priorityMode: OnyxTypes.PriorityMode; + chatReports: OnyxCollection; + betas: OnyxEntry; + policies: OnyxCollection; + allReportActions: OnyxCollection; + transactionViolations: OnyxCollection; + policyMembers: OnyxCollection; + priorityMode: OnyxEntry; }; type WithReportIDsContextProviderProps = OnyxProps & { @@ -79,10 +63,10 @@ function WithReportIDsContextProvider({ SidebarUtils.getOrderedReportIDs( currentReportID ?? null, chatReports, - betas, - policies as OnyxCollection, + betas ?? [], + policies, priorityMode, - allReportActions as OnyxCollection, + allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -114,84 +98,10 @@ function WithReportIDsContextProvider({ return {children}; } -type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean}; -type ReportActionsSelector = Array>; - -/** - * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering - * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. - */ -const chatReportSelector = (report: OnyxEntry): ChatReportSelector => - (report && { - reportID: report.reportID, - participantAccountIDs: report.participantAccountIDs, - hasDraft: report.hasDraft, - isPinned: report.isPinned, - isHidden: report.isHidden, - notificationPreference: report.notificationPreference, - errorFields: { - addWorkspaceRoom: report.errorFields?.addWorkspaceRoom, - }, - lastMessageText: report.lastMessageText, - lastVisibleActionCreated: report.lastVisibleActionCreated, - iouReportID: report.iouReportID, - total: report.total, - nonReimbursableTotal: report.nonReimbursableTotal, - hasOutstandingChildRequest: report.hasOutstandingChildRequest, - isWaitingOnBankAccount: report.isWaitingOnBankAccount, - statusNum: report.statusNum, - stateNum: report.stateNum, - chatType: report.chatType, - type: report.type, - policyID: report.policyID, - visibility: report.visibility, - lastReadTime: report.lastReadTime, - // Needed for name sorting: - reportName: report.reportName, - policyName: report.policyName, - oldPolicyName: report.oldPolicyName, - // Other less obvious properites considered for sorting: - ownerAccountID: report.ownerAccountID, - currency: report.currency, - managerID: report.managerID, - // Other important less obivous properties for filtering: - parentReportActionID: report.parentReportActionID, - parentReportID: report.parentReportID, - isDeletedParentAction: report.isDeletedParentAction, - isUnreadWithMention: ReportUtils.isUnreadWithMention(report), - }) as ChatReportSelector; - -const reportActionsSelector = (reportActions: OnyxEntry): ReportActionsSelector => - (reportActions && - Object.values(reportActions).map((reportAction) => { - const {reportActionID, actionName, errors = [], originalMessage} = reportAction; - const decision = reportAction.message?.[0].moderationDecision?.decision; - - return { - reportActionID, - actionName, - errors, - message: [ - { - moderationDecision: {decision}, - }, - ] as Message[], - originalMessage, - }; - })) as ReportActionsSelector; - -const policySelector = (policy: OnyxEntry): PolicySelector => - (policy && { - type: policy.type, - name: policy.name, - avatar: policy.avatar, - }) as PolicySelector; - const ReportIDsContextProvider = withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, initialValue: {}, - selector: chatReportSelector, }, priorityMode: { key: ONYXKEYS.NVP_PRIORITY_MODE, @@ -204,12 +114,10 @@ const ReportIDsContextProvider = withOnyx Date: Thu, 21 Mar 2024 15:13:38 +0500 Subject: [PATCH 033/306] refactor: use currentReportID from useReportIDs --- src/hooks/useReportIDs.tsx | 5 ++++- src/pages/home/sidebar/SidebarLinksData.js | 6 ++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index 422febfca140..bdb9bbf9ada3 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -27,10 +27,12 @@ type WithReportIDsContextProviderProps = OnyxProps & { type ReportIDsContextValue = { orderedReportIDs: string[]; + currentReportID: string; }; const ReportIDsContext = createContext({ orderedReportIDs: [], + currentReportID: '', }); function WithReportIDsContextProvider({ @@ -91,8 +93,9 @@ function WithReportIDsContextProvider({ const contextValue = useMemo( () => ({ orderedReportIDs: orderedReportIDsWithCurrentReport, + currentReportID: derivedCurrentReportID ?? '', }), - [orderedReportIDsWithCurrentReport], + [orderedReportIDsWithCurrentReport, derivedCurrentReportID], ); return {children}; diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 35c4a2c59cd2..1f4a8cf343d4 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -5,7 +5,6 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import networkPropTypes from '@components/networkPropTypes'; import {withNetwork} from '@components/OnyxProvider'; -import withCurrentReportID from '@components/withCurrentReportID'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; import withNavigationFocus from '@components/withNavigationFocus'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; @@ -44,7 +43,7 @@ const defaultProps = { ...withCurrentUserPersonalDetailsDefaultProps, }; -function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, currentUserPersonalDetails}) { +function SidebarLinksData({isFocused, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, currentUserPersonalDetails}) { const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); @@ -57,7 +56,7 @@ function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onL const orderedReportIDsRef = useRef(null); const isLoading = isLoadingApp; - const {orderedReportIDs} = useReportIDs(); + const {orderedReportIDs, currentReportID} = useReportIDs(); const optionListItems = useMemo(() => { if (deepEqual(orderedReportIDsRef.current, orderedReportIDs)) { @@ -103,7 +102,6 @@ SidebarLinksData.defaultProps = defaultProps; SidebarLinksData.displayName = 'SidebarLinksData'; export default compose( - withCurrentReportID, withCurrentUserPersonalDetails, withNavigationFocus, withNetwork(), From 84aa5ac37af2faf3eeb547b600627ef9c40fa920 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 21 Mar 2024 16:10:24 +0500 Subject: [PATCH 034/306] refactor: remove unnecessary ref --- src/pages/home/sidebar/SidebarLinksData.js | 29 +++------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 1f4a8cf343d4..207eed55586a 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -1,15 +1,11 @@ -import {deepEqual} from 'fast-equals'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import networkPropTypes from '@components/networkPropTypes'; -import {withNetwork} from '@components/OnyxProvider'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; import withNavigationFocus from '@components/withNavigationFocus'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; -import usePrevious from '@hooks/usePrevious'; import {useReportIDs} from '@hooks/useReportIDs'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; @@ -28,8 +24,6 @@ const propTypes = { /** The chat priority mode */ priorityMode: PropTypes.string, - network: networkPropTypes.isRequired, - // eslint-disable-next-line react/forbid-prop-types policyMembers: PropTypes.object, @@ -43,35 +37,19 @@ const defaultProps = { ...withCurrentUserPersonalDetailsDefaultProps, }; -function SidebarLinksData({isFocused, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, currentUserPersonalDetails}) { +function SidebarLinksData({isFocused, insets, isLoadingApp, onLinkClick, priorityMode, policyMembers, currentUserPersonalDetails}) { const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); - const prevPriorityMode = usePrevious(priorityMode); const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, currentUserPersonalDetails.accountID); // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => Policy.openWorkspace(activeWorkspaceID, policyMemberAccountIDs), [activeWorkspaceID]); - const orderedReportIDsRef = useRef(null); const isLoading = isLoadingApp; const {orderedReportIDs, currentReportID} = useReportIDs(); - const optionListItems = useMemo(() => { - if (deepEqual(orderedReportIDsRef.current, orderedReportIDs)) { - return orderedReportIDsRef.current; - } - - // 1. We need to update existing reports only once while loading because they are updated several times during loading and causes this regression: https://github.com/Expensify/App/issues/24596#issuecomment-1681679531 - // 2. If the user is offline, we need to update the reports unconditionally, since the loading of report data might be stuck in this case. - // 3. Changing priority mode to Most Recent will call OpenApp. If there is an existing reports and the priority mode is updated, we want to immediately update the list instead of waiting the OpenApp request to complete - if (!isLoading || !orderedReportIDsRef.current || network.isOffline || (orderedReportIDsRef.current && prevPriorityMode !== priorityMode)) { - orderedReportIDsRef.current = orderedReportIDs; - } - return orderedReportIDsRef.current || []; - }, [orderedReportIDs, isLoading, network.isOffline, prevPriorityMode, priorityMode]); - const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; const isActiveReport = useCallback((reportID) => currentReportIDRef.current === reportID, []); @@ -91,7 +69,7 @@ function SidebarLinksData({isFocused, insets, isLoadingApp, onLinkClick, priorit isActiveReport={isActiveReport} isLoading={isLoading} activeWorkspaceID={activeWorkspaceID} - optionListItems={optionListItems} + optionListItems={orderedReportIDs} /> ); @@ -104,7 +82,6 @@ SidebarLinksData.displayName = 'SidebarLinksData'; export default compose( withCurrentUserPersonalDetails, withNavigationFocus, - withNetwork(), withOnyx({ isLoadingApp: { key: ONYXKEYS.IS_LOADING_APP, From 8532c0864d9b6a2ffb88ae3759801781d6bf23af Mon Sep 17 00:00:00 2001 From: Muhammad Hur Ali Date: Thu, 21 Mar 2024 16:16:02 +0500 Subject: [PATCH 035/306] update comment is updateCurrentReportID function Co-authored-by: Rory Abraham <47436092+roryabraham@users.noreply.github.com> --- src/components/withCurrentReportID.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index 55b542ccacb7..27a965a151de 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -42,7 +42,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro const reportID = Navigation.getTopmostReportId(state) ?? ''; /** - * This is to make sure we don't set the undefined as reportID when + * This is to make sure we don't set the reportID as undefined when * switching between chat list and settings tab. The settings tab * includes multiple screens and we don't want to set the reportID * to falsy value when switching between them. From ac465f3976aef5d891f7ba4e7eb4bc2043ca13a3 Mon Sep 17 00:00:00 2001 From: Muhammad Hur Ali Date: Thu, 21 Mar 2024 16:16:46 +0500 Subject: [PATCH 036/306] update comment in useReportIDs hook Co-authored-by: Rory Abraham <47436092+roryabraham@users.noreply.github.com> --- src/hooks/useReportIDs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index bdb9bbf9ada3..4d7474905a2e 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -47,7 +47,7 @@ function WithReportIDsContextProvider({ /** * Only required to make unit tests work, since we * explicitly pass the currentReportID in LHNTestUtils - * to SidebarLinksData, so this context doesn't have an + * to SidebarLinksData, so this context doesn't have * access to currentReportID in that case. * * This is a workaround to have currentReportID available in testing environment. From 886622e7940a7b28e11e299d0aa89a6e34030cc6 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 21 Mar 2024 16:20:12 +0500 Subject: [PATCH 037/306] refactor: make comment less verbose --- src/components/withCurrentReportID.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index 27a965a151de..a72063913283 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -41,13 +41,9 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro (state: NavigationState) => { const reportID = Navigation.getTopmostReportId(state) ?? ''; - /** - * This is to make sure we don't set the reportID as undefined when - * switching between chat list and settings tab. The settings tab - * includes multiple screens and we don't want to set the reportID - * to falsy value when switching between them. - * - * Doing so avoids an unnecessary re-render of `useReportIDs`. + /* + * Make sure we don't make the reportID undefined when switching between the chat list and settings tab. + * This helps prevent unnecessary re-renders. */ const params = state?.routes?.[state.index]?.params; if (params && 'screen' in params && typeof params.screen === 'string' && params.screen.indexOf('Settings_') !== -1) { From 6d17116c0e2af4fbadbe79b366fb16c7434b51be Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 21 Mar 2024 16:20:46 +0500 Subject: [PATCH 038/306] refactor: remove irrelevant comment --- src/components/LHNOptionsList/LHNOptionsList.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index c9052567ac36..fa4c89216d08 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -122,12 +122,6 @@ function LHNOptionsList({ keyExtractor={keyExtractor} renderItem={renderItem} estimatedItemSize={optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight} - // Previously, we were passing `extraData={[currentReportID]}`, which upon every render, was causing the - // re-render because of the new array reference. FlashList's children actually don't depend on the - // `currentReportID` prop but they depend on the `reportActions`, `reports`, `policy`, `personalDetails`. - // Previously it was working for us because of the new array reference. Even if you only pass an empty - // array, it will still work because of the new reference. But it's better to pass the actual dependencies - // to avoid unnecessary re-renders. extraData={extraData} showsVerticalScrollIndicator={false} /> From 2ef5c7f58a2400bbca01dd007b3bf19b4346aa53 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 22 Mar 2024 13:10:46 +0500 Subject: [PATCH 039/306] refactor: reduce memo usage --- src/hooks/useReportIDs.tsx | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index 4d7474905a2e..4af75c3e4e5b 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -77,26 +77,21 @@ function WithReportIDsContextProvider({ ); const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); - - // 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 - // we first generate the list as if there was no current report, then we check if - // the current report is missing from the list, which should very rarely happen. In this - // case we re-generate the list a 2nd time with the current report included. - const orderedReportIDsWithCurrentReport = useMemo(() => { + const contextValue: ReportIDsContextValue = useMemo(() => { + // 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 + // we first generate the list as if there was no current report, then we check if + // the current report is missing from the list, which should very rarely happen. In this + // case we re-generate the list a 2nd time with the current report included. if (derivedCurrentReportID && !orderedReportIDs.includes(derivedCurrentReportID)) { - return getOrderedReportIDs(derivedCurrentReportID); + return {orderedReportIDs: getOrderedReportIDs(derivedCurrentReportID), currentReportID: derivedCurrentReportID ?? ''}; } - return orderedReportIDs; - }, [getOrderedReportIDs, derivedCurrentReportID, orderedReportIDs]); - const contextValue = useMemo( - () => ({ - orderedReportIDs: orderedReportIDsWithCurrentReport, + return { + orderedReportIDs: getOrderedReportIDs(), currentReportID: derivedCurrentReportID ?? '', - }), - [orderedReportIDsWithCurrentReport, derivedCurrentReportID], - ); + }; + }, [getOrderedReportIDs, orderedReportIDs, derivedCurrentReportID]); return {children}; } From c707b50f47acd879a0b30cae9532a649c0d10303 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 5 Apr 2024 00:56:34 +0800 Subject: [PATCH 040/306] clear sub step when open bank account page --- .../ReimbursementAccount/ReimbursementAccountPage.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 0f5d04919e29..bd3290a2be89 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -263,7 +263,7 @@ function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, pol * Retrieve verified business bank account currently being set up. * @param {boolean} ignoreLocalCurrentStep Pass true if you want the last "updated" view (from db), not the last "viewed" view (from onyx). */ - function fetchData(ignoreLocalCurrentStep) { + function fetchData(ignoreLocalCurrentStep, ignoreLocalSubStep) { // Show loader right away, as optimisticData might be set only later in case multiple calls are in the queue BankAccounts.setReimbursementAccountLoading(true); @@ -272,12 +272,17 @@ function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, pol const stepToOpen = getStepToOpenFromRouteParams(route); const subStep = achData.subStep || ''; const localCurrentStep = achData.currentStep || ''; - BankAccounts.openReimbursementAccountPage(stepToOpen, subStep, ignoreLocalCurrentStep ? '' : localCurrentStep, policyID); + BankAccounts.openReimbursementAccountPage(stepToOpen, ignoreLocalSubStep ? '' : subStep, ignoreLocalCurrentStep ? '' : localCurrentStep, policyID); } useEffect( () => { - fetchData(); + // If the step to open is empty, we want to clear the sub step, so the connect option view is shown to the user + const isStepToOpenEmpty = getStepToOpenFromRouteParams(route) === ''; + if (isStepToOpenEmpty) { + BankAccounts.setBankAccountSubStep(null); + } + fetchData(false, isStepToOpenEmpty); return () => { BankAccounts.clearReimbursementAccount(); }; From a8032031441bc5d6cff613d50da82e55828cd1ef Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 5 Apr 2024 11:15:04 +0800 Subject: [PATCH 041/306] add param to jsdoc --- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index bd3290a2be89..3dbd7681b0c4 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -262,6 +262,7 @@ function ReimbursementAccountPage({reimbursementAccount, route, onfidoToken, pol /** * Retrieve verified business bank account currently being set up. * @param {boolean} ignoreLocalCurrentStep Pass true if you want the last "updated" view (from db), not the last "viewed" view (from onyx). + * @param {boolean} ignoreLocalSubStep Pass true if you want the last "updated" view (from db), not the last "viewed" view (from onyx). */ function fetchData(ignoreLocalCurrentStep, ignoreLocalSubStep) { // Show loader right away, as optimisticData might be set only later in case multiple calls are in the queue From 7f4f51245d96b0184b717aa36883e21d23835397 Mon Sep 17 00:00:00 2001 From: bartektomczyk Date: Thu, 21 Mar 2024 12:00:57 +0100 Subject: [PATCH 042/306] feat: added component for displaying static message inm onboarding --- src/languages/en.ts | 10 ++ src/languages/es.ts | 10 ++ .../report/OnboardingReportFooterMessage.tsx | 92 +++++++++++++++++++ src/styles/index.ts | 4 + 4 files changed, 116 insertions(+) create mode 100644 src/pages/home/report/OnboardingReportFooterMessage.tsx diff --git a/src/languages/en.ts b/src/languages/en.ts index 3b670f7b6ebc..138b28260060 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2665,6 +2665,16 @@ export default { welcomeMessage: 'Welcome to Expensify', welcomeSubtitle: 'What would you like to do?', }, + onboardingBottomMessage: { + [CONST.INTRO_CHOICES.MANAGE_TEAM]: { + phrase1: 'Chat with your setup specialist in ', + phrase2: ' for help', + }, + default: { + phrase1: 'Message ', + phrase2: ' for help with setup', + }, + }, manageTeams: { [CONST.MANAGE_TEAMS_CHOICE.MULTI_LEVEL]: 'Multi level approval', [CONST.MANAGE_TEAMS_CHOICE.CUSTOM_EXPENSE]: 'Custom expense coding', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5027174b2922..0799aefac37d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3158,6 +3158,16 @@ export default { welcomeMessage: 'Bienvenido a Expensify', welcomeSubtitle: '¿Qué te gustaría hacer?', }, + onboardingBottomMessage: { + [CONST.INTRO_CHOICES.MANAGE_TEAM]: { + phrase1: 'Chat with your setup specialist in ', + phrase2: ' for help', + }, + default: { + phrase1: 'Message ', + phrase2: ' for help with setup', + }, + }, manageTeams: { [CONST.MANAGE_TEAMS_CHOICE.MULTI_LEVEL]: 'Aprobación multinivel', [CONST.MANAGE_TEAMS_CHOICE.CUSTOM_EXPENSE]: 'Codificación personalizada de gastos', diff --git a/src/pages/home/report/OnboardingReportFooterMessage.tsx b/src/pages/home/report/OnboardingReportFooterMessage.tsx new file mode 100644 index 000000000000..a48a609b51a7 --- /dev/null +++ b/src/pages/home/report/OnboardingReportFooterMessage.tsx @@ -0,0 +1,92 @@ +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import Navigation from '@navigation/Navigation'; +import * as ReportInstance from '@userActions/Report'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {Policy as PolicyType, Report} from '@src/types/onyx'; + +type OnboardingReportFooterMessageOnyxProps = {reports: OnyxCollection; policies: OnyxCollection}; +type OnboardingReportFooterMessageProps = OnboardingReportFooterMessageOnyxProps & {choice: ValueOf}; + +function OnboardingReportFooterMessage({choice, reports, policies}: OnboardingReportFooterMessageProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {isSmallScreenWidth} = useWindowDimensions(); + + const adminChatReport = useMemo(() => { + const adminsReports = reports ? Object.values(reports).filter((report) => report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ADMINS) : []; + const activePolicies = policies ? Object.values(policies).filter((policy) => PolicyUtils.shouldShowPolicy(policy, false)) : []; + + return adminsReports.find((report) => activePolicies.find((policy) => policy?.id === report?.policyID)); + }, [policies, reports]); + + const content = useMemo(() => { + switch (choice) { + case CONST.INTRO_CHOICES.MANAGE_TEAM: + return ( + <> + {`${translate('onboardingBottomMessage.newDotManageTeam.phrase1')}`} + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(adminChatReport?.reportID ?? ''))} + > + {adminChatReport?.reportName ?? CONST.REPORT.WORKSPACE_CHAT_ROOMS.ADMINS} + + {`${translate('onboardingBottomMessage.newDotManageTeam.phrase2')}`} + + ); + default: + return ( + <> + {`${translate('onboardingBottomMessage.default.phrase1')}`} + ReportInstance.navigateToConciergeChat()} + > + {`${CONST?.CONCIERGE_CHAT_NAME}`} + + {`${translate('onboardingBottomMessage.default.phrase2')}`} + + ); + } + }, [adminChatReport?.reportName, adminChatReport?.reportID, choice, styles.label, translate]); + + return ( + + {content} + + ); +} +OnboardingReportFooterMessage.displayName = 'OnboardingReportFooterMessage'; +export default withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, +})(OnboardingReportFooterMessage); diff --git a/src/styles/index.ts b/src/styles/index.ts index f165974119ff..27178157f9f5 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -525,6 +525,10 @@ const styles = (theme: ThemeColors) => borderRadius: variables.buttonBorderRadius, }, + borderRadiusComponentLarge: { + borderRadius: variables.componentBorderRadiusLarge, + }, + bottomTabBarContainer: { flexDirection: 'row', height: variables.bottomTabHeight, From f662aa3bf58ba151e19b3c17dc8ea9a314e32638 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 10 Apr 2024 15:10:27 +0700 Subject: [PATCH 043/306] fix: Missing 'Leave Thread' Button Offline in Self DM Thread --- src/libs/actions/Report.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index d2f85362baf8..8aa2bc2cbce1 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -851,7 +851,7 @@ function navigateToAndOpenChildReport(childReportID = '0', parentReportAction: P const newChat = ReportUtils.buildOptimisticChatReport( participantAccountIDs, parentReportAction?.message?.[0]?.text, - parentReport?.chatType, + parentReport?.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM ? undefined : parentReport?.chatType, parentReport?.policyID ?? CONST.POLICY.OWNER_EMAIL_FAKE, CONST.POLICY.OWNER_ACCOUNT_ID_FAKE, false, From cf387738a40d1851a55f5729d35cf3f37047eab3 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 12 Apr 2024 09:35:45 +0700 Subject: [PATCH 044/306] fix use isSelfDM --- src/libs/actions/Report.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 8aa2bc2cbce1..a2102b2a8caa 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -851,7 +851,7 @@ function navigateToAndOpenChildReport(childReportID = '0', parentReportAction: P const newChat = ReportUtils.buildOptimisticChatReport( participantAccountIDs, parentReportAction?.message?.[0]?.text, - parentReport?.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM ? undefined : parentReport?.chatType, + ReportUtils.isSelfDM(parentReport) ? undefined : parentReport?.chatType, parentReport?.policyID ?? CONST.POLICY.OWNER_EMAIL_FAKE, CONST.POLICY.OWNER_ACCOUNT_ID_FAKE, false, From 7cef847d8384431a406f8dd69f38b67c2759b589 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 12 Apr 2024 09:38:29 +0700 Subject: [PATCH 045/306] fix lint --- src/libs/actions/Report.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index a2102b2a8caa..c8a7cce42bfe 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -851,7 +851,7 @@ function navigateToAndOpenChildReport(childReportID = '0', parentReportAction: P const newChat = ReportUtils.buildOptimisticChatReport( participantAccountIDs, parentReportAction?.message?.[0]?.text, - ReportUtils.isSelfDM(parentReport) ? undefined : parentReport?.chatType, + parentReport && ReportUtils.isSelfDM(parentReport) ? undefined : parentReport?.chatType, parentReport?.policyID ?? CONST.POLICY.OWNER_EMAIL_FAKE, CONST.POLICY.OWNER_ACCOUNT_ID_FAKE, false, From fc35658735bcd30c4a5af186008640ef084c90d5 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Fri, 12 Apr 2024 13:16:20 -0400 Subject: [PATCH 046/306] Improve getSearchText perf --- src/libs/OptionsListUtils.ts | 37 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 280ba825761f..91b542e7539e 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -432,12 +432,10 @@ function uniqFast(items: string[]): string[] { /** * Returns a string with all relevant search terms. - * Default should be serachable by policy/domain name but not by participants. * * This method must be incredibly performant. It was found to be a big performance bottleneck * when dealing with accounts that have thousands of reports. For loops are more efficient than _.each - * Array.prototype.push.apply is faster than using the spread operator, and concat() is faster than push(). - + * Array.prototype.push.apply is faster than using the spread operator. */ function getSearchText( report: OnyxEntry, @@ -446,22 +444,17 @@ function getSearchText( isChatRoomOrPolicyExpenseChat: boolean, isThread: boolean, ): string { - let searchTerms: string[] = []; - - if (!isChatRoomOrPolicyExpenseChat) { - for (const personalDetail of personalDetailList) { - if (personalDetail.login) { - // The regex below is used to remove dots only from the local part of the user email (local-part@domain) - // so that we can match emails that have dots without explicitly writing the dots (e.g: fistlast@domain will match first.last@domain) - // More info https://github.com/Expensify/App/issues/8007 - searchTerms = searchTerms.concat([ - PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, '', false), - personalDetail.login, - personalDetail.login.replace(/\.(?=[^\s@]*@)/g, ''), - ]); - } + const searchTerms: string[] = []; + + for (const personalDetail of personalDetailList) { + if (personalDetail.login) { + // The regex below is used to remove dots only from the local part of the user email (local-part@domain) + // so that we can match emails that have dots without explicitly writing the dots (e.g: fistlast@domain will match first.last@domain) + // More info https://github.com/Expensify/App/issues/8007 + searchTerms.push(PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, '', false), personalDetail.login, personalDetail.login.replace(/\.(?=[^\s@]*@)/g, '')); } } + if (report) { Array.prototype.push.apply(searchTerms, reportName.split(/[,\s]/)); @@ -475,16 +468,6 @@ function getSearchText( const chatRoomSubtitle = ReportUtils.getChatRoomSubtitle(report); Array.prototype.push.apply(searchTerms, chatRoomSubtitle?.split(/[,\s]/) ?? ['']); - } else { - const visibleChatMemberAccountIDs = report.visibleChatMemberAccountIDs ?? []; - if (allPersonalDetails) { - for (const accountID of visibleChatMemberAccountIDs) { - const login = allPersonalDetails[accountID]?.login; - if (login) { - searchTerms = searchTerms.concat(login); - } - } - } } } From b8d10804913a287f271ee4fd32dee227d26ca74e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 16 Apr 2024 14:40:02 +0800 Subject: [PATCH 047/306] use participants data --- src/libs/OptionsListUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index aa16d7b2dc5a..310ac4edd5a7 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1459,8 +1459,8 @@ function createOptionList(personalDetails: OnyxEntry, repor } const isSelfDM = ReportUtils.isSelfDM(report); - // Currently, currentUser is not included in visibleChatMemberAccountIDs, so for selfDM we need to add the currentUser as participants. - const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : report.visibleChatMemberAccountIDs ?? []; + // Currently, currentUser is not included in participants, so for selfDM we need to add the currentUser as participants. + const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : ReportUtils.getParticipantAccountIDs(report.reportID); if (!accountIDs || accountIDs.length === 0) { return; @@ -1679,8 +1679,8 @@ function getOptions( const isPolicyExpenseChat = option.isPolicyExpenseChat; const isMoneyRequestReport = option.isMoneyRequestReport; const isSelfDM = option.isSelfDM; - // Currently, currentUser is not included in visibleChatMemberAccountIDs, so for selfDM we need to add the currentUser as participants. - const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : report.visibleChatMemberAccountIDs ?? []; + // Currently, currentUser is not included in participants, so for selfDM we need to add the currentUser as participants. + const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : ReportUtils.getParticipantAccountIDs(report.reportID); if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) { return; From 809b5a3291269615bc53e984617c45e0a828e2df Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 16 Apr 2024 14:50:07 +0800 Subject: [PATCH 048/306] optimistically update notification preference to always --- src/libs/actions/IOU.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 85f4b74f3436..068d666eaabe 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2575,6 +2575,11 @@ function createSplitsAndOnyxData( splitChatReport.lastMessageText = splitIOUReportAction.message?.[0]?.text; splitChatReport.lastMessageHtml = splitIOUReportAction.message?.[0]?.html; + let splitChatReportNotificationPreference = splitChatReport.notificationPreference; + if (splitChatReportNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { + splitChatReportNotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; + } + // If we have an existing splitChatReport (group chat or workspace) use it's pending fields, otherwise indicate that we are adding a chat if (!existingSplitChatReport) { splitChatReport.pendingFields = { @@ -2588,7 +2593,10 @@ function createSplitsAndOnyxData( // and we need the data to be available when we navigate to the chat page onyxMethod: existingSplitChatReport ? Onyx.METHOD.MERGE : Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${splitChatReport.reportID}`, - value: splitChatReport, + value: { + ...splitChatReport, + notificationPreference: splitChatReportNotificationPreference, + }, }, { onyxMethod: Onyx.METHOD.SET, From 0c3e03ad63067f3e780b412e2bad06d89c821cae Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 16 Apr 2024 15:27:50 +0800 Subject: [PATCH 049/306] fix test --- src/libs/OptionsListUtils.ts | 4 +-- tests/unit/OptionsListUtilsTest.ts | 51 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 310ac4edd5a7..6baf0cd30529 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1460,7 +1460,7 @@ function createOptionList(personalDetails: OnyxEntry, repor const isSelfDM = ReportUtils.isSelfDM(report); // Currently, currentUser is not included in participants, so for selfDM we need to add the currentUser as participants. - const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : ReportUtils.getParticipantAccountIDs(report.reportID); + const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : Object.keys(report.participants ?? {}).map(Number); if (!accountIDs || accountIDs.length === 0) { return; @@ -1680,7 +1680,7 @@ function getOptions( const isMoneyRequestReport = option.isMoneyRequestReport; const isSelfDM = option.isSelfDM; // Currently, currentUser is not included in participants, so for selfDM we need to add the currentUser as participants. - const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : ReportUtils.getParticipantAccountIDs(report.reportID); + const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : Object.keys(report.participants ?? {}).map(Number); if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) { return; diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index af5782b1ca32..92e5379bfe31 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -21,6 +21,10 @@ describe('OptionsListUtils', () => { reportID: '1', participantAccountIDs: [2, 1], visibleChatMemberAccountIDs: [2, 1], + participants: { + 2: {}, + 1: {}, + }, reportName: 'Iron Man, Mister Fantastic', type: CONST.REPORT.TYPE.CHAT, }, @@ -31,6 +35,9 @@ describe('OptionsListUtils', () => { reportID: '2', participantAccountIDs: [3], visibleChatMemberAccountIDs: [3], + participants: { + 3: {}, + }, reportName: 'Spider-Man', type: CONST.REPORT.TYPE.CHAT, }, @@ -43,6 +50,9 @@ describe('OptionsListUtils', () => { reportID: '3', participantAccountIDs: [1], visibleChatMemberAccountIDs: [1], + participants: { + 1: {}, + }, reportName: 'Mister Fantastic', type: CONST.REPORT.TYPE.CHAT, }, @@ -53,6 +63,9 @@ describe('OptionsListUtils', () => { reportID: '4', participantAccountIDs: [4], visibleChatMemberAccountIDs: [4], + participants: { + 4: {}, + }, reportName: 'Black Panther', type: CONST.REPORT.TYPE.CHAT, }, @@ -63,6 +76,9 @@ describe('OptionsListUtils', () => { reportID: '5', participantAccountIDs: [5], visibleChatMemberAccountIDs: [5], + participants: { + 5: {}, + }, reportName: 'Invisible Woman', type: CONST.REPORT.TYPE.CHAT, }, @@ -73,6 +89,9 @@ describe('OptionsListUtils', () => { reportID: '6', participantAccountIDs: [6], visibleChatMemberAccountIDs: [6], + participants: { + 6: {}, + }, reportName: 'Thor', type: CONST.REPORT.TYPE.CHAT, }, @@ -85,6 +104,9 @@ describe('OptionsListUtils', () => { reportID: '7', participantAccountIDs: [7], visibleChatMemberAccountIDs: [7], + participants: { + 7: {}, + }, reportName: 'Captain America', type: CONST.REPORT.TYPE.CHAT, }, @@ -97,6 +119,9 @@ describe('OptionsListUtils', () => { reportID: '8', participantAccountIDs: [12], visibleChatMemberAccountIDs: [12], + participants: { + 12: {}, + }, reportName: 'Silver Surfer', type: CONST.REPORT.TYPE.CHAT, }, @@ -109,6 +134,9 @@ describe('OptionsListUtils', () => { reportID: '9', participantAccountIDs: [8], visibleChatMemberAccountIDs: [8], + participants: { + 8: {}, + }, reportName: 'Mister Sinister', iouReportID: '100', type: CONST.REPORT.TYPE.CHAT, @@ -122,6 +150,10 @@ describe('OptionsListUtils', () => { isPinned: false, participantAccountIDs: [2, 7], visibleChatMemberAccountIDs: [2, 7], + participants: { + 2: {}, + 7: {}, + }, reportName: '', oldPolicyName: "SHIELD's workspace", chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, @@ -212,6 +244,9 @@ describe('OptionsListUtils', () => { reportID: '11', participantAccountIDs: [999], visibleChatMemberAccountIDs: [999], + participants: { + 999: {}, + }, reportName: 'Concierge', type: CONST.REPORT.TYPE.CHAT, }, @@ -226,6 +261,9 @@ describe('OptionsListUtils', () => { reportID: '12', participantAccountIDs: [1000], visibleChatMemberAccountIDs: [1000], + participants: { + 1000: {}, + }, reportName: 'Chronos', type: CONST.REPORT.TYPE.CHAT, }, @@ -240,6 +278,9 @@ describe('OptionsListUtils', () => { reportID: '13', participantAccountIDs: [1001], visibleChatMemberAccountIDs: [1001], + participants: { + 1001: {}, + }, reportName: 'Receipts', type: CONST.REPORT.TYPE.CHAT, }, @@ -254,6 +295,11 @@ describe('OptionsListUtils', () => { reportID: '14', participantAccountIDs: [1, 10, 3], visibleChatMemberAccountIDs: [1, 10, 3], + participants: { + 1: {}, + 10: {}, + 3: {}, + }, reportName: '', oldPolicyName: 'Avengers Room', chatType: CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, @@ -271,6 +317,10 @@ describe('OptionsListUtils', () => { reportID: '15', participantAccountIDs: [3, 4], visibleChatMemberAccountIDs: [3, 4], + participants: { + 3: {}, + 4: {}, + }, reportName: 'Spider-Man, Black Panther', type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.DOMAIN_ALL, @@ -368,6 +418,7 @@ describe('OptionsListUtils', () => { // When we filter in the Search view without providing a searchValue let results = OptionsListUtils.getSearchOptions(OPTIONS, '', [CONST.BETAS.ALL]); // Then the 2 personalDetails that don't have reports should be returned + console.log('personal details', OPTIONS.personalDetails.length, results.personalDetails.length) expect(results.personalDetails.length).toBe(2); // Then all of the reports should be shown including the archived rooms. From c67ba1229a673ae9cb82ae80b0c004f5df0a86a3 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 16 Apr 2024 15:36:56 +0800 Subject: [PATCH 050/306] remove log --- tests/unit/OptionsListUtilsTest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index 92e5379bfe31..6629bd9e531f 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -418,7 +418,6 @@ describe('OptionsListUtils', () => { // When we filter in the Search view without providing a searchValue let results = OptionsListUtils.getSearchOptions(OPTIONS, '', [CONST.BETAS.ALL]); // Then the 2 personalDetails that don't have reports should be returned - console.log('personal details', OPTIONS.personalDetails.length, results.personalDetails.length) expect(results.personalDetails.length).toBe(2); // Then all of the reports should be shown including the archived rooms. From 9abf0f57b3aac32ceedb6a2125f636ffe51ddc8a Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 16 Apr 2024 15:00:24 +0700 Subject: [PATCH 051/306] Add error for failure invited member --- src/languages/en.ts | 3 +++ src/languages/es.ts | 3 +++ src/libs/actions/Report.ts | 34 +++++++++++++++++++++++++--------- src/pages/RoomMembersPage.tsx | 7 +++++++ src/types/onyx/Report.ts | 1 + 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 9451407c822f..574b761f1b54 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2325,6 +2325,9 @@ export default { memberNotFound: 'Member not found. To invite a new member to the room, please use the Invite button above.', notAuthorized: `You do not have access to this page. Are you trying to join the room? Please reach out to a member of this room so they can add you as a member! Something else? Reach out to ${CONST.EMAIL.CONCIERGE}`, removeMembersPrompt: 'Are you sure you want to remove the selected members from the room?', + error: { + genericAdd: 'There was a problem adding this room member.', + }, }, newTaskPage: { assignTask: 'Assign task', diff --git a/src/languages/es.ts b/src/languages/es.ts index a56c8ac2739d..b16a898524f0 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2356,6 +2356,9 @@ export default { memberNotFound: 'Miembro no encontrado. Para invitar a un nuevo miembro a la sala de chat, por favor, utiliza el botón Invitar que está más arriba.', notAuthorized: `No tienes acceso a esta página. ¿Estás tratando de unirte a la sala de chat? Comunícate con el propietario de esta sala de chat para que pueda añadirte como miembro. ¿Necesitas algo más? Comunícate con ${CONST.EMAIL.CONCIERGE}`, removeMembersPrompt: '¿Estás seguro de que quieres eliminar a los miembros seleccionados de la sala de chat?', + error: { + genericAdd: 'Hubo un problema al agregar este miembro de la sala.', + }, }, newTaskPage: { assignTask: 'Asignar tarea', diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 1e59cf21f542..cbb03c8b90f6 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2588,17 +2588,18 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { - participantAccountIDs: report.participantAccountIDs, - visibleChatMemberAccountIDs: report.visibleChatMemberAccountIDs, - participants: inviteeAccountIDs.reduce((revertedParticipants: Record, accountID) => { - // eslint-disable-next-line no-param-reassign - revertedParticipants[accountID] = null; - return revertedParticipants; - }, {}), - pendingChatMembers: report?.pendingChatMembers ?? null, + pendingChatMembers: + pendingChatMembers.map((pendingChatMember) => { + if (!inviteeAccountIDs.includes(Number(pendingChatMember.accountID))) { + return pendingChatMember; + } + return { + ...pendingChatMember, + errors: ErrorUtils.getMicroSecondOnyxError('roomMembersPage.error.genericAdd'), + }; + }) ?? null, }, }, - ...newPersonalDetailsOnyxData.finallyData, ]; if (ReportUtils.isGroupChat(report)) { @@ -2621,6 +2622,20 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails API.write(WRITE_COMMANDS.INVITE_TO_ROOM, parameters, {optimisticData, successData, failureData}); } +function clearAddRoomMemberError(reportID: string, invitedAccountID: string) { + const report = currentReportData?.[reportID]; + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { + pendingChatMembers: report?.pendingChatMembers?.filter((pendingChatMember) => pendingChatMember.accountID !== invitedAccountID), + participantAccountIDs: report?.parentReportActionIDs?.filter((parentReportActionID) => parentReportActionID !== Number(invitedAccountID)), + participants: { + [invitedAccountID]: null, + }, + }); + Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { + [invitedAccountID]: null, + }); +} + function updateGroupChatMemberRoles(reportID: string, accountIDList: number[], role: ValueOf) { const participants: Participants = {}; const memberRoles: Record = {}; @@ -3256,4 +3271,5 @@ export { leaveGroupChat, removeFromGroupChat, updateGroupChatMemberRoles, + clearAddRoomMemberError, }; diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index d025a3bde265..95534a870f23 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -216,6 +216,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { }, ], pendingAction: pendingChatMember?.pendingAction, + errors: pendingChatMember?.errors, }); }); @@ -224,6 +225,10 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { return result; }; + const dismissError = useCallback((item: ListItem) => { + Report.clearAddRoomMemberError(report.reportID, String(item.accountID ?? '')); + }, [report.reportID]); + const isPolicyMember = useMemo(() => { if (!report?.policyID || policies === null) { return false; @@ -232,6 +237,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { }, [report?.policyID, policies]); const data = getMemberOptions(); const headerMessage = searchValue.trim() && !data.length ? translate('roomMembersPage.memberNotFound') : ''; + return ( diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index fe3eec6dc11e..eb3467879d2f 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -21,6 +21,7 @@ type Note = OnyxCommon.OnyxValueWithOfflineFeedback<{ type PendingChatMember = { accountID: string; pendingAction: OnyxCommon.PendingAction; + errors?: OnyxCommon.Errors; }; type Participant = { From 9e4f4640198210dfc259da9901500df48e85852c Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 16 Apr 2024 13:47:47 +0500 Subject: [PATCH 052/306] fix: typecheck --- tests/perf-test/SidebarUtils.perf-test.ts | 26 ++++++++++++++++----- tests/utils/LHNTestUtils.tsx | 1 + tests/utils/collections/createCollection.ts | 25 +------------------- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 4bd04e823f63..8566abb97c7f 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -9,7 +9,7 @@ import type {PersonalDetails, 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 createCollection, {createNestedCollection} from '../utils/collections/createCollection'; +import createCollection from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; import createRandomReportAction, {getRandomDate} from '../utils/collections/reportActions'; @@ -51,11 +51,25 @@ const policies = createCollection( const mockedBetas = Object.values(CONST.BETAS); -const allReportActions = createNestedCollection( - (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, - (item) => `${item.reportActionID}`, - (index) => createRandomReportAction(index), -); +const allReportActions = Object.fromEntries( + Object.keys(reportActions).map((key) => [ + `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${key}`, + [ + { + errors: reportActions[key].errors ?? [], + message: [ + { + moderationDecision: { + decision: reportActions[key].message?.[0]?.moderationDecision?.decision, + }, + }, + ], + reportActionID: reportActions[key].reportActionID, + }, + ], + ]), +) as unknown as OnyxCollection; + const currentReportId = '1'; const transactionViolations = {} as OnyxCollection; diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 331453adfeb8..ab710c5dc8e0 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -297,6 +297,7 @@ function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { right: 0, bottom: 0, }} + // @ts-expect-error - we need this prop to be able to test the component but normally its provided by HOC currentReportID={currentReportID} /> diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index 4a2a1fa4eb78..927864191411 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -8,27 +8,4 @@ export default function createCollection(createKey: (item: T, index: number) } return map; -} - -function createNestedCollection( - createParentKey: (item: T, index: number) => string | number, - createKey: (item: T, index: number) => string | number, - createItem: (index: number) => T, - length = 500, -): Record> { - const map: Record> = {}; - - for (let i = 0; i < length; i++) { - const item = createItem(i); - const itemKey = createKey(item, i); - const itemParentKey = createParentKey(item, i); - map[itemParentKey] = { - ...map[itemParentKey], - [itemKey]: item, - }; - } - - return map; -} - -export {createNestedCollection}; +} \ No newline at end of file From 20d798e6eed9ded69373add54e11c0445ac9f9b4 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 16 Apr 2024 13:49:26 +0500 Subject: [PATCH 053/306] refactor: remove unnecessary diff --- tests/utils/collections/createCollection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index 927864191411..848ef8f81f47 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -8,4 +8,4 @@ export default function createCollection(createKey: (item: T, index: number) } return map; -} \ No newline at end of file +} From dd9d04542fa1cae44bcbb8eabc699bfe6bb6ed64 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 16 Apr 2024 16:41:25 +0700 Subject: [PATCH 054/306] fix lint --- src/pages/RoomMembersPage.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 95534a870f23..b623c73dd0ed 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -225,9 +225,12 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { return result; }; - const dismissError = useCallback((item: ListItem) => { - Report.clearAddRoomMemberError(report.reportID, String(item.accountID ?? '')); - }, [report.reportID]); + const dismissError = useCallback( + (item: ListItem) => { + Report.clearAddRoomMemberError(report.reportID, String(item.accountID ?? '')); + }, + [report.reportID], + ); const isPolicyMember = useMemo(() => { if (!report?.policyID || policies === null) { From c729a13a48b3c42f840c380e36832cdc2ce7d8d7 Mon Sep 17 00:00:00 2001 From: staszekscp Date: Tue, 16 Apr 2024 12:24:56 +0200 Subject: [PATCH 055/306] fix new arch on hybrid app --- patches/@onfido+react-native-sdk+10.6.0.patch | 51 ++++------ patches/react-native+0.73.4+014+fixPath.patch | 13 +++ ...t-native-vision-camera+4.0.0-beta.13.patch | 92 ++++++++++++------- src/components/InitialURLContextProvider.tsx | 12 +-- 4 files changed, 91 insertions(+), 77 deletions(-) create mode 100644 patches/react-native+0.73.4+014+fixPath.patch diff --git a/patches/@onfido+react-native-sdk+10.6.0.patch b/patches/@onfido+react-native-sdk+10.6.0.patch index 90e73ec197a1..eedde72022b4 100644 --- a/patches/@onfido+react-native-sdk+10.6.0.patch +++ b/patches/@onfido+react-native-sdk+10.6.0.patch @@ -1106,12 +1106,17 @@ index 0000000..3b65b7a +@end diff --git a/node_modules/@onfido/react-native-sdk/ios/RNOnfidoSdk.mm b/node_modules/@onfido/react-native-sdk/ios/RNOnfidoSdk.mm new file mode 100644 -index 0000000..4d21970 +index 0000000..998f79b --- /dev/null +++ b/node_modules/@onfido/react-native-sdk/ios/RNOnfidoSdk.mm -@@ -0,0 +1,59 @@ +@@ -0,0 +1,64 @@ +#import "RNOnfidoSdk.h" ++ ++#ifdef USE_FRAMEWORKS ++#import ++#else +#import ++#endif + +@implementation RNOnfidoSdk { + OnfidoSdk *_onfidoSdk; @@ -1189,7 +1194,7 @@ index 0000000..c48f86e + +export default TurboModuleRegistry.getEnforcing("RNOnfidoSdk"); diff --git a/node_modules/@onfido/react-native-sdk/js/Onfido.ts b/node_modules/@onfido/react-native-sdk/js/Onfido.ts -index db35471..8bb6a57 100644 +index db35471..fa6c1ef 100644 --- a/node_modules/@onfido/react-native-sdk/js/Onfido.ts +++ b/node_modules/@onfido/react-native-sdk/js/Onfido.ts @@ -1,4 +1,4 @@ @@ -1222,7 +1227,7 @@ index db35471..8bb6a57 100644 addCustomMediaCallback(callback: (result: OnfidoMediaResult) => OnfidoMediaResult) { diff --git a/node_modules/@onfido/react-native-sdk/onfido-react-native-sdk.podspec b/node_modules/@onfido/react-native-sdk/onfido-react-native-sdk.podspec -index a9de0d0..fcd6d14 100644 +index a9de0d0..da83d9f 100644 --- a/node_modules/@onfido/react-native-sdk/onfido-react-native-sdk.podspec +++ b/node_modules/@onfido/react-native-sdk/onfido-react-native-sdk.podspec @@ -2,6 +2,8 @@ require "json" @@ -1234,7 +1239,7 @@ index a9de0d0..fcd6d14 100644 Pod::Spec.new do |s| s.name = "onfido-react-native-sdk" s.version = package["version"] -@@ -15,10 +17,15 @@ Pod::Spec.new do |s| +@@ -15,10 +17,22 @@ Pod::Spec.new do |s| s.platforms = { :ios => "11.0" } s.source = { :git => "https://github.com/onfido/react-native-sdk.git", :tag => "#{s.version}" } @@ -1246,6 +1251,13 @@ index a9de0d0..fcd6d14 100644 - s.dependency "React" - s.dependency "Onfido", "~> 29.6.0" + s.dependency "Onfido", "~> 29.7.0" ++ ++ if ENV['USE_FRAMEWORKS'] == '1' ++ s.pod_target_xcconfig = { ++ "OTHER_CFLAGS" => "$(inherited) -DUSE_FRAMEWORKS", ++ "OTHER_CPLUSPLUSFLAGS" => "$(inherited) -DUSE_FRAMEWORKS", ++ } ++ end + + if defined?(install_modules_dependencies()) != nil + install_modules_dependencies(s) @@ -1253,35 +1265,6 @@ index a9de0d0..fcd6d14 100644 + s.dependency "React" + end end -diff --git a/node_modules/@onfido/react-native-sdk/package.json b/node_modules/@onfido/react-native-sdk/package.json -index 50a331b..754c3c0 100644 ---- a/node_modules/@onfido/react-native-sdk/package.json -+++ b/node_modules/@onfido/react-native-sdk/package.json -@@ -24,7 +24,8 @@ - ], - "testPathIgnorePatterns": [ - "/TestApp", -- "/SampleApp" -+ "/SampleApp", -+ "/FabricSample" - ], - "globals": { - "__DEV__": true -@@ -82,6 +83,14 @@ - "react-native": "0.72.6", - "typescript": "^4.6.4" - }, -+ "codegenConfig": { -+ "name": "rnonfidosdk", -+ "type": "modules", -+ "jsSrcsDir": "./js", -+ "android": { -+ "javaPackageName": "com.onfido.reactnative.sdk" -+ } -+ }, - "dependencies": { - "js-base64": "3.7.5" - }, diff --git a/node_modules/@onfido/react-native-sdk/scripts/update-integration-versions.sh b/node_modules/@onfido/react-native-sdk/scripts/update-integration-versions.sh index 49f9d4c..1b9f304 100755 --- a/node_modules/@onfido/react-native-sdk/scripts/update-integration-versions.sh diff --git a/patches/react-native+0.73.4+014+fixPath.patch b/patches/react-native+0.73.4+014+fixPath.patch new file mode 100644 index 000000000000..ac49cca8621c --- /dev/null +++ b/patches/react-native+0.73.4+014+fixPath.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/react-native/scripts/codegen/generate-artifacts-executor.js b/node_modules/react-native/scripts/codegen/generate-artifacts-executor.js +index 025f80c..d276c69 100644 +--- a/node_modules/react-native/scripts/codegen/generate-artifacts-executor.js ++++ b/node_modules/react-native/scripts/codegen/generate-artifacts-executor.js +@@ -454,7 +454,7 @@ function findCodegenEnabledLibraries( + codegenConfigFilename, + codegenConfigKey, + ) { +- const pkgJson = readPackageJSON(appRootDir); ++ const pkgJson = readPackageJSON(path.join(appRootDir, process.env.REACT_NATIVE_DIR ? 'react-native' : '')); + const dependencies = {...pkgJson.dependencies, ...pkgJson.devDependencies}; + const libraries = []; + diff --git a/patches/react-native-vision-camera+4.0.0-beta.13.patch b/patches/react-native-vision-camera+4.0.0-beta.13.patch index 6a1cd1c74576..7daf4ef24cf9 100644 --- a/patches/react-native-vision-camera+4.0.0-beta.13.patch +++ b/patches/react-native-vision-camera+4.0.0-beta.13.patch @@ -1,8 +1,50 @@ diff --git a/node_modules/react-native-vision-camera/VisionCamera.podspec b/node_modules/react-native-vision-camera/VisionCamera.podspec -index 3a0e313..357a282 100644 +index 3a0e313..e983153 100644 --- a/node_modules/react-native-vision-camera/VisionCamera.podspec +++ b/node_modules/react-native-vision-camera/VisionCamera.podspec -@@ -40,6 +40,7 @@ Pod::Spec.new do |s| +@@ -2,7 +2,13 @@ require "json" + + package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +-nodeModules = File.join(File.dirname(`cd "#{Pod::Config.instance.installation_root.to_s}" && node --print "require.resolve('react-native/package.json')"`), '..') ++pkgJsonPath = ENV['REACT_NATIVE_DIR'] ? '../react-native/package.json' : 'react-native/package.json' ++nodeModules = File.join(File.dirname(`cd "#{Pod::Config.instance.installation_root.to_s}" && node --print "require.resolve('../react-native/package.json')"`), '..') ++ ++frameworks_flags = { ++ "OTHER_CFLAGS" => "$(inherited) -DUSE_FRAMEWORKS", ++ "OTHER_CPLUSPLUSFLAGS" => "$(inherited) -DUSE_FRAMEWORKS", ++} + + forceDisableFrameProcessors = false + if defined?($VCDisableFrameProcessors) +@@ -15,6 +21,13 @@ workletsPath = File.join(nodeModules, "react-native-worklets-core") + hasWorklets = File.exist?(workletsPath) && !forceDisableFrameProcessors + Pod::UI.puts("[VisionCamera] react-native-worklets-core #{hasWorklets ? "found" : "not found"}, Frame Processors #{hasWorklets ? "enabled" : "disabled"}!") + ++default_config = { ++ "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) SK_METAL=1 SK_GANESH=1 VISION_CAMERA_ENABLE_FRAME_PROCESSORS=#{hasWorklets}", ++ "OTHER_SWIFT_FLAGS" => "$(inherited) -DRCT_NEW_ARCH_ENABLED #{hasWorklets ? "-D VISION_CAMERA_ENABLE_FRAME_PROCESSORS" : ""}", ++ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", ++ "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/cpp/\"/** " ++} ++ + Pod::Spec.new do |s| + s.name = "VisionCamera" + s.version = package["version"] +@@ -27,19 +40,13 @@ Pod::Spec.new do |s| + s.platforms = { :ios => "12.4" } + s.source = { :git => "https://github.com/mrousavy/react-native-vision-camera.git", :tag => "#{s.version}" } + +- s.pod_target_xcconfig = { +- "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) SK_METAL=1 SK_GANESH=1 VISION_CAMERA_ENABLE_FRAME_PROCESSORS=#{hasWorklets}", +- "OTHER_SWIFT_FLAGS" => "$(inherited) #{hasWorklets ? "-D VISION_CAMERA_ENABLE_FRAME_PROCESSORS" : ""}", +- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", +- "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/cpp/\"/** " +- } +- + s.requires_arc = true + + # All source files that should be publicly visible # Note how this does not include headers, since those can nameclash. s.source_files = [ # Core @@ -10,7 +52,7 @@ index 3a0e313..357a282 100644 "ios/*.{m,mm,swift}", "ios/Core/*.{m,mm,swift}", "ios/Extensions/*.{m,mm,swift}", -@@ -47,6 +48,7 @@ Pod::Spec.new do |s| +@@ -47,6 +54,7 @@ Pod::Spec.new do |s| "ios/React Utils/*.{m,mm,swift}", "ios/Types/*.{m,mm,swift}", "ios/CameraBridge.h", @@ -18,16 +60,18 @@ index 3a0e313..357a282 100644 # Frame Processors hasWorklets ? "ios/Frame Processor/*.{m,mm,swift}" : "", -@@ -66,9 +68,10 @@ Pod::Spec.new do |s| +@@ -66,9 +74,12 @@ Pod::Spec.new do |s| "ios/**/*.h" ] - + - s.dependency "React" - s.dependency "React-Core" - s.dependency "React-callinvoker" -+ s.pod_target_xcconfig = { -+ "OTHER_SWIFT_FLAGS" => "-DRCT_NEW_ARCH_ENABLED" -+ } ++ if ENV['USE_FRAMEWORKS'] == '1' ++ s.pod_target_xcconfig = default_config.merge(frameworks_flags) ++ else ++ s.pod_target_xcconfig = default_config ++ end + install_modules_dependencies(s) if hasWorklets @@ -609,8 +653,7 @@ index 0000000..56a6c3d +include ':VisionCamera' diff --git a/node_modules/react-native-vision-camera/android/src/main/.DS_Store b/node_modules/react-native-vision-camera/android/src/main/.DS_Store new file mode 100644 -index 0000000..63b728b -Binary files /dev/null and b/node_modules/react-native-vision-camera/android/src/main/.DS_Store differ +index 0000000..e69de29 diff --git a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraDevicesManager.kt b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraDevicesManager.kt index a7c8358..a935ef6 100644 --- a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraDevicesManager.kt @@ -1273,13 +1316,12 @@ index 0000000..46c2c2c +#endif /* RCT_NEW_ARCH_ENABLED */ diff --git a/node_modules/react-native-vision-camera/ios/RNCameraView.mm b/node_modules/react-native-vision-camera/ios/RNCameraView.mm new file mode 100644 -index 0000000..63fb00f +index 0000000..019be20 --- /dev/null +++ b/node_modules/react-native-vision-camera/ios/RNCameraView.mm -@@ -0,0 +1,373 @@ +@@ -0,0 +1,377 @@ +// This guard prevent the code from being compiled in the old architecture +#ifdef RCT_NEW_ARCH_ENABLED -+//#import "RNCameraView.h" +#import + +#import @@ -1292,7 +1334,12 @@ index 0000000..63fb00f +#import +#import +#import ++ ++#ifdef USE_FRAMEWORKS ++#import ++#else +#import "VisionCamera-Swift.h" ++#endif + +@interface RNCameraView : RCTViewComponentView +@end @@ -1939,25 +1986,6 @@ index 0000000..e47e42f @@ -0,0 +1 @@ +{"version":3,"file":"CameraViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/specs/CameraViewNativeComponent.ts"],"names":[],"mappings":";;AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAC;AAGnG,MAAM,MAAM,yBAAyB,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;AAEnE,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,cAAc,EAAE,OAAO,CAAC;IACxB,uBAAuB,EAAE,OAAO,CAAC;IACjC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,QAAQ,CAAC;QAChB,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAC/B,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,MAAM,CAAC;QACxB,uBAAuB,EAAE,MAAM,EAAE,CAAC;QAClC,YAAY,EAAE,MAAM,EAAE,CAAC;KACxB,CAAC,CAAC;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,KAAK,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kCAAkC,CAAC,EAAE,OAAO,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,QAAQ,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,gBAAgB,CAAC,EAAE,QAAQ,CAAC;YAC1B,CAAC,CAAC,EAAE,MAAM,CAAC;YACX,CAAC,CAAC,EAAE,MAAM,CAAC;YACX,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,MAAM,CAAC,EAAE,MAAM,CAAC;SACjB,CAAC,CAAC;KACJ,CAAC,CAAC;IACH,aAAa,CAAC,EAAE,kBAAkB,CAChC,QAAQ,CAAC;QACP,KAAK,CAAC,EAAE,QAAQ,CAAC;YACf,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,KAAK,CAAC,EAAE,QAAQ,CAAC;gBAAE,CAAC,EAAE,MAAM,CAAC;gBAAC,CAAC,EAAE,MAAM,CAAC;gBAAC,KAAK,EAAE,MAAM,CAAC;gBAAC,MAAM,EAAE,MAAM,CAAA;aAAC,CAAC,CAAC;SAC1E,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,QAAQ,CAAC;YAAE,KAAK,EAAE,KAAK,CAAC;YAAC,MAAM,EAAE,KAAK,CAAA;SAAE,CAAC,CAAC;QAClD,OAAO,CAAC,EAAE,QAAQ,CAAC;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAC9C,CAAC,CACH,CAAC;IACF,SAAS,CAAC,EAAE,kBAAkB,CAC5B,QAAQ,CAAC;QACP,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CACH,CAAC;IACF,SAAS,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7C,SAAS,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7C,aAAa,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,EAAE,kBAAkB,CAC1B,QAAQ,CAAC;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,QAAQ,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACrF,CAAC,CACH,CAAC;IACF,WAAW,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;CAC/C;;AAED,wBAAiE"} \ No newline at end of file -diff --git a/node_modules/react-native-vision-camera/package.json b/node_modules/react-native-vision-camera/package.json -index 86352fa..7af9577 100644 ---- a/node_modules/react-native-vision-camera/package.json -+++ b/node_modules/react-native-vision-camera/package.json -@@ -166,5 +166,13 @@ - ] - ] - }, -- "packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" -+ "codegenConfig": { -+ "name": "RNVisioncameraSpec", -+ "type": "all", -+ "jsSrcsDir": "./src/specs", -+ "android": { -+ "javaPackageName": "com.mrousavy.camera" -+ } -+ }, -+ "packageManager": "yarn@1.22.19" - } diff --git a/node_modules/react-native-vision-camera/src/Camera.tsx b/node_modules/react-native-vision-camera/src/Camera.tsx index 18733ba..1668322 100644 --- a/node_modules/react-native-vision-camera/src/Camera.tsx diff --git a/src/components/InitialURLContextProvider.tsx b/src/components/InitialURLContextProvider.tsx index a3df93844ca9..4cc800497b0f 100644 --- a/src/components/InitialURLContextProvider.tsx +++ b/src/components/InitialURLContextProvider.tsx @@ -1,6 +1,5 @@ import React, {createContext, useEffect, useState} from 'react'; import type {ReactNode} from 'react'; -import {Linking} from 'react-native'; import type {Route} from '@src/ROUTES'; /** Initial url that will be opened when NewDot is embedded into Hybrid App. */ @@ -15,16 +14,7 @@ type InitialURLContextProviderProps = { }; function InitialURLContextProvider({children, url}: InitialURLContextProviderProps) { - const [initialURL, setInitialURL] = useState(url); - useEffect(() => { - if (initialURL) { - return; - } - Linking.getInitialURL().then((initURL) => { - setInitialURL(initURL as Route); - }); - }, [initialURL]); - return {children}; + return {children}; } InitialURLContextProvider.displayName = 'InitialURLContextProvider'; From 6f20396264e128ff960c6da68d0e862d7942dc0a Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 16 Apr 2024 17:23:49 +0500 Subject: [PATCH 056/306] refactor: leverage useOnyx hook instead of withOnyx HOC --- src/hooks/useReportIDs.tsx | 195 +++++++++++++++---------------------- 1 file changed, 81 insertions(+), 114 deletions(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index c807e7dfcdcc..d3426866fe63 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -1,5 +1,5 @@ import React, {createContext, useCallback, useContext, useMemo} from 'react'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -16,18 +16,7 @@ type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean}; type PolicySelector = Pick; type ReportActionsSelector = Array>; -type OnyxProps = { - chatReports: OnyxCollection; - betas: OnyxEntry; - policies: OnyxCollection; - allReportActions: OnyxCollection; - transactionViolations: OnyxCollection; - policyMembers: OnyxCollection; - priorityMode: OnyxEntry; - reportsDrafts: OnyxCollection; -}; - -type WithReportIDsContextProviderProps = OnyxProps & { +type ReportIDsContextProviderProps = { children: React.ReactNode; currentReportIDForTests?: string; }; @@ -42,71 +31,6 @@ const ReportIDsContext = createContext({ currentReportID: '', }); -function WithReportIDsContextProvider({ - children, - chatReports, - betas, - policies, - allReportActions, - transactionViolations, - policyMembers, - priorityMode, - reportsDrafts, - /** - * Only required to make unit tests work, since we - * explicitly pass the currentReportID in LHNTestUtils - * to SidebarLinksData, so this context doesn't have - * access to currentReportID in that case. - * - * This is a workaround to have currentReportID available in testing environment. - */ - currentReportIDForTests, -}: WithReportIDsContextProviderProps) { - const {accountID} = useCurrentUserPersonalDetails(); - const currentReportIDValue = useCurrentReportID(); - const derivedCurrentReportID = currentReportIDForTests ?? currentReportIDValue?.currentReportID; - const {activeWorkspaceID} = useActiveWorkspace(); - - const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, accountID), [activeWorkspaceID, policyMembers, accountID]); - - const getOrderedReportIDs = useCallback( - (currentReportID?: string) => - SidebarUtils.getOrderedReportIDs( - currentReportID ?? null, - chatReports, - betas, - policies as OnyxCollection, - priorityMode, - allReportActions as OnyxCollection, - transactionViolations, - activeWorkspaceID, - policyMemberAccountIDs, - ), - // we need reports draft in deps array for reloading of list when reportsDrafts will change - // eslint-disable-next-line react-hooks/exhaustive-deps - [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, reportsDrafts], - ); - - const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); - const contextValue: ReportIDsContextValue = useMemo(() => { - // 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 - // we first generate the list as if there was no current report, then we check if - // the current report is missing from the list, which should very rarely happen. In this - // case we re-generate the list a 2nd time with the current report included. - if (derivedCurrentReportID && !orderedReportIDs.includes(derivedCurrentReportID)) { - return {orderedReportIDs: getOrderedReportIDs(derivedCurrentReportID), currentReportID: derivedCurrentReportID ?? ''}; - } - - return { - orderedReportIDs: getOrderedReportIDs(), - currentReportID: derivedCurrentReportID ?? '', - }; - }, [getOrderedReportIDs, orderedReportIDs, derivedCurrentReportID]); - - return {children}; -} - /** * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. @@ -176,42 +100,85 @@ const policySelector = (policy: OnyxEntry): PolicySelector => avatar: policy.avatar, }) as PolicySelector; -const ReportIDsContextProvider = withOnyx({ - chatReports: { - key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector, - initialValue: {}, - }, - priorityMode: { - key: ONYXKEYS.NVP_PRIORITY_MODE, - initialValue: CONST.PRIORITY_MODE.DEFAULT, - }, - betas: { - key: ONYXKEYS.BETAS, - initialValue: [], - }, - allReportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - selector: reportActionsSelector, - initialValue: {}, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, - initialValue: {}, - }, - policyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - initialValue: {}, - }, - reportsDrafts: { - key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, - initialValue: {}, - }, -})(WithReportIDsContextProvider); +const priorityModeOptions = { + initialValue: CONST.PRIORITY_MODE.DEFAULT, +}; + +const chatReportsOptions = { + selector: chatReportSelector, +}; + +const policiesOptions = {selector: policySelector}; + +const allReportActionsOptions = {selector: reportActionsSelector}; + +const betasOptions = {initialValue: []}; + +function ReportIDsContextProvider({ + children, + /** + * Only required to make unit tests work, since we + * explicitly pass the currentReportID in LHNTestUtils + * to SidebarLinksData, so this context doesn't have + * access to currentReportID in that case. + * + * This is a workaround to have currentReportID available in testing environment. + */ + currentReportIDForTests, +}: ReportIDsContextProviderProps) { + const [priorityMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE, priorityModeOptions); + const [chatReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, chatReportsOptions); + const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, policiesOptions); + const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, allReportActionsOptions); + const [policyMembers] = useOnyx(ONYXKEYS.COLLECTION.POLICY_MEMBERS); + const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const [reportsDrafts] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT); + const [betas] = useOnyx(ONYXKEYS.BETAS, betasOptions); + + const {accountID} = useCurrentUserPersonalDetails(); + const currentReportIDValue = useCurrentReportID(); + const derivedCurrentReportID = currentReportIDForTests ?? currentReportIDValue?.currentReportID; + const {activeWorkspaceID} = useActiveWorkspace(); + + const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, accountID), [activeWorkspaceID, policyMembers, accountID]); + + const getOrderedReportIDs = useCallback( + (currentReportID?: string) => + SidebarUtils.getOrderedReportIDs( + currentReportID ?? null, + chatReports, + betas, + policies as OnyxCollection, + priorityMode, + allReportActions as OnyxCollection, + transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + ), + // we need reports draft in deps array for reloading of list when reportsDrafts will change + // eslint-disable-next-line react-hooks/exhaustive-deps + [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, reportsDrafts], + ); + + const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); + const contextValue: ReportIDsContextValue = useMemo(() => { + // 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 + // we first generate the list as if there was no current report, then we check if + // the current report is missing from the list, which should very rarely happen. In this + // case we re-generate the list a 2nd time with the current report included. + if (derivedCurrentReportID && !orderedReportIDs.includes(derivedCurrentReportID)) { + return {orderedReportIDs: getOrderedReportIDs(derivedCurrentReportID), currentReportID: derivedCurrentReportID ?? ''}; + } + + return { + orderedReportIDs: getOrderedReportIDs(), + currentReportID: derivedCurrentReportID ?? '', + }; + }, [getOrderedReportIDs, orderedReportIDs, derivedCurrentReportID]); + + return {children}; +} function useReportIDs() { return useContext(ReportIDsContext); From efbbda3de71002b7a1f30e1e0f2bb3ec7c7a6b90 Mon Sep 17 00:00:00 2001 From: GandalfGwaihir Date: Wed, 17 Apr 2024 05:27:35 +0530 Subject: [PATCH 057/306] Allow the requestee in 1:1 transactions to hold requests --- src/components/MoneyRequestHeader.tsx | 2 +- src/pages/iou/HoldReasonPage.tsx | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index f451f5f15581..1ce9fa31385e 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -97,7 +97,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, IOU.unholdRequest(iouTransactionID, report?.reportID); } else { const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); - Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? '', iouTransactionID, report?.reportID, activeRoute)); + Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, iouTransactionID, report?.reportID, activeRoute)); } }; diff --git a/src/pages/iou/HoldReasonPage.tsx b/src/pages/iou/HoldReasonPage.tsx index 2a5cba810759..52387fd3a377 100644 --- a/src/pages/iou/HoldReasonPage.tsx +++ b/src/pages/iou/HoldReasonPage.tsx @@ -46,6 +46,11 @@ function HoldReasonPage({route}: HoldReasonPageProps) { const {transactionID, reportID, backTo} = route.params; const report = ReportUtils.getReport(reportID); + + // We check if the report is part of a policy, if not then it's a personal request (1:1 request) + // We need to allow both users in the 1:1 request to put the request on hold + const isWorkspaceRequest = ReportUtils.isGroupPolicy(report); + console.log('isWorkspaceRequest', isWorkspaceRequest); const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); const navigateBack = () => { @@ -53,7 +58,10 @@ function HoldReasonPage({route}: HoldReasonPageProps) { }; const onSubmit = (values: FormOnyxValues) => { - if (!ReportUtils.canEditMoneyRequest(parentReportAction)) { + // We have extra !!isWorkspaceRequest condition as in case of 1:1 request, canEditMoneyRequest will rightly return false + // as we do not allow requestee to edit fields like description and amount, + // but we still want the requestee to be able to put the request on hold + if (!ReportUtils.canEditMoneyRequest(parentReportAction) && isWorkspaceRequest) { return; } @@ -68,7 +76,11 @@ function HoldReasonPage({route}: HoldReasonPageProps) { if (!values.comment) { errors.comment = 'common.error.fieldRequired'; } - if (!ReportUtils.canEditMoneyRequest(parentReportAction)) { + + // We have extra !!isWorkspaceRequest condition as in case of 1:1 request, canEditMoneyRequest will rightly return false + // as we do not allow requestee to edit fields like description and amount, + // but we still want the requestee to be able to put the request on hold + if (!ReportUtils.canEditMoneyRequest(parentReportAction) && isWorkspaceRequest) { const formErrors = {}; ErrorUtils.addErrorMessage(formErrors, 'reportModified', 'common.error.requestModified'); FormActions.setErrors(ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM, formErrors); From 9711d32ac5e541d96feb99bb1b66d9c550deb36c Mon Sep 17 00:00:00 2001 From: GandalfGwaihir Date: Wed, 17 Apr 2024 05:35:47 +0530 Subject: [PATCH 058/306] Remove console log --- src/pages/iou/HoldReasonPage.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/HoldReasonPage.tsx b/src/pages/iou/HoldReasonPage.tsx index 52387fd3a377..2740fcb0a79a 100644 --- a/src/pages/iou/HoldReasonPage.tsx +++ b/src/pages/iou/HoldReasonPage.tsx @@ -50,7 +50,6 @@ function HoldReasonPage({route}: HoldReasonPageProps) { // We check if the report is part of a policy, if not then it's a personal request (1:1 request) // We need to allow both users in the 1:1 request to put the request on hold const isWorkspaceRequest = ReportUtils.isGroupPolicy(report); - console.log('isWorkspaceRequest', isWorkspaceRequest); const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); const navigateBack = () => { @@ -58,7 +57,7 @@ function HoldReasonPage({route}: HoldReasonPageProps) { }; const onSubmit = (values: FormOnyxValues) => { - // We have extra !!isWorkspaceRequest condition as in case of 1:1 request, canEditMoneyRequest will rightly return false + // We have extra isWorkspaceRequest condition as in case of 1:1 request, canEditMoneyRequest will rightly return false // as we do not allow requestee to edit fields like description and amount, // but we still want the requestee to be able to put the request on hold if (!ReportUtils.canEditMoneyRequest(parentReportAction) && isWorkspaceRequest) { @@ -76,8 +75,7 @@ function HoldReasonPage({route}: HoldReasonPageProps) { if (!values.comment) { errors.comment = 'common.error.fieldRequired'; } - - // We have extra !!isWorkspaceRequest condition as in case of 1:1 request, canEditMoneyRequest will rightly return false + // We have extra isWorkspaceRequest condition as in case of 1:1 request, canEditMoneyRequest will rightly return false // as we do not allow requestee to edit fields like description and amount, // but we still want the requestee to be able to put the request on hold if (!ReportUtils.canEditMoneyRequest(parentReportAction) && isWorkspaceRequest) { From 56c484e5432ebdb77dd13da8ce928f85be3929dc Mon Sep 17 00:00:00 2001 From: GandalfGwaihir Date: Wed, 17 Apr 2024 05:48:20 +0530 Subject: [PATCH 059/306] add isWorkspaceRequest as dependency --- src/pages/iou/HoldReasonPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/HoldReasonPage.tsx b/src/pages/iou/HoldReasonPage.tsx index 2740fcb0a79a..6e31f6202383 100644 --- a/src/pages/iou/HoldReasonPage.tsx +++ b/src/pages/iou/HoldReasonPage.tsx @@ -86,7 +86,7 @@ function HoldReasonPage({route}: HoldReasonPageProps) { return errors; }, - [parentReportAction], + [parentReportAction, isWorkspaceRequest], ); useEffect(() => { From 26bcce68b747dde6bf8c26f73b5207d708004782 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:00:02 +0200 Subject: [PATCH 060/306] add permissions to report --- src/CONST.ts | 6 ++++++ src/types/onyx/Report.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 2b85bcb2c326..425391f6ebea 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -837,6 +837,12 @@ const CONST = { OWNER_EMAIL_FAKE: '__FAKE__', OWNER_ACCOUNT_ID_FAKE: 0, DEFAULT_REPORT_NAME: 'Chat Report', + PERMISSIONS: { + READ: 'read', + WRITE: 'write', + SHARE: 'share', + OWN: 'own', + } }, NEXT_STEP: { FINISHED: 'Finished!', diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index fe3eec6dc11e..36d345b1084c 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -186,6 +186,8 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< transactionThreadReportID?: string; fieldList?: Record; + + permissions?: Array>; }, PolicyReportField['fieldID'] >; From de29862ae77c1cd6b5fd2fa03fc8e0443ed133ed Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:01:28 +0200 Subject: [PATCH 061/306] create isReadOnly --- src/libs/ReportUtils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 39964bfcc4d7..6b9ef9031cf0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5989,6 +5989,13 @@ function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyID: s return isChatRoom(report) && !isThread(report); } +/** + * + * Checks if report is in read-only mode. + */ +function isReadOnly(report: OnyxEntry): boolean { + return !report?.permissions?.includes(CONST.REPORT.PERMISSIONS.WRITE) ?? false; +} export { getReportParticipantsTitle, @@ -6228,6 +6235,7 @@ export { buildParticipantsFromAccountIDs, canReportBeMentionedWithinPolicy, getAllHeldTransactions, + isReadOnly, }; export type { From e2ad69dc9610992ac13438b4316c2f2bbe53d1a2 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:08:32 +0200 Subject: [PATCH 062/306] integrate OnboardingReportFooterMessage into ReportFooter --- src/libs/ReportUtils.ts | 17 +++++++++-------- .../report/OnboardingReportFooterMessage.tsx | 11 +++++++---- src/pages/home/report/ReportFooter.tsx | 6 ++++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6b9ef9031cf0..e20686e1326a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5198,6 +5198,14 @@ function isMoneyRequestReportPendingDeletion(report: OnyxEntry | EmptyOb return parentReportAction?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } +/** + * + * Checks if report is in read-only mode. + */ +function isReadOnly(report: OnyxEntry): boolean { + return !report?.permissions?.includes(CONST.REPORT.PERMISSIONS.WRITE) ?? false; +} + function canUserPerformWriteAction(report: OnyxEntry) { const reportErrors = getAddWorkspaceRoomOrChatReportErrors(report); @@ -5206,7 +5214,7 @@ function canUserPerformWriteAction(report: OnyxEntry) { return false; } - return !isArchivedRoom(report) && isEmptyObject(reportErrors) && report && isAllowedToComment(report) && !isAnonymousUser; + return !isArchivedRoom(report) && isEmptyObject(reportErrors) && report && isAllowedToComment(report) && !isAnonymousUser && !isReadOnly(report); } /** @@ -5989,13 +5997,6 @@ function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyID: s return isChatRoom(report) && !isThread(report); } -/** - * - * Checks if report is in read-only mode. - */ -function isReadOnly(report: OnyxEntry): boolean { - return !report?.permissions?.includes(CONST.REPORT.PERMISSIONS.WRITE) ?? false; -} export { getReportParticipantsTitle, diff --git a/src/pages/home/report/OnboardingReportFooterMessage.tsx b/src/pages/home/report/OnboardingReportFooterMessage.tsx index a48a609b51a7..f6c91d3b9f12 100644 --- a/src/pages/home/report/OnboardingReportFooterMessage.tsx +++ b/src/pages/home/report/OnboardingReportFooterMessage.tsx @@ -1,8 +1,7 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxCollection} from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; @@ -16,8 +15,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy as PolicyType, Report} from '@src/types/onyx'; -type OnboardingReportFooterMessageOnyxProps = {reports: OnyxCollection; policies: OnyxCollection}; -type OnboardingReportFooterMessageProps = OnboardingReportFooterMessageOnyxProps & {choice: ValueOf}; +// TODO: Use a proper choice type +type OnboardingReportFooterMessageOnyxProps = {choice: OnyxEntry; reports: OnyxCollection; policies: OnyxCollection}; +type OnboardingReportFooterMessageProps = OnboardingReportFooterMessageOnyxProps; function OnboardingReportFooterMessage({choice, reports, policies}: OnboardingReportFooterMessageProps) { const {translate} = useLocalize(); @@ -83,6 +83,9 @@ function OnboardingReportFooterMessage({choice, reports, policies}: OnboardingRe } OnboardingReportFooterMessage.displayName = 'OnboardingReportFooterMessage'; export default withOnyx({ + choice: { + key: ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, + }, reports: { key: ONYXKEYS.COLLECTION.REPORT, }, diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx index bd143f9ef196..bccec0597fb0 100644 --- a/src/pages/home/report/ReportFooter.tsx +++ b/src/pages/home/report/ReportFooter.tsx @@ -20,6 +20,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; +import OnboardingReportFooterMessage from './OnboardingReportFooterMessage'; import ReportActionCompose from './ReportActionCompose/ReportActionCompose'; type ReportFooterOnyxProps = { @@ -81,6 +82,7 @@ function ReportFooter({ const isSmallSizeLayout = windowWidth - (isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint; const hideComposer = !ReportUtils.canUserPerformWriteAction(report); + const isReadOnlyReport = ReportUtils.isReadOnly(report); const allPersonalDetails = usePersonalDetails(); @@ -125,6 +127,10 @@ function ReportFooter({ [report.reportID, handleCreateTask], ); + if (isReadOnlyReport) { + ; + } + return ( <> {hideComposer && ( From 471a02adff994ef218ec3128614a92534fa7f6c4 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:09:13 +0200 Subject: [PATCH 063/306] prettify --- src/pages/home/report/OnboardingReportFooterMessage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/home/report/OnboardingReportFooterMessage.tsx b/src/pages/home/report/OnboardingReportFooterMessage.tsx index f6c91d3b9f12..54659c14cf6e 100644 --- a/src/pages/home/report/OnboardingReportFooterMessage.tsx +++ b/src/pages/home/report/OnboardingReportFooterMessage.tsx @@ -81,7 +81,9 @@ function OnboardingReportFooterMessage({choice, reports, policies}: OnboardingRe ); } + OnboardingReportFooterMessage.displayName = 'OnboardingReportFooterMessage'; + export default withOnyx({ choice: { key: ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, From b4aed0fa5eb550664687f06008359fc21bb881c3 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:10:17 +0200 Subject: [PATCH 064/306] restrict task action in header --- src/components/TaskHeaderActionButton.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/TaskHeaderActionButton.tsx b/src/components/TaskHeaderActionButton.tsx index 2d964f58c253..a7e3abcd3012 100644 --- a/src/components/TaskHeaderActionButton.tsx +++ b/src/components/TaskHeaderActionButton.tsx @@ -25,6 +25,10 @@ function TaskHeaderActionButton({report, session}: TaskHeaderActionButtonProps) const {translate} = useLocalize(); const styles = useThemeStyles(); + if (ReportUtils.isReadOnly(report)) { + return null; + } + return (