diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index 62e39edf8d754..57b27b234ebe6 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -107,6 +107,7 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen selectedOptions, chatOptions.recentReports, chatOptions.personalDetails, + privateIsArchivedMap, currentUserAccountID, personalDetails, false, diff --git a/src/components/Search/SearchFiltersParticipantsSelector.tsx b/src/components/Search/SearchFiltersParticipantsSelector.tsx index ef44361298221..9458f6b0cedea 100644 --- a/src/components/Search/SearchFiltersParticipantsSelector.tsx +++ b/src/components/Search/SearchFiltersParticipantsSelector.tsx @@ -6,6 +6,7 @@ import SelectionListWithSections from '@components/SelectionList/SelectionListWi import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useReportAttributes from '@hooks/useReportAttributes'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; @@ -96,6 +97,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate, const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [recentAttendees] = useOnyx(ONYXKEYS.NVP_RECENT_ATTENDEES); + const privateIsArchivedMap = usePrivateIsArchivedMap(); // Transform raw recentAttendees into Option[] format for use with getValidOptions (only for attendee filter) const recentAttendeeLists = useMemo( @@ -203,6 +205,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate, selectedOptions, chatOptions.recentReports, chatOptions.personalDetails, + privateIsArchivedMap, currentUserAccountID, personalDetails, true, @@ -260,7 +263,18 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate, sections: newSections, headerMessage: message, }; - }, [areOptionsInitialized, cleanSearchTerm, selectedOptions, chatOptions, personalDetails, reportAttributesDerived, translate, formatPhoneNumber, currentUserAccountID]); + }, [ + areOptionsInitialized, + cleanSearchTerm, + selectedOptions, + chatOptions, + personalDetails, + reportAttributesDerived, + translate, + formatPhoneNumber, + currentUserAccountID, + privateIsArchivedMap, + ]); const resetChanges = useCallback(() => { setSelectedOptions([]); diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 937dbb0394537..a9fdd6833b75a 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1226,6 +1226,7 @@ function getReportDisplayOption( */ function getPolicyExpenseReportOption( participant: Participant | SearchOptionData, + privateIsArchived: string | undefined, currentUserAccountID: number, personalDetails: OnyxEntry, expenseReport: OnyxEntry, @@ -1248,7 +1249,7 @@ function getPolicyExpenseReportOption( forcePolicyNamePreview: false, }, reportAttributesDerived, - undefined, + privateIsArchived, visibleReportActionsData, ); @@ -2911,6 +2912,7 @@ function formatSectionsFromSearchTerm( selectedOptions: SearchOptionData[], filteredRecentReports: SearchOptionData[], filteredPersonalDetails: SearchOptionData[], + privateIsArchivedMap: Record, currentUserAccountID: number, personalDetails: OnyxEntry = {}, shouldGetOptionDetails = false, @@ -2932,7 +2934,8 @@ function formatSectionsFromSearchTerm( // TODO: This allReports usage is temporary and will be removed once the full Onyx.connect() refactor is complete (https://github.com/Expensify/App/issues/66378) const expenseReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.reportID}`]; const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${expenseReport?.chatReportID}`]; - return getPolicyExpenseReportOption(participant, currentUserAccountID, personalDetails, expenseReport, chatReport, reportAttributesDerived); + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${expenseReport?.reportID}`]; + return getPolicyExpenseReportOption(participant, privateIsArchived, currentUserAccountID, personalDetails, expenseReport, chatReport, reportAttributesDerived); } return getParticipantsOption(participant, personalDetails); }) @@ -2964,7 +2967,8 @@ function formatSectionsFromSearchTerm( // TODO: This allReports usage is temporary and will be removed once the full Onyx.connect() refactor is complete (https://github.com/Expensify/App/issues/66378) const expenseReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.reportID}`]; const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${expenseReport?.chatReportID}`]; - return getPolicyExpenseReportOption(participant, currentUserAccountID, personalDetails, expenseReport, chatReport, reportAttributesDerived); + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${expenseReport?.reportID}`]; + return getPolicyExpenseReportOption(participant, privateIsArchived, currentUserAccountID, personalDetails, expenseReport, chatReport, reportAttributesDerived); } return getParticipantsOption(participant, personalDetails); }) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index ef0ac24085864..6588ef8ea557a 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -22,6 +22,7 @@ import useFilteredOptions from '@hooks/useFilteredOptions'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; +import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useSingleExecution from '@hooks/useSingleExecution'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -242,6 +243,8 @@ function NewChatPage({ref}: NewChatPageProps) { const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false}); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [reportAttributesDerivedFull] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES); + const privateIsArchivedMap = usePrivateIsArchivedMap(); + const reportAttributesDerived = reportAttributesDerivedFull?.reports; const selectionListRef = useRef(null); @@ -275,6 +278,7 @@ function NewChatPage({ref}: NewChatPageProps) { selectedOptions as OptionData[], recentReports, personalDetails, + privateIsArchivedMap, currentUserAccountID, allPersonalDetails, undefined, diff --git a/src/pages/iou/SplitBillDetailsPage.tsx b/src/pages/iou/SplitBillDetailsPage.tsx index 98a68e6248c4b..3b9b88dd92aac 100644 --- a/src/pages/iou/SplitBillDetailsPage.tsx +++ b/src/pages/iou/SplitBillDetailsPage.tsx @@ -1,3 +1,4 @@ +import {privateIsArchivedSelector} from '@selectors/ReportNameValuePairs'; import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -67,13 +68,23 @@ function SplitBillDetailsPage({route, report, reportAction}: SplitBillDetailsPag const reportAttributesDerived = useReportAttributes(); const [betas] = useOnyx(ONYXKEYS.BETAS); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.chatReportID)}`); + const [privateIsArchived] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`, {selector: privateIsArchivedSelector}); + // In case this is workspace split expense, we manually add the workspace as the second participant of the split expense // because we don't save any accountID in the report action's originalMessage other than the payee's accountID let participants: Array; if (isPolicyExpenseChat(report)) { participants = [ getParticipantsOption({accountID: participantAccountIDs.at(0), selected: true, reportID: ''}, personalDetails), - getPolicyExpenseReportOption({...report, selected: true, reportID}, currentUserPersonalDetails.accountID, personalDetails, report, chatReport, reportAttributesDerived), + getPolicyExpenseReportOption( + {...report, selected: true, reportID}, + privateIsArchived, + currentUserPersonalDetails.accountID, + personalDetails, + report, + chatReport, + reportAttributesDerived, + ), ]; } else { participants = participantAccountIDs.map((accountID) => getParticipantsOption({accountID, selected: true, reportID: ''}, personalDetails)); diff --git a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx index b968aa5a1d0cf..56d66f9d53899 100644 --- a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx @@ -11,6 +11,7 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; +import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useReportAttributes from '@hooks/useReportAttributes'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useUserToInviteReports from '@hooks/useUserToInviteReports'; @@ -71,6 +72,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType const currentUserEmail = currentUserPersonalDetails.email ?? ''; const currentUserAccountID = currentUserPersonalDetails.accountID; const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); + const privateIsArchivedMap = usePrivateIsArchivedMap(); useEffect(() => { searchUserInServer(debouncedSearchTerm.trim()); @@ -160,6 +162,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType [], chatOptions.recentReports, chatOptions.personalDetails, + privateIsArchivedMap, currentUserAccountID, personalDetails, true, @@ -191,8 +194,17 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType newSections.push({ data: [chatOptions.userToInvite].map((participant) => { const isPolicyExpenseChat = participant?.isPolicyExpenseChat ?? false; + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${userToInviteExpenseReport?.reportID}`]; return isPolicyExpenseChat - ? getPolicyExpenseReportOption(participant, currentUserAccountID, personalDetails, userToInviteExpenseReport, userToInviteChatReport, reportAttributesDerived) + ? getPolicyExpenseReportOption( + participant, + privateIsArchived, + currentUserAccountID, + personalDetails, + userToInviteExpenseReport, + userToInviteChatReport, + reportAttributesDerived, + ) : getParticipantsOption(participant, personalDetails); }), sectionIndex: 3, @@ -222,6 +234,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType translate, loginList, countryCode, + privateIsArchivedMap, currentUserAccountID, currentUserEmail, ]); diff --git a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx index afb945b554611..750f1419b2dd5 100644 --- a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx @@ -13,6 +13,7 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; +import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useReportAttributes from '@hooks/useReportAttributes'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useSearchSelector from '@hooks/useSearchSelector'; @@ -71,6 +72,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde const reportAttributesDerived = useReportAttributes(); const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); + const privateIsArchivedMap = usePrivateIsArchivedMap(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserEmail = currentUserPersonalDetails.email ?? ''; const currentUserAccountID = currentUserPersonalDetails.accountID; @@ -207,6 +209,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde initialSelectedOptions, orderedAvailableOptions.recentReports, orderedAvailableOptions.personalDetails, + privateIsArchivedMap, currentUserAccountID, personalDetails, true, @@ -252,8 +255,17 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde title: undefined, data: [orderedAvailableOptions.userToInvite].map((participant) => { const isPolicyExpenseChat = participant?.isPolicyExpenseChat ?? false; + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${userToInviteExpenseReport?.reportID}`]; return isPolicyExpenseChat - ? getPolicyExpenseReportOption(participant, currentUserAccountID, personalDetails, userToInviteExpenseReport, userToInviteChatReport, reportAttributesDerived) + ? getPolicyExpenseReportOption( + participant, + privateIsArchived, + currentUserAccountID, + personalDetails, + userToInviteExpenseReport, + userToInviteChatReport, + reportAttributesDerived, + ) : getParticipantsOption(participant, personalDetails); }) as OptionData[], sectionIndex: 3, diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 345738bfb2976..699d0593d0daa 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -23,6 +23,7 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePreferredPolicy from '@hooks/usePreferredPolicy'; +import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useReportAttributes from '@hooks/useReportAttributes'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useSearchSelector from '@hooks/useSearchSelector'; @@ -126,6 +127,7 @@ function MoneyRequestParticipantsSelector({ const reportAttributesDerived = useReportAttributes(); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE); const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); + const privateIsArchivedMap = usePrivateIsArchivedMap(); const [textInputAutoFocus, setTextInputAutoFocus] = useState(!isNative); const selectionListRef = useRef(null); @@ -292,6 +294,7 @@ function MoneyRequestParticipantsSelector({ participants.map((participant) => ({...participant, reportID: participant.reportID})) as OptionData[], [], [], + privateIsArchivedMap, currentUserAccountID, personalDetails, true, @@ -354,8 +357,17 @@ function MoneyRequestParticipantsSelector({ title: undefined, data: [availableOptions.userToInvite].map((participant) => { const isPolicyExpenseChat = participant?.isPolicyExpenseChat ?? false; + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${userToInviteExpenseReport?.reportID}`]; return isPolicyExpenseChat - ? getPolicyExpenseReportOption(participant, currentUserAccountID, personalDetails, userToInviteExpenseReport, userToInviteChatReport, reportAttributesDerived) + ? getPolicyExpenseReportOption( + participant, + privateIsArchived, + currentUserAccountID, + personalDetails, + userToInviteExpenseReport, + userToInviteChatReport, + reportAttributesDerived, + ) : getParticipantsOption(participant, personalDetails); }), sectionIndex: 5, @@ -388,6 +400,7 @@ function MoneyRequestParticipantsSelector({ isPerDiemRequest, showImportContacts, inputHelperText, + privateIsArchivedMap, currentUserAccountID, currentUserEmail, ]); diff --git a/src/selectors/ReportNameValuePairs.ts b/src/selectors/ReportNameValuePairs.ts new file mode 100644 index 0000000000000..155a95e7341b8 --- /dev/null +++ b/src/selectors/ReportNameValuePairs.ts @@ -0,0 +1,10 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import type {ReportNameValuePairs} from '@src/types/onyx'; + +/** + * Selector that extracts only the private_isArchived value from a single ReportNameValuePairs entry + */ +const privateIsArchivedSelector = (reportNameValuePairs: OnyxEntry): string | undefined => reportNameValuePairs?.private_isArchived; + +// eslint-disable-next-line import/prefer-default-export +export {privateIsArchivedSelector}; diff --git a/tests/perf-test/OptionsListUtils.perf-test.ts b/tests/perf-test/OptionsListUtils.perf-test.ts index 61bbf6e0cce63..bbbc05e24636d 100644 --- a/tests/perf-test/OptionsListUtils.perf-test.ts +++ b/tests/perf-test/OptionsListUtils.perf-test.ts @@ -270,6 +270,7 @@ describe('OptionsListUtils', () => { Object.values(selectedOptions), Object.values(filteredRecentReports), Object.values(filteredPersonalDetails), + {}, MOCK_CURRENT_USER_ACCOUNT_ID, mockedPersonalDetails, true, @@ -283,6 +284,6 @@ describe('OptionsListUtils', () => { const mockedPersonalDetails = getMockedPersonalDetails(PERSONAL_DETAILS_COUNT); await waitForBatchedUpdates(); - await measureFunction(() => formatSectionsFromSearchTerm('', Object.values(selectedOptions), [], [], MOCK_CURRENT_USER_ACCOUNT_ID, mockedPersonalDetails, true)); + await measureFunction(() => formatSectionsFromSearchTerm('', Object.values(selectedOptions), [], [], {}, MOCK_CURRENT_USER_ACCOUNT_ID, mockedPersonalDetails, true)); }); }); diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 2f6d40170062b..a2526a02533f1 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -11,7 +11,7 @@ import useReportIsArchived from '@hooks/useReportIsArchived'; import DateUtils from '@libs/DateUtils'; import {translate} from '@libs/Localize'; import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; -import type {OptionList, Options, SearchOption} from '@libs/OptionsListUtils'; +import type {OptionList, Options, SearchOption, SearchOptionData} from '@libs/OptionsListUtils'; import { canCreateOptimisticPersonalDetailOption, createFilteredOptionList, @@ -23,6 +23,7 @@ import { filterSelfDMChat, filterWorkspaceChats, formatMemberForList, + formatSectionsFromSearchTerm, getCurrentUserSearchTerms, getFilteredRecentAttendees, getLastActorDisplayName, @@ -5941,7 +5942,7 @@ describe('OptionsListUtils', () => { selected: true, }; - const option = getPolicyExpenseReportOption(participant, CURRENT_USER_ACCOUNT_ID, testPersonalDetails, report, undefined); + const option = getPolicyExpenseReportOption(participant, undefined, CURRENT_USER_ACCOUNT_ID, testPersonalDetails, report, undefined); expect(option).toBeDefined(); expect(option.text).toBe('Test Workspace Policy'); @@ -6002,7 +6003,7 @@ describe('OptionsListUtils', () => { isPolicyExpenseChat: true, }; - const option = getPolicyExpenseReportOption(participant, CURRENT_USER_ACCOUNT_ID, testPersonalDetails, report, undefined); + const option = getPolicyExpenseReportOption(participant, undefined, CURRENT_USER_ACCOUNT_ID, testPersonalDetails, report, undefined); expect(option).toBeDefined(); expect(option.text).toBe('Team Workspace'); @@ -6046,7 +6047,7 @@ describe('OptionsListUtils', () => { }; // Should not throw when personalDetails is empty - const option = getPolicyExpenseReportOption(participant, CURRENT_USER_ACCOUNT_ID, {}, report, undefined); + const option = getPolicyExpenseReportOption(participant, undefined, CURRENT_USER_ACCOUNT_ID, {}, report, undefined); expect(option).toBeDefined(); expect(option.text).toBe('Workspace Without Details'); @@ -6090,7 +6091,7 @@ describe('OptionsListUtils', () => { }; // Should not throw when personalDetails is undefined - const option = getPolicyExpenseReportOption(participant, CURRENT_USER_ACCOUNT_ID, undefined, report, undefined); + const option = getPolicyExpenseReportOption(participant, undefined, CURRENT_USER_ACCOUNT_ID, undefined, report, undefined); expect(option).toBeDefined(); expect(option.text).toBe('Workspace Undefined Details'); @@ -6142,10 +6143,10 @@ describe('OptionsListUtils', () => { selected: false, }; - const optionSelected = getPolicyExpenseReportOption(participantSelected, CURRENT_USER_ACCOUNT_ID, {}, report, undefined); + const optionSelected = getPolicyExpenseReportOption(participantSelected, undefined, CURRENT_USER_ACCOUNT_ID, {}, report, undefined); // eslint-disable-next-line rulesdir/no-negated-variables - const optionNotSelected = getPolicyExpenseReportOption(participantNotSelected, CURRENT_USER_ACCOUNT_ID, {}, report, undefined); + const optionNotSelected = getPolicyExpenseReportOption(participantNotSelected, undefined, CURRENT_USER_ACCOUNT_ID, {}, report, undefined); expect(optionSelected.isSelected).toBe(true); expect(optionSelected.selected).toBe(true); @@ -6154,6 +6155,341 @@ describe('OptionsListUtils', () => { }); }); + describe('getPolicyExpenseReportOption with privateIsArchived', () => { + it('should set private_isArchived on the option when privateIsArchived is provided', async () => { + const reportID = '301'; + const testPolicyID = 'policy301'; + const ownerAccountID = 2001; + const archivedTimestamp = DateUtils.getDBTime(); + + const report: Report = { + reportID, + reportName: 'Archived Policy Expense', + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + policyID: testPolicyID, + ownerAccountID, + participants: { + [ownerAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + const policy: Policy = { + id: testPolicyID, + name: 'Archived Workspace', + type: CONST.POLICY.TYPE.TEAM, + owner: 'owner@test.com', + role: 'user', + outputCurrency: 'USD', + isPolicyExpenseChatEnabled: true, + }; + + const testPersonalDetails = { + [ownerAccountID]: { + accountID: ownerAccountID, + displayName: 'Archived Owner', + login: 'archivedowner@test.com', + }, + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, report); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${testPolicyID}`, policy); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, testPersonalDetails); + await waitForBatchedUpdates(); + + const participant = { + reportID, + policyID: testPolicyID, + isPolicyExpenseChat: true, + selected: false, + }; + + const option = getPolicyExpenseReportOption(participant, archivedTimestamp, CURRENT_USER_ACCOUNT_ID, testPersonalDetails, report, undefined); + + expect(option).toBeDefined(); + expect(option.private_isArchived).toBe(archivedTimestamp); + expect(option.text).toBe('Archived Workspace'); + }); + + it('should set private_isArchived to undefined when privateIsArchived is not provided', async () => { + const reportID = '302'; + const testPolicyID = 'policy302'; + const ownerAccountID = 2002; + + const report: Report = { + reportID, + reportName: 'Active Policy Expense', + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + policyID: testPolicyID, + ownerAccountID, + participants: { + [ownerAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + const policy: Policy = { + id: testPolicyID, + name: 'Active Workspace', + type: CONST.POLICY.TYPE.TEAM, + owner: 'owner@test.com', + role: 'user', + outputCurrency: 'USD', + isPolicyExpenseChatEnabled: true, + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, report); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${testPolicyID}`, policy); + await waitForBatchedUpdates(); + + const participant = { + reportID, + policyID: testPolicyID, + isPolicyExpenseChat: true, + }; + + const option = getPolicyExpenseReportOption(participant, undefined, CURRENT_USER_ACCOUNT_ID, {}, report, undefined); + + expect(option).toBeDefined(); + expect(option.private_isArchived).toBeUndefined(); + }); + }); + + describe('formatSectionsFromSearchTerm', () => { + const formatTestPolicyID = 'policyFormat1'; + const formatOwnerAccountID = 3001; + const formatMemberAccountID = 3002; + const formatReportID1 = '401'; + const formatReportID2 = '402'; + + const formatPolicy: Policy = { + id: formatTestPolicyID, + name: 'Format Test Workspace', + type: CONST.POLICY.TYPE.TEAM, + owner: 'formatowner@test.com', + role: 'admin', + outputCurrency: 'USD', + isPolicyExpenseChatEnabled: true, + }; + + const formatPersonalDetails = { + [formatOwnerAccountID]: { + accountID: formatOwnerAccountID, + displayName: 'Format Owner', + login: 'formatowner@test.com', + }, + [formatMemberAccountID]: { + accountID: formatMemberAccountID, + displayName: 'Format Member', + login: 'formatmember@test.com', + }, + }; + + beforeAll(async () => { + const report1: Report = { + reportID: formatReportID1, + reportName: 'Archived Format Chat', + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + policyID: formatTestPolicyID, + ownerAccountID: formatOwnerAccountID, + participants: { + [formatOwnerAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + const report2: Report = { + reportID: formatReportID2, + reportName: 'Active Format Chat', + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + policyID: formatTestPolicyID, + ownerAccountID: formatMemberAccountID, + participants: { + [formatMemberAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${formatReportID1}`, report1); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${formatReportID2}`, report2); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${formatTestPolicyID}`, formatPolicy); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, formatPersonalDetails); + await waitForBatchedUpdates(); + }); + + it('should pass privateIsArchived from map to policy expense options when searchTerm is empty and shouldGetOptionDetails is true', () => { + const archivedTimestamp = DateUtils.getDBTime(); + const privateIsArchivedMap: Record = { + [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${formatReportID1}`]: archivedTimestamp, + }; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID1, + isPolicyExpenseChat: true, + selected: true, + text: 'Format Test Workspace', + alternateText: '', + isSelected: true, + } as SearchOptionData, + ]; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.data).toHaveLength(1); + + const option = result.section.data.at(0) as SearchOptionData; + expect(option.private_isArchived).toBe(archivedTimestamp); + }); + + it('should not set private_isArchived when report is not in the archived map', () => { + const privateIsArchivedMap: Record = {}; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID2, + isPolicyExpenseChat: true, + selected: true, + text: 'Format Test Workspace', + alternateText: '', + isSelected: true, + } as SearchOptionData, + ]; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.data).toHaveLength(1); + + const option = result.section.data.at(0) as SearchOptionData; + expect(option.private_isArchived).toBeUndefined(); + }); + + it('should handle mix of archived and non-archived policy expense chats', () => { + const archivedTimestamp = DateUtils.getDBTime(); + const privateIsArchivedMap: Record = { + [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${formatReportID1}`]: archivedTimestamp, + }; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID1, + isPolicyExpenseChat: true, + selected: true, + text: 'Archived Workspace', + alternateText: '', + isSelected: true, + } as SearchOptionData, + { + reportID: formatReportID2, + isPolicyExpenseChat: true, + selected: true, + text: 'Active Workspace', + alternateText: '', + isSelected: true, + } as SearchOptionData, + ]; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.data).toHaveLength(2); + + const archivedOption = result.section.data.at(0) as SearchOptionData; + const activeOption = result.section.data.at(1) as SearchOptionData; + expect(archivedOption.private_isArchived).toBe(archivedTimestamp); + expect(activeOption.private_isArchived).toBeUndefined(); + }); + + it('should not transform options when shouldGetOptionDetails is false', () => { + const archivedTimestamp = DateUtils.getDBTime(); + const privateIsArchivedMap: Record = { + [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${formatReportID1}`]: archivedTimestamp, + }; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID1, + isPolicyExpenseChat: true, + selected: true, + text: 'Format Test Workspace', + alternateText: '', + isSelected: true, + } as SearchOptionData, + ]; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, false); + + expect(result.section.data).toHaveLength(1); + // When shouldGetOptionDetails is false, the original selectedOptions are returned unchanged + expect(result.section.data.at(0)).toBe(selectedOptions.at(0)); + }); + + it('should pass privateIsArchived from map when searchTerm matches and participant is not in filtered lists', () => { + const archivedTimestamp = DateUtils.getDBTime(); + const privateIsArchivedMap: Record = { + [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${formatReportID1}`]: archivedTimestamp, + }; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID1, + accountID: formatOwnerAccountID, + isPolicyExpenseChat: true, + selected: true, + text: 'Format Test Workspace', + alternateText: '', + isSelected: true, + login: 'formatowner@test.com', + displayName: 'Format Owner', + } as SearchOptionData, + ]; + + // Pass empty filtered lists so the selected option is not deduplicated + const result = formatSectionsFromSearchTerm('format', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.data).toHaveLength(1); + + const option = result.section.data.at(0) as SearchOptionData; + expect(option.private_isArchived).toBe(archivedTimestamp); + }); + + it('should handle non-policy expense chat participants without privateIsArchived', () => { + const archivedTimestamp = DateUtils.getDBTime(); + const privateIsArchivedMap: Record = { + [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${formatReportID1}`]: archivedTimestamp, + }; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID1, + accountID: formatOwnerAccountID, + isPolicyExpenseChat: false, + selected: true, + text: 'Format Owner', + alternateText: '', + isSelected: true, + login: 'formatowner@test.com', + displayName: 'Format Owner', + } as SearchOptionData, + ]; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.data).toHaveLength(1); + + // Non-policy expense chats go through getParticipantsOption, not getPolicyExpenseReportOption + // so private_isArchived is not set + const option = result.section.data.at(0); + expect(option).toBeDefined(); + expect((option as SearchOptionData).private_isArchived).toBeUndefined(); + }); + + it('should return empty section when no selectedOptions are provided', () => { + const result = formatSectionsFromSearchTerm('', [], [], [], {}, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.data).toHaveLength(0); + }); + }); + describe('getUserToInviteOption', () => { it('should not return userToInvite for plain text name when shouldAcceptName is false', () => { const result = getUserToInviteOption({ @@ -6458,7 +6794,7 @@ describe('OptionsListUtils', () => { }; // When we call getPolicyExpenseReportOption with report passed directly - const option = getPolicyExpenseReportOption(participant, CURRENT_USER_ACCOUNT_ID, PERSONAL_DETAILS, report, undefined); + const option = getPolicyExpenseReportOption(participant, undefined, CURRENT_USER_ACCOUNT_ID, PERSONAL_DETAILS, report, undefined); // Then the option should be created successfully expect(option).toBeDefined();