diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 27c6ebf2daa5..02e92e32bab2 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -233,6 +233,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio policy: itemPolicy, isReportArchived: !!itemReportNameValuePairs?.private_isArchived, policyForMovingExpensesID, + chatReport, }); const shouldShowRBRorGBRTooltip = firstReportIDWithGBRorRBR === reportID; diff --git a/src/components/OptionListContextProvider.tsx b/src/components/OptionListContextProvider.tsx index 3a108beef173..23c9931f67f0 100644 --- a/src/components/OptionListContextProvider.tsx +++ b/src/components/OptionListContextProvider.tsx @@ -215,7 +215,8 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { continue; } - const newReportOption = createOptionFromReport(report, personalDetails, reportAttributes?.reports, {showPersonalDetails: true}); + const chatReport = report.chatReportID ? reports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`] : undefined; + const newReportOption = createOptionFromReport(report, personalDetails, reportAttributes?.reports, {showPersonalDetails: true}, chatReport); const replaceIndex = options.reports.findIndex((option) => option.reportID === report.reportID); newReportOptions.push({ newReportOption, diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index 2d8f438aeb86..920485faaf1a 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -63,7 +63,9 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen const selectedOptions = useMemo(() => { return selectedReportIDs.map((id) => { - const report = getSelectedOptionData(createOptionFromReport({...reports?.[`${ONYXKEYS.COLLECTION.REPORT}${id}`], reportID: id}, personalDetails, reportAttributesDerived)); + const reportData = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${id}`]; + const chatReport = reportData?.chatReportID ? reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportData.chatReportID}`] : undefined; + const report = getSelectedOptionData(createOptionFromReport({...reportData, reportID: id}, personalDetails, reportAttributesDerived, undefined, chatReport)); const isReportArchived = archivedReportsIdSet.has(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`); const alternateText = getAlternateText(report, {}, isReportArchived, {}); return {...report, alternateText}; diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 8f547047cb0e..d639fe4af813 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -156,7 +156,8 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla return undefined; } - const option = createOptionFromReport(report, personalDetails, undefined, {showPersonalDetails: true}); + const chatReport = report.chatReportID ? reports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`] : undefined; + const option = createOptionFromReport(report, personalDetails, undefined, {showPersonalDetails: true}, chatReport); reportForContextualSearch = option; } diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index f675cf270cdc..a410b7f074c7 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -223,15 +223,6 @@ Onyx.connect({ callback: (val) => (allPolicies = val), }); -let allReports: OnyxCollection; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallback: true, - callback: (value) => { - allReports = value; - }, -}); - let allReportNameValuePairs: OnyxCollection; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, @@ -265,8 +256,8 @@ Onyx.connect({ const reportActionsArray = Object.values(reportActions[1] ?? {}); let sortedReportActions = getSortedReportActions(withDEWRoutedActionsArray(reportActionsArray), true); allSortedReportActions[reportID] = sortedReportActions; - const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; - const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`]; + const report = getReportOrDraftReport(reportID); + const chatReport = getReportOrDraftReport(report?.chatReportID); // If the report is a one-transaction report and has , we need to return the combined reportActions so that the LHN can display modifications // to the transaction thread or the report itself @@ -464,12 +455,13 @@ function getAlternateText( lastActorDetails: Partial | null = {}, ) { const report = getReportOrDraftReport(option.reportID); + const chatReport = getReportOrDraftReport(report?.chatReportID); const isAdminRoom = reportUtilsIsAdminRoom(report); const isAnnounceRoom = reportUtilsIsAnnounceRoom(report); const isGroupChat = reportUtilsIsGroupChat(report); const isExpenseThread = isMoneyRequest(report); const formattedLastMessageText = - formatReportLastMessageText(Parser.htmlToText(option.lastMessageText ?? '')) || getLastMessageTextForReport({report, lastActorDetails, isReportArchived}); + formatReportLastMessageText(Parser.htmlToText(option.lastMessageText ?? '')) || getLastMessageTextForReport({report, lastActorDetails, isReportArchived, chatReport}); const reportPrefix = getReportSubtitlePrefix(report); const formattedLastMessageTextWithPrefix = reportPrefix + formattedLastMessageText; @@ -597,6 +589,7 @@ function getLastMessageTextForReport({ policy, isReportArchived = false, policyForMovingExpensesID, + chatReport, }: { report: OnyxEntry; lastActorDetails: Partial | null; @@ -605,6 +598,7 @@ function getLastMessageTextForReport({ policy?: OnyxEntry; isReportArchived?: boolean; policyForMovingExpensesID?: string; + chatReport?: OnyxEntry; }): string { const reportID = report?.reportID; const lastReportAction = reportID ? lastVisibleReportActions[reportID] : undefined; @@ -803,7 +797,6 @@ function getLastMessageTextForReport({ } if (reportID && !lastMessageTextFromReport && lastReportAction) { - const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`]; // If the report is a one-transaction report, get the last message text from combined report actions so the LHN can display modifications to the transaction thread or the report itself const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, allSortedReportActions[reportID]); if (transactionThreadReportID) { @@ -833,6 +826,7 @@ function createOption( report: OnyxInputOrEntry, config?: PreviewConfig, reportAttributesDerived?: ReportAttributesDerivedValue['reports'], + chatReport?: OnyxEntry, ): SearchOptionData { const {showChatPreviewLine = false, forcePolicyNamePreview = false, showPersonalDetails = false, selected, isSelected, isDisabled} = config ?? {}; @@ -907,14 +901,14 @@ function createOption( // If displaying chat preview line is needed, let's overwrite the default alternate text const lastActorDetails = personalDetails?.[report?.lastActorAccountID ?? String(CONST.DEFAULT_NUMBER_ID)] ?? {}; - result.lastMessageText = getLastMessageTextForReport({report, lastActorDetails, isReportArchived: !!result.private_isArchived}); + result.lastMessageText = getLastMessageTextForReport({report, lastActorDetails, isReportArchived: !!result.private_isArchived, chatReport}); result.alternateText = showPersonalDetails && personalDetail?.login ? personalDetail.login : getAlternateText(result, {showChatPreviewLine, forcePolicyNamePreview}, !!result.private_isArchived, lastActorDetails); const personalDetailsForCompute: PersonalDetailsList | undefined = personalDetails ?? undefined; - const computedReportName = computeReportName(report, allReports, allPolicies, undefined, allReportNameValuePairs, personalDetailsForCompute, allReportActions); + const computedReportName = computeReportName(report, undefined, allPolicies, undefined, allReportNameValuePairs, personalDetailsForCompute, allReportActions); reportName = showPersonalDetails ? getDisplayNameForParticipant({accountID: accountIDs.at(0), formatPhoneNumber: formatPhoneNumberPhoneUtils}) || formatPhoneNumberPhoneUtils(personalDetail?.login ?? '') : computedReportName; @@ -1171,6 +1165,7 @@ function processReport( report: OnyxEntry | null, personalDetails: OnyxEntry, reportAttributesDerived?: ReportAttributesDerivedValue['reports'], + chatReport?: OnyxEntry, ): { reportMapEntry?: [number, Report]; // The entry to add to reportMapForAccountIDs if applicable reportOption: SearchOption | null; // The report option to add to allReportOptions if applicable @@ -1194,7 +1189,7 @@ function processReport( reportMapEntry, reportOption: { item: report, - ...createOption(accountIDs, personalDetails, report, undefined, reportAttributesDerived), + ...createOption(accountIDs, personalDetails, report, undefined, reportAttributesDerived, chatReport), }, }; } @@ -1207,7 +1202,8 @@ function createOptionList(personalDetails: OnyxEntry, repor if (reports) { for (const report of Object.values(reports)) { - const {reportMapEntry, reportOption} = processReport(report, personalDetails, reportAttributesDerived); + const chatReport = report?.chatReportID ? reports[`${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`] : undefined; + const {reportMapEntry, reportOption} = processReport(report, personalDetails, reportAttributesDerived, chatReport); if (reportMapEntry) { const [accountID, reportValue] = reportMapEntry; @@ -1220,18 +1216,23 @@ function createOptionList(personalDetails: OnyxEntry, repor } } - const allPersonalDetailsOptions = Object.values(personalDetails ?? {}).map((personalDetail) => ({ - item: personalDetail, - ...createOption( - [personalDetail?.accountID ?? CONST.DEFAULT_NUMBER_ID], - personalDetails, - reportMapForAccountIDs[personalDetail?.accountID ?? CONST.DEFAULT_NUMBER_ID], - { - showPersonalDetails: true, - }, - reportAttributesDerived, - ), - })); + const allPersonalDetailsOptions = Object.values(personalDetails ?? {}).map((personalDetail) => { + const mappedReport = reportMapForAccountIDs[personalDetail?.accountID ?? CONST.DEFAULT_NUMBER_ID]; + const chatReport = mappedReport?.chatReportID && reports ? reports[`${ONYXKEYS.COLLECTION.REPORT}${mappedReport.chatReportID}`] : undefined; + return { + item: personalDetail, + ...createOption( + [personalDetail?.accountID ?? CONST.DEFAULT_NUMBER_ID], + personalDetails, + mappedReport, + { + showPersonalDetails: true, + }, + reportAttributesDerived, + chatReport, + ), + }; + }); span.setAttributes({ personalDetails: allPersonalDetailsOptions.length, @@ -1317,7 +1318,8 @@ function createFilteredOptionList( // Step 5: Process the limited set of reports (performance optimization) const reportOptions: Array> = []; for (const report of limitedReports) { - const {reportMapEntry, reportOption} = processReport(report, personalDetails, reportAttributesDerived); + const chatReport = report?.chatReportID ? reports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`] : undefined; + const {reportMapEntry, reportOption} = processReport(report, personalDetails, reportAttributesDerived, chatReport); if (reportMapEntry) { const [accountID, reportValue] = reportMapEntry; @@ -1344,10 +1346,12 @@ function createFilteredOptionList( const personalDetailsOptions = includeP2P ? Object.values(personalDetails ?? {}).map((personalDetail) => { const accountID = personalDetail?.accountID ?? CONST.DEFAULT_NUMBER_ID; + const mappedReport = reportMapForAccountIDs[accountID]; + const chatReport = mappedReport?.chatReportID ? reports?.[`${ONYXKEYS.COLLECTION.REPORT}${mappedReport.chatReportID}`] : undefined; return { item: personalDetail, - ...createOption([accountID], personalDetails, reportMapForAccountIDs[accountID], {showPersonalDetails: true}, reportAttributesDerived), + ...createOption([accountID], personalDetails, mappedReport, {showPersonalDetails: true}, reportAttributesDerived, chatReport), }; }) : []; @@ -1358,12 +1362,18 @@ function createFilteredOptionList( }; } -function createOptionFromReport(report: Report, personalDetails: OnyxEntry, reportAttributesDerived?: ReportAttributesDerivedValue['reports'], config?: PreviewConfig) { +function createOptionFromReport( + report: Report, + personalDetails: OnyxEntry, + reportAttributesDerived?: ReportAttributesDerivedValue['reports'], + config?: PreviewConfig, + chatReport?: OnyxEntry, +) { const accountIDs = getParticipantsAccountIDsForDisplay(report); return { item: report, - ...createOption(accountIDs, personalDetails, report, config, reportAttributesDerived), + ...createOption(accountIDs, personalDetails, report, config, reportAttributesDerived, chatReport), }; } @@ -1798,7 +1808,7 @@ function getUserToInviteContactOption({ return userToInvite; } -function isValidReport(option: SearchOption, config: IsValidReportsConfig, draftComment: string | undefined): boolean { +function isValidReport(option: SearchOption, config: IsValidReportsConfig, draftComment: string | undefined, chatReport?: OnyxEntry): boolean { const { betas = [], includeMultipleParticipantReports = false, @@ -1819,8 +1829,6 @@ function isValidReport(option: SearchOption, config: IsValidReportsConfi preferredPolicyID, } = config; const topmostReportId = Navigation.getTopmostReportId(); - - const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${option.item.chatReportID}`]; const doesReportHaveViolations = shouldDisplayViolationsRBRInLHN(option.item, transactionViolations); const shouldBeInOptionList = shouldReportBeInOptionList({ @@ -1973,12 +1981,12 @@ function prepareReportOptionsForDisplay(options: Array>, co let isOptionUnread = option.isUnread; if (shouldUnreadBeBold) { - const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`]; + const chatReport = getReportOrDraftReport(report.chatReportID); const oneTransactionThreadReportID = report.type === CONST.REPORT.TYPE.IOU || report.type === CONST.REPORT.TYPE.EXPENSE || report.type === CONST.REPORT.TYPE.INVOICE ? getOneTransactionThreadReportID(report, chatReport, allSortedReportActions[report.reportID]) : undefined; - const oneTransactionThreadReport = oneTransactionThreadReportID ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${oneTransactionThreadReportID}`] : undefined; + const oneTransactionThreadReport = oneTransactionThreadReportID ? getReportOrDraftReport(oneTransactionThreadReportID) : undefined; isOptionUnread = isUnread(report, oneTransactionThreadReport, !!option.private_isArchived) && !!report.lastActorAccountID; } @@ -2154,6 +2162,7 @@ function getValidOptions( } const draftComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`]; + const chatReport = getReportOrDraftReport(report.item.chatReportID); return isValidReport( report, @@ -2164,6 +2173,7 @@ function getValidOptions( loginsToExclude, }, draftComment, + chatReport, ); }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 694842b3ad35..99517cf0a2c1 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -112,6 +112,7 @@ import { // eslint-disable-next-line @typescript-eslint/no-deprecated getReportName, getReportNotificationPreference, + getReportOrDraftReport, getReportParticipantsTitle, getReportSubtitlePrefix, getUnreportedTransactionMessage, @@ -795,7 +796,8 @@ function getOptionData({ const lastActorDisplayName = getLastActorDisplayName(lastActorDetails); let lastMessageTextFromReport = lastMessageTextFromReportProp; if (!lastMessageTextFromReport) { - lastMessageTextFromReport = getLastMessageTextForReport({report, lastActorDetails, movedFromReport, movedToReport, policy, isReportArchived}); + const chatReport = getReportOrDraftReport(report?.chatReportID); + lastMessageTextFromReport = getLastMessageTextForReport({report, lastActorDetails, movedFromReport, movedToReport, policy, isReportArchived, chatReport}); } // We need to remove sms domain in case the last message text has a phone number mention with sms domain. diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index e4dfa68ebdf3..a2ebc9de4144 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -2874,4 +2874,97 @@ describe('OptionsListUtils', () => { expect(searchTerms2.includes(displayName)).toBe(true); }); }); + + describe('getLastMessageTextForReport with chatReport parameter', () => { + it('should work correctly when chatReport is passed', async () => { + const chatReportID = '999'; + + const report: Report = { + ...createRandomReport(0, undefined), + chatReportID, + type: CONST.REPORT.TYPE.EXPENSE, + }; + + const chatReport: Report = { + ...createRandomReport(1, undefined), + reportID: chatReportID, + }; + + // Test that the function works without crashing when chatReport is passed + const result = getLastMessageTextForReport({ + report, + lastActorDetails: null, + isReportArchived: false, + chatReport, + }); + + // The function should return a string (may be empty string) + expect(typeof result).toBe('string'); + }); + + it('should work correctly when chatReport is undefined', async () => { + const report: Report = { + ...createRandomReport(0, undefined), + type: CONST.REPORT.TYPE.CHAT, + }; + + const result = getLastMessageTextForReport({ + report, + lastActorDetails: null, + isReportArchived: false, + chatReport: undefined, + }); + + expect(typeof result).toBe('string'); + }); + }); + + describe('createOption with chatReport parameter', () => { + it('should work correctly when chatReport is passed', async () => { + const reportID = '123'; + const chatReportID = '456'; + + const report: Report = { + ...createRandomReport(0, undefined), + reportID, + chatReportID, + participants: { + 1: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + 2: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + const chatReport: Report = { + ...createRandomReport(1, undefined), + reportID: chatReportID, + }; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, report); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, chatReport); + await waitForBatchedUpdates(); + + const result = createOption([1, 2], PERSONAL_DETAILS, report, undefined, undefined, chatReport); + + expect(result.reportID).toBe(reportID); + expect(typeof result.text).toBe('string'); + }); + + it('should work correctly when chatReport is undefined', async () => { + const report: Report = { + ...createRandomReport(0, undefined), + participants: { + 1: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + 2: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); + await waitForBatchedUpdates(); + + // Should not throw when chatReport is undefined + const result = createOption([1, 2], PERSONAL_DETAILS, report, undefined, undefined, undefined); + + expect(result.reportID).toBe(report.reportID); + }); + }); });