diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts index f2325eda532d..38abe075ef81 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts +++ b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts @@ -4,20 +4,23 @@ import type {ValueOf} from 'type-fest'; import type {Attachment} from '@components/Attachments/types'; import * as FileUtils from '@libs/fileDownload/FileUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import {getReport} from '@libs/ReportUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import CONST from '@src/CONST'; import type {ReportAction, ReportActions} from '@src/types/onyx'; +import type {Note} from '@src/types/onyx/Report'; /** * Constructs the initial component state from report actions */ function extractAttachments( type: ValueOf, - {reportID, accountID, parentReportAction, reportActions}: {reportID?: string; accountID?: number; parentReportAction?: OnyxEntry; reportActions?: OnyxEntry}, + { + privateNotes, + accountID, + parentReportAction, + reportActions, + }: {privateNotes?: Record; accountID?: number; parentReportAction?: OnyxEntry; reportActions?: OnyxEntry}, ) { - const report = getReport(reportID); - const privateNotes = report?.privateNotes; const targetNote = privateNotes?.[Number(accountID)]?.note ?? ''; const attachments: Attachment[] = []; diff --git a/src/components/Attachments/AttachmentCarousel/index.native.tsx b/src/components/Attachments/AttachmentCarousel/index.native.tsx index aad307073c0f..15740725c42e 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.native.tsx @@ -33,7 +33,7 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, const parentReportAction = report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : undefined; let targetAttachments: Attachment[] = []; if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) { - targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {reportID: report.reportID, accountID}); + targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {privateNotes: report.privateNotes, accountID}); } else { targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions}); } diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index 947569538d32..eeac97bc5fa5 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -59,7 +59,7 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, const parentReportAction = report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : undefined; let targetAttachments: Attachment[] = []; if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) { - targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {reportID: report.reportID, accountID}); + targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {privateNotes: report.privateNotes, accountID}); } else { targetAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions: reportActions ?? undefined}); } @@ -91,7 +91,7 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate(targetAttachments[initialPage]); } } - }, [reportActions, parentReportActions, compareImage, report.parentReportActionID, attachments, setDownloadButtonVisibility, onNavigate, accountID, report.reportID, type]); + }, [report.privateNotes, reportActions, parentReportActions, compareImage, report.parentReportActionID, attachments, setDownloadButtonVisibility, onNavigate, accountID, type]); // Scroll position is affected when window width is resized, so we readjust it on width changes useEffect(() => { diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx index 345bd338f365..27c7f229a247 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer.tsx @@ -3,14 +3,13 @@ import React, {useMemo} from 'react'; import type {TextStyle} from 'react-native'; import {StyleSheet} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getReport} from '@libs/ReportUtils'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -34,8 +33,7 @@ const getMentionDetails = (htmlAttributeReportID: string, currentReport: OnyxEnt // Get mention details based on reportID from tag attribute if (!isEmpty(htmlAttributeReportID)) { - const report = getReport(htmlAttributeReportID); - + const report = reports?.[htmlAttributeReportID]; reportID = report?.reportID ?? htmlAttributeReportID; mentionDisplayText = removeLeadingLTRAndHash(report?.reportName ?? report?.displayName ?? htmlAttributeReportID); // Get mention details from name inside tnode @@ -61,7 +59,8 @@ function MentionReportRenderer({style, tnode, TDefaultRenderer, reports, ...defa const htmlAttributeReportID = tnode.attributes.reportid; const currentReportID = useCurrentReportID(); - const currentReport = getReport(currentReportID?.currentReportID); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const [currentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${currentReportID?.currentReportID || -1}`); // When we invite someone to a room they don't have the policy object, but we still want them to be able to see and click on report mentions, so we only check if the policyID in the report is from a workspace const isGroupPolicyReport = useMemo(() => currentReport && !isEmptyObject(currentReport) && !!currentReport.policyID && currentReport.policyID !== CONST.POLICY.ID_FAKE, [currentReport]); diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 6e0dbf267151..c7797a37fd12 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -2,6 +2,7 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef, useState} from 'react'; import type {GestureResponderEvent, ViewStyle} from 'react-native'; import {StyleSheet, View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -26,6 +27,7 @@ import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManag import * as ReportUtils from '@libs/ReportUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OptionRowLHNProps} from './types'; @@ -37,6 +39,9 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const isFocusedRef = useRef(true); const {shouldUseNarrowLayout} = useResponsiveLayout(); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID || -1}`); + const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); @@ -120,7 +125,6 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const statusClearAfterDate = optionItem.status?.clearAfter ?? ''; const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText ? `${statusText} ` : ''}(${formattedDate})` : statusText; - const report = ReportUtils.getReport(optionItem.reportID ?? '-1'); const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(!isEmptyObject(report) ? report : undefined); const isGroupChat = ReportUtils.isGroupChat(optionItem) || ReportUtils.isDeprecatedGroupDM(optionItem); diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx index e1d9ffaeeef8..2b1cd0729c0b 100644 --- a/src/components/SettlementButton.tsx +++ b/src/components/SettlementButton.tsx @@ -1,7 +1,7 @@ import React, {useMemo} from 'react'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import * as ReportUtils from '@libs/ReportUtils'; @@ -144,7 +144,8 @@ function SettlementButton({ const {isOffline} = useNetwork(); const session = useSession(); - const chatReport = ReportUtils.getReport(chatReportID); + // The app would crash due to subscribing to the entire report collection if chatReportID is an empty string. So we should have a fallback ID here. + const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || -1}`); const isInvoiceReport = (!isEmptyObject(iouReport) && ReportUtils.isInvoiceReport(iouReport)) || false; const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport); const shouldShowPaywithExpensifyOption = !isPaidGroupPolicy || (!shouldHidePaymentOptions && ReportUtils.isPayer(session, iouReport as OnyxEntry)); diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index fc2288c5dbf6..da781f6c0bc9 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -2,11 +2,10 @@ import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyTagList, ReportAction} from '@src/types/onyx'; +import type {PolicyTagList, Report, ReportAction} from '@src/types/onyx'; import type {ModifiedExpense} from '@src/types/onyx/OriginalMessage'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; -import getReportPolicyID from './getReportPolicyID'; import * as Localize from './Localize'; import * as PolicyUtils from './PolicyUtils'; import * as TransactionUtils from './TransactionUtils'; @@ -24,6 +23,13 @@ Onyx.connect({ }, }); +let allReports: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (value) => (allReports = value), +}); + /** * Builds the partial message fragment for a modified field on the expense. */ @@ -110,7 +116,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr return ''; } const reportActionOriginalMessage = reportAction?.originalMessage as ModifiedExpense | undefined; - const policyID = getReportPolicyID(reportID) ?? '-1'; + const policyID = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]?.policyID ?? '-1'; const removalFragments: string[] = []; const setFragments: string[] = []; diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 34e093f2b74b..6389876a1858 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -1,11 +1,13 @@ import {findFocusedRoute} from '@react-navigation/core'; import type {EventArg, NavigationContainerEventMap} from '@react-navigation/native'; import {CommonActions, getPathFromState, StackActions} from '@react-navigation/native'; +import type {OnyxCollection} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; import * as ReportUtils from '@libs/ReportUtils'; -import {getReport} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; +import ONYXKEYS from '@src/ONYXKEYS'; import type {HybridAppRoute, Route} from '@src/ROUTES'; import ROUTES, {HYBRID_APP_ROUTES} from '@src/ROUTES'; import {PROTECTED_SCREENS} from '@src/SCREENS'; @@ -32,6 +34,13 @@ let pendingRoute: Route | null = null; let shouldPopAllStateOnUP = false; +let allReports: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (value) => (allReports = value), +}); + /** * Inform the navigation that next time user presses UP we should pop all the state back to LHN. */ @@ -59,7 +68,7 @@ const dismissModal = (reportID?: string, ref = navigationRef) => { originalDismissModal(ref); return; } - const report = getReport(reportID); + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; originalDismissModalWithReport({reportID, ...report}, ref); }; // Re-exporting the closeRHPFlow here to fill in default value for navigationRef. The closeRHPFlow isn't defined in this file to avoid cyclic dependencies. diff --git a/src/libs/Notification/PushNotification/subscribePushNotification/index.ts b/src/libs/Notification/PushNotification/subscribePushNotification/index.ts index 792c49c8bbcc..18099f157d6a 100644 --- a/src/libs/Notification/PushNotification/subscribePushNotification/index.ts +++ b/src/libs/Notification/PushNotification/subscribePushNotification/index.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; import applyOnyxUpdatesReliably from '@libs/actions/applyOnyxUpdatesReliably'; import * as ActiveClientManager from '@libs/ActiveClientManager'; import Log from '@libs/Log'; @@ -6,13 +7,13 @@ import Navigation from '@libs/Navigation/Navigation'; import type {ReportActionPushNotificationData} from '@libs/Notification/PushNotification/NotificationType'; import getPolicyEmployeeAccountIDs from '@libs/PolicyEmployeeListUtils'; import {extractPolicyIDFromPath} from '@libs/PolicyUtils'; -import {doesReportBelongToWorkspace, getReport} from '@libs/ReportUtils'; +import {doesReportBelongToWorkspace} from '@libs/ReportUtils'; import Visibility from '@libs/Visibility'; import * as Modal from '@userActions/Modal'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {OnyxUpdatesFromServer} from '@src/types/onyx'; +import type {OnyxUpdatesFromServer, Report} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import PushNotification from '..'; @@ -27,6 +28,13 @@ Onyx.connect({ }, }); +let allReports: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (value) => (allReports = value), +}); + function getLastUpdateIDAppliedToClient(): Promise { return new Promise((resolve) => { Onyx.connect({ @@ -74,7 +82,7 @@ function navigateToReport({reportID, reportActionID}: ReportActionPushNotificati Log.info('[PushNotification] Navigating to report', false, {reportID, reportActionID}); const policyID = lastVisitedPath && extractPolicyIDFromPath(lastVisitedPath); - const report = getReport(reportID.toString()); + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const policyEmployeeAccountIDs = policyID ? getPolicyEmployeeAccountIDs(policyID) : []; const reportBelongsToWorkspace = policyID && !isEmptyObject(report) && doesReportBelongToWorkspace(report, policyEmployeeAccountIDs, policyID); diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index ad0cf6974f70..569e12820b2d 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -335,6 +335,34 @@ Onyx.connect({ }, }); +let allReports: OnyxCollection = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (value) => (allReports = value), +}); + +let allReportsDraft: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_DRAFT, + waitForCollectionCallback: true, + callback: (value) => (allReportsDraft = value), +}); + +/** + * Get the report or draft report given a reportID + */ +function getReportOrDraftReport(reportID: string | undefined): OnyxEntry { + if (!allReports && !allReportsDraft) { + return undefined; + } + + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const draftReport = allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`]; + + return report ?? draftReport; +} + /** * @param defaultValues {login: accountID} In workspace invite page, when new user is added we pass available data to opt in * @returns Returns avatar data for a list of user accountIDs @@ -577,7 +605,7 @@ function getLastActorDisplayName(lastActorDetails: Partial | nu * Update alternate text for the option when applicable */ function getAlternateText(option: ReportUtils.OptionData, {showChatPreviewLine = false, forcePolicyNamePreview = false}: PreviewConfig) { - const report = ReportUtils.getReport(option.reportID); + const report = getReportOrDraftReport(option.reportID); const isAdminRoom = ReportUtils.isAdminRoom(report); const isAnnounceRoom = ReportUtils.isAnnounceRoom(report); @@ -653,7 +681,7 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails const properSchemaForMoneyRequestMessage = ReportUtils.getReportPreviewMessage(report, lastReportAction, true, false, null, true); lastMessageTextFromReport = ReportUtils.formatReportLastMessageText(properSchemaForMoneyRequestMessage); } else if (ReportActionUtils.isReportPreviewAction(lastReportAction)) { - const iouReport = ReportUtils.getReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction)); + const iouReport = getReportOrDraftReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction)); const lastIOUMoneyReportAction = allSortedReportActions[iouReport?.reportID ?? '-1']?.find( (reportAction, key) => ReportActionUtils.shouldReportActionBeVisible(reportAction, key) && @@ -832,7 +860,7 @@ function createOption( * Get the option for a given report. */ function getReportOption(participant: Participant): ReportUtils.OptionData { - const report = ReportUtils.getReport(participant.reportID); + const report = getReportOrDraftReport(participant.reportID); const visibleParticipantAccountIDs = ReportUtils.getParticipantsAccountIDsForDisplay(report, true); const option = createOption( @@ -866,7 +894,7 @@ function getReportOption(participant: Participant): ReportUtils.OptionData { * Get the option for a policy expense report. */ function getPolicyExpenseReportOption(participant: Participant | ReportUtils.OptionData): ReportUtils.OptionData { - const expenseReport = ReportUtils.isPolicyExpenseChat(participant) ? ReportUtils.getReport(participant.reportID) : null; + const expenseReport = ReportUtils.isPolicyExpenseChat(participant) ? getReportOrDraftReport(participant.reportID) : null; const visibleParticipantAccountIDs = Object.entries(expenseReport?.participants ?? {}) .filter(([, reportParticipant]) => reportParticipant && !reportParticipant.hidden) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8a30936ba33a..c0c3f5a6dfa0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -65,7 +65,6 @@ import * as store from './actions/ReimbursementAccount/store'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import {hasValidDraftComment} from './DraftCommentUtils'; -import originalGetReportPolicyID from './getReportPolicyID'; import isReportMessageAttachment from './isReportMessageAttachment'; import localeCompare from './LocaleCompare'; import * as LocalePhoneNumber from './LocalePhoneNumber'; @@ -587,9 +586,9 @@ function getChatType(report: OnyxInputOrEntry | Participant | EmptyObjec } /** - * Get the report given a reportID + * Get the report or draft report given a reportID */ -function getReport(reportID: string | undefined): OnyxEntry { +function getReportOrDraftReport(reportID: string | undefined): OnyxEntry { if (!allReports && !allReportsDraft) { return undefined; } @@ -633,7 +632,7 @@ function getRootParentReport(report: OnyxEntry | undefined | EmptyObject return report; } - const parentReport = getReport(report?.parentReportID); + const parentReport = getReportOrDraftReport(report?.parentReportID); // Runs recursion to iterate a parent report return getRootParentReport(!isEmptyObject(parentReport) ? parentReport : undefined); @@ -891,6 +890,12 @@ function isInvoiceRoom(report: OnyxEntry | EmptyObject): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; } +function isInvoiceRoomWithID(reportID?: string): boolean { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID || -1}`]; + return isInvoiceRoom(report); +} + /** * Checks if a report is a completed task report. */ @@ -1240,6 +1245,15 @@ function isArchivedRoom(report: OnyxInputOrEntry | EmptyObject, reportNa return report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED && report?.stateNum === CONST.REPORT.STATE_NUM.APPROVED; } +/** + * Whether the report with the provided reportID is an archived room + */ +function isArchivedRoomWithID(reportID?: string) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID || -1}`]; + return isArchivedRoom(report); +} + /** * Whether the provided report is a closed report */ @@ -1530,7 +1544,7 @@ function canAddOrDeleteTransactions(moneyRequestReport: OnyxEntry): bool * policy admin */ function canDeleteReportAction(reportAction: OnyxInputOrEntry, reportID: string): boolean { - const report = getReport(reportID); + const report = getReportOrDraftReport(reportID); const isActionOwner = reportAction?.actorAccountID === currentUserAccountID; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`] ?? null; @@ -1543,7 +1557,7 @@ function canDeleteReportAction(reportAction: OnyxInputOrEntry, rep return false; } - const linkedReport = isThreadFirstChat(reportAction, reportID) ? getReport(report?.parentReportID) : report; + const linkedReport = isThreadFirstChat(reportAction, reportID) ? getReportOrDraftReport(report?.parentReportID) : report; if (isActionOwner) { if (!isEmptyObject(linkedReport) && isMoneyRequestReport(linkedReport)) { return canAddOrDeleteTransactions(linkedReport); @@ -1903,7 +1917,7 @@ function getGroupChatName(participantAccountIDs?: number[], shouldApplyLimit = f } function getParticipants(reportID: string) { - const report = getReport(reportID); + const report = getReportOrDraftReport(reportID); if (!report) { return {}; } @@ -2064,7 +2078,7 @@ function getIcons( } if (isInvoiceReport(report)) { - const invoiceRoomReport = getReport(report.chatReportID); + const invoiceRoomReport = getReportOrDraftReport(report.chatReportID); const icons = [getWorkspaceIcon(invoiceRoomReport, policy)]; if (invoiceRoomReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { @@ -2161,7 +2175,8 @@ function getDeletedParentActionMessageForChatReport(reportAction: OnyxEntry, report: OnyxEntry, shouldUseShortDisplayName = true): string { +function getReimbursementQueuedActionMessage(reportAction: OnyxEntry, reportOrID: OnyxEntry | string, shouldUseShortDisplayName = true): string { + const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID; const submitterDisplayName = getDisplayNameForParticipant(report?.ownerAccountID, shouldUseShortDisplayName) ?? ''; const originalMessage = reportAction?.originalMessage as IOUMessage | undefined; let messageKey: TranslationPaths; @@ -2179,9 +2194,10 @@ function getReimbursementQueuedActionMessage(reportAction: OnyxEntry, - report: OnyxEntry | EmptyObject, + reportOrID: OnyxEntry | EmptyObject | string, isLHNPreview = false, ): string { + const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID; const originalMessage = reportAction?.originalMessage; const amount = originalMessage?.amount; const currency = originalMessage?.currency; @@ -2240,7 +2256,7 @@ function buildOptimisticCancelPaymentReportAction(expenseReportID: string, amoun */ function getLastVisibleMessage(reportID: string | undefined, actionsToMerge: ReportActions = {}): LastVisibleMessage { - const report = getReport(reportID); + const report = getReportOrDraftReport(reportID); const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(reportID ?? '-1', actionsToMerge); // For Chat Report with deleted parent actions, let us fetch the correct message @@ -2293,7 +2309,7 @@ function requiresAttentionFromCurrentUser(optionOrReport: OnyxEntry | Op return true; } - if (isArchivedRoom(optionOrReport) || isArchivedRoom(getReport(optionOrReport.parentReportID))) { + if (isArchivedRoom(optionOrReport) || isArchivedRoom(getReportOrDraftReport(optionOrReport.parentReportID))) { return false; } @@ -2565,7 +2581,7 @@ function getTransactionDetails(transaction: OnyxInputOrEntry, creat if (!transaction) { return; } - const report = getReport(transaction?.reportID); + const report = getReportOrDraftReport(transaction?.reportID); return { created: TransactionUtils.getFormattedCreated(transaction, createdDateFormat), amount: TransactionUtils.getAmount(transaction, !isEmptyObject(report) && isExpenseReport(report)), @@ -2629,7 +2645,7 @@ function canEditMoneyRequest(reportAction: OnyxInputOrEntry): bool return reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK; } - const moneyRequestReport = getReport(String(moneyRequestReportID)); + const moneyRequestReport = getReportOrDraftReport(String(moneyRequestReportID)); const isRequestor = currentUserAccountID === reportAction?.actorAccountID; if (isIOUReport(moneyRequestReport)) { @@ -2736,7 +2752,7 @@ function canHoldUnholdReportAction(reportAction: OnyxInputOrEntry) } const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; - const moneyRequestReport = getReport(String(moneyRequestReportID)); + const moneyRequestReport = getReportOrDraftReport(String(moneyRequestReportID)); if (!moneyRequestReportID || !moneyRequestReport) { return {canHoldRequest: false, canUnholdRequest: false}; @@ -2747,7 +2763,7 @@ function canHoldUnholdReportAction(reportAction: OnyxInputOrEntry) const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); - const parentReport = getReport(String(moneyRequestReport.parentReportID)); + const parentReport = getReportOrDraftReport(String(moneyRequestReport.parentReportID)); const parentReportAction = ReportActionsUtils.getParentReportAction(moneyRequestReport); const isRequestIOU = parentReport?.type === 'iou'; @@ -2777,7 +2793,7 @@ const changeMoneyRequestHoldStatus = (reportAction: OnyxEntry): vo } const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; - const moneyRequestReport = getReport(String(moneyRequestReportID)); + const moneyRequestReport = getReportOrDraftReport(String(moneyRequestReportID)); if (!moneyRequestReportID || !moneyRequestReport) { return; } @@ -2915,7 +2931,7 @@ function getTransactionReportName(reportAction: OnyxEntry | EmptyObject, + reportOrID: OnyxInputOrEntry | EmptyObject | string, iouReportAction: OnyxInputOrEntry | EmptyObject = {}, shouldConsiderScanningReceiptOrPendingRoute = false, isPreviewMessageForParentChatReport = false, @@ -2923,6 +2939,7 @@ function getReportPreviewMessage( isForListPreview = false, originalReportAction: OnyxInputOrEntry | EmptyObject = iouReportAction, ): string { + const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID; const reportActionMessage = iouReportAction?.message?.[0]?.html ?? ''; if (isEmptyObject(report) || !report?.reportID) { @@ -3228,7 +3245,7 @@ function parseReportActionHtmlToText(reportAction: OnyxEntry, repo for (const match of matches) { if (match[1] !== childReportID) { // eslint-disable-next-line @typescript-eslint/no-use-before-define - reportIDToName[match[1]] = getReportName(getReport(match[1])) ?? ''; + reportIDToName[match[1]] = getReportName(getReportOrDraftReport(match[1])) ?? ''; } } @@ -3261,7 +3278,7 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, report return ReportActionsUtils.getReportActionMessageText(reportAction); } if (ReportActionsUtils.isReimbursementQueuedAction(reportAction)) { - return getReimbursementQueuedActionMessage(reportAction, getReport(reportID), false); + return getReimbursementQueuedActionMessage(reportAction, getReportOrDraftReport(reportID), false); } return parseReportActionHtmlToText(reportAction, reportID ?? '', childReportID); @@ -3574,7 +3591,7 @@ function addDomainToShortMention(mention: string): string | undefined { function getParsedComment(text: string, parsingDetails?: ParsingDetails): string { let isGroupPolicyReport = false; if (parsingDetails?.reportID) { - const currentReport = getReport(parsingDetails?.reportID); + const currentReport = getReportOrDraftReport(parsingDetails?.reportID); isGroupPolicyReport = isReportInGroupPolicy(currentReport); } @@ -3992,7 +4009,7 @@ function getIOUSubmittedMessage(report: OnyxEntry) { * @param isSettlingUp - Whether we are settling up an IOU */ function getIOUReportActionMessage(iouReportID: string, type: string, total: number, comment: string, currency: string, paymentType = '', isSettlingUp = false): Message[] { - const report = getReport(iouReportID); + const report = getReportOrDraftReport(iouReportID); if (type === CONST.REPORT.ACTIONS.TYPE.SUBMITTED) { return getIOUSubmittedMessage(!isEmptyObject(report) ? report : undefined); @@ -5060,7 +5077,7 @@ function buildTransactionThread( existingTransactionThreadReportID?: string, ): OptimisticChatReport { const participantAccountIDs = [...new Set([currentUserAccountID, Number(reportAction?.actorAccountID)])].filter(Boolean) as number[]; - const existingTransactionThreadReport = getReport(existingTransactionThreadReportID); + const existingTransactionThreadReport = getReportOrDraftReport(existingTransactionThreadReportID); if (existingTransactionThreadReportID && existingTransactionThreadReport) { return { @@ -5242,7 +5259,7 @@ function canAccessReport(report: OnyxEntry, policies: OnyxCollection, currentReportId: string): boolean { - const currentReport = getReport(currentReportId); + const currentReport = getReportOrDraftReport(currentReportId); const parentReport = getParentReport(!isEmptyObject(currentReport) ? currentReport : undefined); const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`] ?? {}; const isChildReportHasComment = Object.values(reportActions ?? {})?.some((reportAction) => (reportAction?.childVisibleActionCount ?? 0) > 0); @@ -5527,6 +5544,12 @@ function chatIncludesChronos(report: OnyxInputOrEntry | EmptyObject): bo return participantAccountIDs.includes(CONST.ACCOUNT_ID.CHRONOS); } +function chatIncludesChronosWithID(reportID?: string): boolean { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID || -1}`]; + return chatIncludesChronos(report); +} + /** * Can only flag if: * @@ -5535,12 +5558,12 @@ function chatIncludesChronos(report: OnyxInputOrEntry | EmptyObject): bo * - It's an ADD_COMMENT that is not an attachment */ function canFlagReportAction(reportAction: OnyxInputOrEntry, reportID: string | undefined): boolean { - let report = getReport(reportID); + let report = getReportOrDraftReport(reportID); // If the childReportID exists in reportAction and is equal to the reportID, // the report action being evaluated is the parent report action in a thread, and we should get the parent report to evaluate instead. if (reportAction?.childReportID?.toString() === reportID?.toString()) { - report = getReport(report?.parentReportID); + report = getReportOrDraftReport(report?.parentReportID); } const isCurrentUserAction = reportAction?.actorAccountID === currentUserAccountID; if (ReportActionsUtils.isWhisperAction(reportAction)) { @@ -5668,13 +5691,6 @@ function getReportIDFromLink(url: string | null): string { return reportID; } -/** - * Get the report policyID given a reportID - */ -function getReportPolicyID(reportID?: string): string | undefined { - return originalGetReportPolicyID(reportID); -} - /** * Check if the chat report is linked to an iou that is waiting for the current user to add a credit bank account. */ @@ -5895,7 +5911,7 @@ function canLeaveRoom(report: OnyxEntry, isPolicyEmployee: boolean): boo return false; } - const invoiceReport = getReport(report?.iouReportID ?? '-1'); + const invoiceReport = getReportOrDraftReport(report?.iouReportID ?? '-1'); if (invoiceReport?.ownerAccountID === currentUserAccountID) { return false; @@ -6023,7 +6039,8 @@ function getAddWorkspaceRoomOrChatReportErrors(report: OnyxEntry): Error /** * Return true if the expense report is marked for deletion. */ -function isMoneyRequestReportPendingDeletion(report: OnyxEntry | EmptyObject): boolean { +function isMoneyRequestReportPendingDeletion(reportOrID: OnyxEntry | EmptyObject | string): boolean { + const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID; if (!isMoneyRequestReport(report)) { return false; } @@ -6275,7 +6292,7 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry, } const originalMessage = reportAction.originalMessage; const {IOUReportID} = originalMessage; - const iouReport = getReport(IOUReportID); + const iouReport = getReportOrDraftReport(IOUReportID); let translationKey: TranslationPaths; if (originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) { // The `REPORT_ACTION_TYPE.PAY` action type is used for both fulfilling existing requests and sending money. To @@ -6555,7 +6572,7 @@ function shouldDisableThread(reportAction: OnyxInputOrEntry, repor const isReportPreviewAction = ReportActionsUtils.isReportPreviewAction(reportAction); const isIOUAction = ReportActionsUtils.isMoneyRequestAction(reportAction); const isWhisperAction = ReportActionsUtils.isWhisperAction(reportAction) || ReportActionsUtils.isActionableTrackExpense(reportAction); - const isArchivedReport = isArchivedRoom(getReport(reportID)); + const isArchivedReport = isArchivedRoom(getReportOrDraftReport(reportID)); const isActionDisabled = CONST.REPORT.ACTIONS.THREAD_DISABLED.some((action: string) => action === reportAction?.actionName); return ( @@ -6580,7 +6597,7 @@ function getAllAncestorReportActions(report: Report | null | undefined): Ancesto let currentReport = report; while (parentReportID) { - const parentReport = getReport(parentReportID); + const parentReport = getReportOrDraftReport(parentReportID); const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '-1'); if (!parentReportAction || ReportActionsUtils.isTransactionThread(parentReportAction) || ReportActionsUtils.isReportPreviewAction(parentReportAction)) { @@ -6624,7 +6641,7 @@ function getAllAncestorReportActionIDs(report: Report | null | undefined, includ let parentReportActionID = report.parentReportActionID; while (parentReportID) { - const parentReport = getReport(parentReportID); + const parentReport = getReportOrDraftReport(parentReportID); const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '-1'); if ( @@ -6655,7 +6672,7 @@ function getAllAncestorReportActionIDs(report: Report | null | undefined, includ * @param type The type of action in the child report */ function getOptimisticDataForParentReportAction(reportID: string, lastVisibleActionCreated: string, type: string): Array { - const report = getReport(reportID); + const report = getReportOrDraftReport(reportID); if (!report || isEmptyObject(report)) { return []; @@ -6665,7 +6682,7 @@ function getOptimisticDataForParentReportAction(reportID: string, lastVisibleAct const totalAncestor = ancestors.reportIDs.length; return Array.from(Array(totalAncestor), (_, index) => { - const ancestorReport = getReport(ancestors.reportIDs[index]); + const ancestorReport = getReportOrDraftReport(ancestors.reportIDs[index]); if (!ancestorReport || isEmptyObject(ancestorReport)) { return {} as EmptyObject; @@ -6769,7 +6786,7 @@ function getTripTransactions(tripRoomReportID: string | undefined, reportFieldTo } function getTripIDFromTransactionParentReport(transactionParentReport: OnyxEntry | undefined | null): string | undefined { - return getReport(transactionParentReport?.parentReportID)?.tripData?.tripID; + return getReportOrDraftReport(transactionParentReport?.parentReportID)?.tripData?.tripID; } /** @@ -7026,6 +7043,7 @@ export { canShowReportRecipientLocalTime, canUserPerformWriteAction, chatIncludesChronos, + chatIncludesChronosWithID, chatIncludesConcierge, createDraftTransactionAndNavigateToParticipantSelector, doesReportBelongToWorkspace, @@ -7079,7 +7097,6 @@ export { getPolicyType, getReimbursementDeQueuedActionMessage, getReimbursementQueuedActionMessage, - getReport, getReportActionActorAccountID, getReportDescriptionText, getReportFieldKey, @@ -7088,7 +7105,6 @@ export { getReportNotificationPreference, getReportOfflinePendingActionAndErrors, getReportParticipantsTitle, - getReportPolicyID, getReportPreviewMessage, getReportRecipientAccountIDs, getRoom, @@ -7133,6 +7149,7 @@ export { isAllowedToSubmitDraftExpenseReport, isAnnounceRoom, isArchivedRoom, + isArchivedRoomWithID, isClosedReport, isCanceledTaskReport, isChatReport, @@ -7204,6 +7221,7 @@ export { isValidReportIDFromPath, isWaitingForAssigneeToCompleteTask, isInvoiceRoom, + isInvoiceRoomWithID, isInvoiceReport, isOpenInvoiceReport, canWriteInReport, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9d22f2358f1c..59fca7e502c3 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -173,6 +173,13 @@ Onyx.connect({ callback: (value) => (allReports = value ?? null), }); +let allReportsDraft: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_DRAFT, + waitForCollectionCallback: true, + callback: (value) => (allReportsDraft = value), +}); + let allTransactions: NonNullable> = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, @@ -285,6 +292,20 @@ Onyx.connect({ }, }); +/** + * Get the report or draft report given a reportID + */ +function getReportOrDraftReport(reportID: string | undefined): OnyxEntry { + if (!allReports && !allReportsDraft) { + return undefined; + } + + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const draftReport = allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`]; + + return report ?? draftReport; +} + /** * Find the report preview action from given chat report and iou report */ @@ -2308,7 +2329,7 @@ function createDistanceRequest( ) { // If the report is an iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); - const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report?.chatReportID) : report; + const currentChatReport = isMoneyRequestReport ? getReportOrDraftReport(report?.chatReportID) : report; const moneyRequestReportID = isMoneyRequestReport ? report?.reportID : ''; const optimisticReceipt: Receipt = { @@ -3419,7 +3440,7 @@ function requestMoney( ) { // If the report is iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); - const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report?.chatReportID) : report; + const currentChatReport = isMoneyRequestReport ? getReportOrDraftReport(report?.chatReportID) : report; const moneyRequestReportID = isMoneyRequestReport ? report?.reportID : ''; const isMovingTransactionFromTrackExpense = IOUUtils.isMovingTransactionFromTrackExpense(action); @@ -3597,7 +3618,7 @@ function trackExpense( linkedTrackedExpenseReportID?: string, ) { const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); - const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report; + const currentChatReport = isMoneyRequestReport ? getReportOrDraftReport(report.chatReportID) : report; const moneyRequestReportID = isMoneyRequestReport ? report.reportID : ''; const isMovingTransactionFromTrackExpense = IOUUtils.isMovingTransactionFromTrackExpense(action); @@ -6168,7 +6189,7 @@ function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObj const chatReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`] ?? {}; return Object.values(chatReportActions).some((action) => { - const iouReport = ReportUtils.getReport(action.childReportID ?? '-1'); + const iouReport = getReportOrDraftReport(action.childReportID ?? '-1'); const policy = PolicyUtils.getPolicy(iouReport?.policyID); const shouldShowSettlementButton = canIOUBePaid(iouReport, chatReport, policy) || canApproveIOU(iouReport, chatReport, policy); return action.childReportID?.toString() !== excludedIOUReportID && action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && shouldShowSettlementButton; @@ -6184,7 +6205,7 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full } const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(total, expenseReport.currency ?? '', expenseReport.reportID); const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, CONST.REPORT.STATUS_NUM.APPROVED); - const chatReport = ReportUtils.getReport(expenseReport.chatReportID); + const chatReport = getReportOrDraftReport(expenseReport.chatReportID); const optimisticReportActionsData: OnyxUpdate = { onyxMethod: Onyx.METHOD.MERGE, @@ -6310,7 +6331,7 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full function submitReport(expenseReport: OnyxTypes.Report) { const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; - const parentReport = ReportUtils.getReport(expenseReport.parentReportID); + const parentReport = getReportOrDraftReport(expenseReport.parentReportID); const policy = PolicyUtils.getPolicy(expenseReport.policyID); const isCurrentUserManager = currentUserPersonalDetails.accountID === expenseReport.managerID; const isSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy); @@ -6642,7 +6663,7 @@ function replaceReceipt(transactionID: string, file: File, source: string) { */ function setMoneyRequestParticipantsFromReport(transactionID: string, report: OnyxEntry): Participant[] { // If the report is iou or expense report, we should get the chat report to set participant for request money - const chatReport = ReportUtils.isMoneyRequestReport(report) ? ReportUtils.getReport(report?.chatReportID) : report; + const chatReport = ReportUtils.isMoneyRequestReport(report) ? getReportOrDraftReport(report?.chatReportID) : report; const currentUserAccountID = currentUserPersonalDetails.accountID; const shouldAddAsReport = !isEmptyObject(chatReport) && ReportUtils.isSelfDM(chatReport); let participants: Participant[] = []; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index b261bb0ade90..ec2f9119551e 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -478,7 +478,7 @@ function addActions(reportID: string, text = '', file?: FileObject) { lastReadTime: currentTime, }; - const report = ReportUtils.getReport(reportID); + const report = currentReportData?.[reportID]; if (!isEmptyObject(report) && ReportUtils.getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { optimisticReport.notificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; @@ -2595,7 +2595,7 @@ function joinRoom(report: OnyxEntry) { } function leaveGroupChat(reportID: string) { - const report = ReportUtils.getReport(reportID); + const report = currentReportData?.[reportID]; if (!report) { Log.warn('Attempting to leave Group Chat that does not existing locally'); return; diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 00b2b3edafae..4435978075e0 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -4,12 +4,26 @@ import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ReportActions} from '@src/types/onyx/ReportAction'; +import type {Report as OnyxReportType, ReportActions} from '@src/types/onyx'; import type ReportAction from '@src/types/onyx/ReportAction'; import * as Report from './Report'; type IgnoreDirection = 'parent' | 'child'; +let allReportActions: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (value) => (allReportActions = value), +}); + +let allReports: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (value) => (allReports = value), +}); + function clearReportActionErrors(reportID: string, reportAction: ReportAction, keys?: string[]) { const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); @@ -58,18 +72,6 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k }); } -let allReportActions: OnyxCollection; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - waitForCollectionCallback: true, - callback: (actions) => { - if (!actions) { - return; - } - allReportActions = actions; - }, -}); - /** * ignore: `undefined` means we want to check both parent and children report actions @@ -83,7 +85,7 @@ function clearAllRelatedReportActionErrors(reportID: string, reportAction: Repor clearReportActionErrors(reportID, reportAction, keys); - const report = ReportUtils.getReport(reportID); + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; if (report?.parentReportID && report?.parentReportActionID && ignore !== 'parent') { const parentReportAction = ReportActionUtils.getReportAction(report.parentReportID, report.parentReportActionID); const parentErrorKeys = Object.keys(parentReportAction?.errors ?? {}).filter((err) => errorKeys.includes(err)); diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 35eaa8f12432..d3d2deb3b1f1 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1006,7 +1006,7 @@ function canModifyTask(taskReport: OnyxEntry, sessionAccountID return true; } - if (!ReportUtils.canWriteInReport(ReportUtils.getReport(taskReport?.reportID))) { + if (!ReportUtils.canWriteInReport(taskReport)) { return false; } @@ -1014,7 +1014,7 @@ function canModifyTask(taskReport: OnyxEntry, sessionAccountID } function clearTaskErrors(reportID: string) { - const report = ReportUtils.getReport(reportID); + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; // Delete the task preview in the parent report if (report?.pendingFields?.createChat === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { diff --git a/src/libs/getReportPolicyID.ts b/src/libs/getReportPolicyID.ts deleted file mode 100644 index 12124f24fbe7..000000000000 --- a/src/libs/getReportPolicyID.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report} from '@src/types/onyx'; -import type {EmptyObject} from '@src/types/utils/EmptyObject'; - -let allReports: OnyxCollection; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallback: true, - callback: (value) => (allReports = value), -}); - -/** - * Get the report given a reportID - */ -function getReport(reportID: string | undefined): OnyxEntry | EmptyObject { - if (!allReports) { - return {}; - } - - return allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? {}; -} - -/** - * Get the report policyID given a reportID. - * We need to define this method in a separate file to avoid cyclic dependency. - */ -function getReportPolicyID(reportID?: string): string | undefined { - return getReport(reportID)?.policyID; -} - -export default getReportPolicyID; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index ade50c0e2c9b..4967083833bf 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -529,8 +529,7 @@ function ReportScreen({ } if (prevReport.parentReportID) { // Prevent navigation to the IOU/Expense Report if it is pending deletion. - const parentReport = ReportUtils.getReport(prevReport.parentReportID); - if (ReportUtils.isMoneyRequestReportPendingDeletion(parentReport)) { + if (ReportUtils.isMoneyRequestReportPendingDeletion(prevReport.parentReportID)) { return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(prevReport.parentReportID)); diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx index 7144a8fbb354..f1e541d2fa91 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -135,6 +135,8 @@ function BaseReportActionContextMenu({ return reportActions[reportActionID]; }, [reportActions, reportActionID]); + const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, reportAction), [reportID, reportAction]); + const shouldEnableArrowNavigation = !isMini && (isVisible || shouldKeepOpen); let filteredContextMenuActions = ContextMenuActions.filter( (contextAction) => @@ -202,8 +204,6 @@ function BaseReportActionContextMenu({ ); const openOverflowMenu = (event: GestureResponderEvent | MouseEvent, anchorRef: MutableRefObject) => { - const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); - const originalReport = ReportUtils.getReport(originalReportID); showContextMenu( CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, event, @@ -218,8 +218,8 @@ function BaseReportActionContextMenu({ checkIfContextMenuActive?.(); setShouldKeepOpen(false); }, - ReportUtils.isArchivedRoom(originalReport), - ReportUtils.chatIncludesChronos(originalReport), + ReportUtils.isArchivedRoomWithID(originalReportID), + ReportUtils.chatIncludesChronosWithID(originalReportID), undefined, undefined, filteredContextMenuActions, diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index e5c8947646f0..370093e122a2 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -367,8 +367,8 @@ const ContextMenuActions: ContextMenuAction[] = [ if (!isAttachment) { const content = selection || messageHtml; if (isReportPreviewAction) { - const iouReport = ReportUtils.getReport(ReportActionsUtils.getIOUReportIDFromReportActionPreview(reportAction)); - const displayMessage = ReportUtils.getReportPreviewMessage(iouReport, reportAction); + const iouReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(reportAction); + const displayMessage = ReportUtils.getReportPreviewMessage(iouReportID, reportAction); Clipboard.setString(displayMessage); } else if (ReportActionsUtils.isTaskAction(reportAction)) { const displayMessage = TaskUtils.getTaskReportActionMessage(reportAction).text; @@ -378,8 +378,7 @@ const ContextMenuActions: ContextMenuAction[] = [ Clipboard.setString(modifyExpenseMessage); } else if (ReportActionsUtils.isReimbursementDeQueuedAction(reportAction)) { const {expenseReportID} = reportAction.originalMessage; - const expenseReport = ReportUtils.getReport(expenseReportID); - const displayMessage = ReportUtils.getReimbursementDeQueuedActionMessage(reportAction, expenseReport); + const displayMessage = ReportUtils.getReimbursementDeQueuedActionMessage(reportAction, expenseReportID); Clipboard.setString(displayMessage); } else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { const displayMessage = ReportUtils.getIOUReportActionDisplayMessage(reportAction, transaction); @@ -391,7 +390,7 @@ const ContextMenuActions: ContextMenuAction[] = [ const logMessage = ReportActionsUtils.getMemberChangeMessageFragment(reportAction).html ?? ''; setClipboardMessage(logMessage); } else if (ReportActionsUtils.isReimbursementQueuedAction(reportAction)) { - Clipboard.setString(ReportUtils.getReimbursementQueuedActionMessage(reportAction, ReportUtils.getReport(reportID), false)); + Clipboard.setString(ReportUtils.getReimbursementQueuedActionMessage(reportAction, reportID, false)); } else if (ReportActionsUtils.isActionableMentionWhisper(reportAction)) { const mentionWhisperMessage = ReportActionsUtils.getActionableMentionWhisperMessage(reportAction); setClipboardMessage(mentionWhisperMessage); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index fffc8bbbbdd9..b9dda0b5abd9 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -3,7 +3,7 @@ import React, {memo, useCallback, useContext, useEffect, useMemo, useRef, useSta import type {GestureResponderEvent, TextInput} from 'react-native'; import {InteractionManager, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; import {AttachmentContext} from '@components/AttachmentContext'; import Button from '@components/Button'; @@ -200,7 +200,9 @@ function ReportActionItem({ const downloadedPreviews = useRef([]); const prevDraftMessage = usePrevious(draftMessage); const originalReportID = ReportUtils.getOriginalReportID(report.reportID, action); - const originalReport = report.reportID === originalReportID ? report : ReportUtils.getReport(originalReportID); + // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || -1}`); const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; const reportScrollManager = useReportScrollManager(); const isActionableWhisper = @@ -340,8 +342,8 @@ function ReportActionItem({ draftMessage ?? '', () => setIsContextMenuActive(true), toggleContextMenuFromActiveReportAction, - ReportUtils.isArchivedRoom(originalReport), - ReportUtils.chatIncludesChronos(originalReport), + ReportUtils.isArchivedRoomWithID(originalReportID), + ReportUtils.chatIncludesChronosWithID(originalReportID), false, false, [], @@ -349,7 +351,7 @@ function ReportActionItem({ setIsEmojiPickerActive as () => void, ); }, - [draftMessage, action, report.reportID, toggleContextMenuFromActiveReportAction, originalReport, originalReportID], + [draftMessage, action, report.reportID, toggleContextMenuFromActiveReportAction, originalReportID], ); // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. @@ -555,7 +557,7 @@ function ReportActionItem({ ); } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_QUEUED) { - const linkedReport = ReportUtils.isChatThread(report) ? ReportUtils.getReport(report.parentReportID) : report; + const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); const paymentType = action.originalMessage.paymentType ?? ''; @@ -866,7 +868,7 @@ function ReportActionItem({ disabledActions={!ReportUtils.canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []} isVisible={hovered && draftMessage === undefined && !hasErrors} draftMessage={draftMessage} - isChronosReport={ReportUtils.chatIncludesChronos(originalReport)} + isChronosReport={ReportUtils.chatIncludesChronosWithID(originalReportID)} checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} setIsEmojiPickerActive={setIsEmojiPickerActive} /> diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx index a5dcfcf3cc1e..720c2b174164 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -63,6 +63,7 @@ function ReportActionItemParentAction({ }: ReportActionItemParentActionProps) { const styles = useThemeStyles(); const ancestorIDs = useRef(ReportUtils.getAllAncestorReportActionIDs(report)); + const ancestorReports = useRef>>({}); const [allAncestors, setAllAncestors] = useState([]); const {isOffline} = useNetwork(); @@ -73,7 +74,8 @@ function ReportActionItemParentAction({ unsubscribeReports.push( onyxSubscribe({ key: `${ONYXKEYS.COLLECTION.REPORT}${ancestorReportID}`, - callback: () => { + callback: (val) => { + ancestorReports.current[ancestorReportID] = val; setAllAncestors(ReportUtils.getAllAncestorReportActions(report)); }, }), @@ -109,11 +111,11 @@ function ReportActionItemParentAction({ > { const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(ancestor.reportAction, ancestor.reportAction.reportActionID ?? '-1'); // Pop the thread report screen before navigating to the chat report. diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index c537fedfe994..7c6b6f7fa6a2 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -6,7 +6,6 @@ import type {Attachment} from '@components/Attachments/types'; import ComposerFocusManager from '@libs/ComposerFocusManager'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; -import * as ReportUtils from '@libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -17,7 +16,7 @@ function ReportAttachments({route}: ReportAttachmentsProps) { const reportID = route.params.reportID; const type = route.params.type; const accountID = route.params.accountID; - const report = ReportUtils.getReport(reportID); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID || -1}`); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); // In native the imported images sources are of type number. Ref: https://reactnative.dev/docs/image#imagesource diff --git a/src/pages/home/report/SystemChatReportFooterMessage.tsx b/src/pages/home/report/SystemChatReportFooterMessage.tsx index d75158ed2f0c..1c1ff2120674 100644 --- a/src/pages/home/report/SystemChatReportFooterMessage.tsx +++ b/src/pages/home/report/SystemChatReportFooterMessage.tsx @@ -1,5 +1,5 @@ import React, {useMemo} from 'react'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Banner from '@components/Banner'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -8,7 +8,6 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; import Navigation from '@navigation/Navigation'; import * as ReportInstance from '@userActions/Report'; import type {OnboardingPurposeType} from '@src/CONST'; @@ -34,14 +33,16 @@ function SystemChatReportFooterMessage({choice, policies, activePolicyID}: Syste const {translate} = useLocalize(); const styles = useThemeStyles(); - const adminChatReport = useMemo(() => { + const adminChatReportID = useMemo(() => { const adminPolicy = activePolicyID ? PolicyUtils.getPolicy(activePolicyID) : Object.values(policies ?? {}).find((policy) => PolicyUtils.shouldShowPolicy(policy, false) && policy?.role === CONST.POLICY.ROLE.ADMIN && policy?.chatReportIDAdmins); - return ReportUtils.getReport(String(adminPolicy?.chatReportIDAdmins)); + return String(adminPolicy?.chatReportIDAdmins ?? -1); }, [activePolicyID, policies]); + const [adminChatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${adminChatReportID}`); + const content = useMemo(() => { switch (choice) { case CONST.ONBOARDING_CHOICES.MANAGE_TEAM: diff --git a/src/pages/iou/HoldReasonPage.tsx b/src/pages/iou/HoldReasonPage.tsx index ea88c12059a9..0070f5e5be0a 100644 --- a/src/pages/iou/HoldReasonPage.tsx +++ b/src/pages/iou/HoldReasonPage.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import React, {useCallback, useEffect} from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; @@ -45,7 +46,7 @@ function HoldReasonPage({route}: HoldReasonPageProps) { const {transactionID, reportID, backTo} = route.params; - const report = ReportUtils.getReport(reportID); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID || -1}`); // We first check if the report is part of a policy - if not, then it's a personal request (1:1 request) // For personal requests, we need to allow both users to put the request on hold diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index 66591246434d..7fbc8d260f8a 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -94,7 +94,7 @@ function IOURequestStepParticipants({ const firstParticipantReportID = val[0]?.reportID ?? ''; const rateID = DistanceRequestUtils.getCustomUnitRateID(firstParticipantReportID); - const isInvoice = iouType === CONST.IOU.TYPE.INVOICE && ReportUtils.isInvoiceRoom(ReportUtils.getReport(firstParticipantReportID)); + const isInvoice = iouType === CONST.IOU.TYPE.INVOICE && ReportUtils.isInvoiceRoomWithID(firstParticipantReportID); numberOfParticipants.current = val.length; IOU.setMoneyRequestParticipants(transactionID, val); diff --git a/tests/actions/EnforceActionExportRestrictions.ts b/tests/actions/EnforceActionExportRestrictions.ts index 710c37789a2f..bd2a13304078 100644 --- a/tests/actions/EnforceActionExportRestrictions.ts +++ b/tests/actions/EnforceActionExportRestrictions.ts @@ -1,4 +1,5 @@ import * as IOU from '@libs/actions/IOU'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as Policy from '@userActions/Policy/Policy'; import * as Task from '@userActions/Task'; @@ -27,6 +28,11 @@ describe('ReportUtils', () => { // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal expect(ReportUtils.getAllReportActions).toBeUndefined(); }); + + it('does not export getReport', () => { + // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal + expect(ReportUtils.getReportOrDraftReport).toBeUndefined(); + }); }); describe('Policy', () => { @@ -41,6 +47,11 @@ describe('IOU', () => { // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal expect(IOU.getPolicy).toBeUndefined(); }); + + it('does not export getReport', () => { + // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal + expect(IOU.getReportOrDraftReport).toBeUndefined(); + }); }); describe('Task', () => { @@ -49,3 +60,10 @@ describe('Task', () => { expect(Task.getParentReport).toBeUndefined(); }); }); + +describe('OptionsListUtils', () => { + it('does not export getReport', () => { + // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal + expect(OptionsListUtils.getReportOrDraftReport).toBeUndefined(); + }); +});