diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 758f9b21335e..ab5ae68d523c 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -103,11 +103,12 @@ function SettlementButton({ // 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. // eslint-disable-next-line rulesdir/no-default-id-values const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || CONST.DEFAULT_NUMBER_ID}`, {canBeMissing: true}); + const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID, {canBeMissing: true}); const [iouReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport?.reportID}`, {canBeMissing: true}); const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isUserValidatedSelector, canBeMissing: true}); const policyEmployeeAccountIDs = getPolicyEmployeeAccountIDs(policy, accountID); - const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(chatReport, policyEmployeeAccountIDs, policyID) : false; + const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(chatReport, policyEmployeeAccountIDs, policyID, conciergeReportID) : false; const policyIDKey = reportBelongsToWorkspace ? policyID : (iouReport?.policyID ?? CONST.POLICY.ID_FAKE); const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true}); const hasActivatedWallet = ([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM] as string[]).includes(userWallet?.tierName ?? ''); diff --git a/src/components/SidePanel/HelpComponents/HelpContent.tsx b/src/components/SidePanel/HelpComponents/HelpContent.tsx index ddcfb78ebb94..2d83cadef093 100644 --- a/src/components/SidePanel/HelpComponents/HelpContent.tsx +++ b/src/components/SidePanel/HelpComponents/HelpContent.tsx @@ -53,6 +53,7 @@ function HelpContent({closeSidePanel}: HelpContentProps) { }); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${params?.reportID || String(CONST.DEFAULT_NUMBER_ID)}`, {canBeMissing: true}); + const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID, {canBeMissing: true}); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`, {canBeMissing: true}); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`, {canBeMissing: true}); @@ -89,7 +90,7 @@ function HelpContent({closeSidePanel}: HelpContentProps) { const cleanedPath = path.replaceAll('?', ''); const expenseType = getExpenseType(transaction); - const reportType = getHelpPaneReportType(report); + const reportType = getHelpPaneReportType(report, conciergeReportID); if (expenseType && reportType !== CONST.REPORT.HELP_TYPE.EXPENSE_REPORT) { return cleanedPath.replaceAll(':reportID', `:${CONST.REPORT.HELP_TYPE.EXPENSE}/:${expenseType}`); @@ -100,7 +101,7 @@ function HelpContent({closeSidePanel}: HelpContentProps) { } return cleanedPath; - }, [routeName, transaction, report]); + }, [routeName, transaction, report, conciergeReportID]); const wasPreviousNarrowScreen = useRef(!isExtraLargeScreenWidth); useEffect(() => { diff --git a/src/hooks/usePaymentOptions.ts b/src/hooks/usePaymentOptions.ts index c260240d767d..a397cf284bcc 100644 --- a/src/hooks/usePaymentOptions.ts +++ b/src/hooks/usePaymentOptions.ts @@ -68,10 +68,11 @@ function usePaymentOptions({ // 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. // eslint-disable-next-line rulesdir/no-default-id-values const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || CONST.DEFAULT_NUMBER_ID}`, {canBeMissing: true}); + const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID, {canBeMissing: true}); const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true}); const hasActivatedWallet = ([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM] as string[]).includes(userWallet?.tierName ?? ''); const policyEmployeeAccountIDs = getPolicyEmployeeAccountIDs(policy, accountID); - const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(chatReport, policyEmployeeAccountIDs, policyID) : false; + const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(chatReport, policyEmployeeAccountIDs, policyID, conciergeReportID) : false; const policyIDKey = reportBelongsToWorkspace ? policyID : CONST.POLICY.ID_FAKE; const lastPaymentMethodSelector = useCallback( (paymentMethod: OnyxEntry) => { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2c00bc38705e..2d51e6157675 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1001,11 +1001,11 @@ getEnvironment().then((env) => { // Example case: when we need to get a report name of a thread which is dependent on a report action message. const parsedReportActionMessageCache: Record = {}; -let conciergeReportID: OnyxEntry; +let conciergeReportIDOnyxConnect: OnyxEntry; Onyx.connectWithoutView({ key: ONYXKEYS.CONCIERGE_REPORT_ID, callback: (value) => { - conciergeReportID = value; + conciergeReportIDOnyxConnect = value; }, }); @@ -1882,8 +1882,8 @@ function getReportNotificationPreference(report: OnyxEntry): ValueOf): boolean { - return !!report && report?.reportID === conciergeReportID; +function isConciergeChatReport(report: OnyxInputOrEntry, conciergeReportID?: string): boolean { + return !!report && report?.reportID === (conciergeReportID ?? conciergeReportIDOnyxConnect); } function findSelfDMReportID(): string | undefined { @@ -1909,9 +1909,9 @@ function isPolicyRelatedReport(report: OnyxEntry, policyID?: string) { * Checks if the supplied report belongs to workspace based on the provided params. If the report's policyID is _FAKE_ or has no value, it means this report is a DM. * In this case report and workspace members must be compared to determine whether the report belongs to the workspace. */ -function doesReportBelongToWorkspace(report: OnyxEntry, policyMemberAccountIDs: number[], policyID?: string) { +function doesReportBelongToWorkspace(report: OnyxEntry, policyMemberAccountIDs: number[], policyID: string | undefined, conciergeReportID: string) { return ( - isConciergeChatReport(report) || + isConciergeChatReport(report, conciergeReportID) || (report?.policyID === CONST.POLICY.ID_FAKE || !report?.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : isPolicyRelatedReport(report, policyID)) ); } @@ -1974,7 +1974,7 @@ function shouldEnableNegative(report: OnyxEntry, policy?: OnyxEntry>, policyMemberAccountIDs: number[] = [], policyID?: string) { - return reports.filter((report) => !!report && doesReportBelongToWorkspace(report, policyMemberAccountIDs, policyID)); + return reports.filter((report) => !!report && doesReportBelongToWorkspace(report, policyMemberAccountIDs, policyID, conciergeReportIDOnyxConnect ?? '')); } /** @@ -2574,12 +2574,12 @@ function isMoneyRequestReport(reportOrID: OnyxInputOrEntry | string, rep /** * Determines the Help Panel report type based on the given report. */ -function getHelpPaneReportType(report: OnyxEntry): ValueOf | undefined { +function getHelpPaneReportType(report: OnyxEntry, conciergeReportID: string): ValueOf | undefined { if (!report) { return undefined; } - if (isConciergeChatReport(report)) { + if (isConciergeChatReport(report, conciergeReportID)) { return CONST.REPORT.HELP_TYPE.CHAT_CONCIERGE; } diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 96548c51a32c..a5ef4ec2aa45 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -76,6 +76,7 @@ function SuggestionMention({ suggestionValuesRef.current = suggestionValues; const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false}); + const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID, {canBeMissing: true}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isMentionSuggestionsMenuVisible = !!suggestionValues.suggestedMentions.length && suggestionValues.shouldShowSuggestionMenu; @@ -99,7 +100,7 @@ function SuggestionMention({ ); const weightedPersonalDetails: PersonalDetailsList | SuggestionPersonalDetailsList | undefined = useMemo(() => { const policyEmployeeAccountIDs = getPolicyEmployeeAccountIDs(policy, currentUserPersonalDetails.accountID); - if (!isGroupChat(currentReport) && !doesReportBelongToWorkspace(currentReport, policyEmployeeAccountIDs, policyID)) { + if (!isGroupChat(currentReport) && !doesReportBelongToWorkspace(currentReport, policyEmployeeAccountIDs, policyID, conciergeReportID)) { return personalDetails; } return lodashMapValues(personalDetails, (detail) => @@ -110,7 +111,7 @@ function SuggestionMention({ } : null, ); - }, [policyID, policy, currentReport, personalDetails, getPersonalDetailsWeight, currentUserPersonalDetails.accountID]); + }, [policyID, policy, currentReport, personalDetails, getPersonalDetailsWeight, currentUserPersonalDetails.accountID, conciergeReportID]); const [highlightedMentionIndex, setHighlightedMentionIndex] = useArrowKeyFocusManager({ isActive: isMentionSuggestionsMenuVisible, diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index b14f5486bf31..a518f428cafe 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -54,6 +54,7 @@ import { canSeeDefaultRoom, canUserPerformWriteAction, createDraftTransactionAndNavigateToParticipantSelector, + doesReportBelongToWorkspace, excludeParticipantsForDisplay, findLastAccessedReport, getAllReportActionsErrorsAndReportActionThatRequiresAttention, @@ -65,6 +66,7 @@ import { getDisplayNameForParticipant, getDisplayNamesWithTooltips, getHarvestOriginalReportID, + getHelpPaneReportType, getIconsForParticipants, getIndicatedMissingPaymentMethod, getIOUReportActionDisplayMessage, @@ -11061,6 +11063,224 @@ describe('ReportUtils', () => { }); }); + describe('doesReportBelongToWorkspace', () => { + const policyID = 'test-policy-123'; + const conciergeReportID = 'concierge-report-456'; + + beforeEach(async () => { + await Onyx.clear(); + await waitForBatchedUpdates(); + }); + + it('should return true for concierge chat report when conciergeReportID matches', () => { + const conciergeReport: Report = { + reportID: conciergeReportID, + type: CONST.REPORT.TYPE.CHAT, + }; + + const result = doesReportBelongToWorkspace(conciergeReport, [], policyID, conciergeReportID); + expect(result).toBe(true); + }); + + it('should return false for concierge chat report when conciergeReportID does not match', () => { + const conciergeReport: Report = { + reportID: 'different-report-id', + type: CONST.REPORT.TYPE.CHAT, + policyID: CONST.POLICY.ID_FAKE, + }; + + const result = doesReportBelongToWorkspace(conciergeReport, [], policyID, conciergeReportID); + expect(result).toBe(false); + }); + + it('should return true for policy related report with matching policyID', () => { + const policyReport: Report = { + reportID: 'policy-report-123', + type: CONST.REPORT.TYPE.CHAT, + policyID, + }; + + const result = doesReportBelongToWorkspace(policyReport, [], policyID, conciergeReportID); + expect(result).toBe(true); + }); + + it('should return true for DM report with participant in workspace', () => { + const dmReport: Report = { + reportID: 'dm-report-123', + type: CONST.REPORT.TYPE.CHAT, + policyID: CONST.POLICY.ID_FAKE, + participants: { + 1: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + 2: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + const policyMemberAccountIDs = [1, 2]; + + const result = doesReportBelongToWorkspace(dmReport, policyMemberAccountIDs, policyID, conciergeReportID); + expect(result).toBe(true); + }); + + it('should return false for DM report with no participants in workspace', () => { + const dmReport: Report = { + reportID: 'dm-report-123', + type: CONST.REPORT.TYPE.CHAT, + policyID: CONST.POLICY.ID_FAKE, + participants: { + 3: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + 4: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + const policyMemberAccountIDs = [1, 2]; + + const result = doesReportBelongToWorkspace(dmReport, policyMemberAccountIDs, policyID, conciergeReportID); + expect(result).toBe(false); + }); + + it('should return false for report with no policyID and no matching participants', () => { + const report: Report = { + reportID: 'report-123', + type: CONST.REPORT.TYPE.CHAT, + participants: { + 5: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + const result = doesReportBelongToWorkspace(report, [1, 2], policyID, conciergeReportID); + expect(result).toBe(false); + }); + + it('should return false for invoice report with different policyID in invoiceReceiver', () => { + const invoiceReport: Report = { + reportID: 'invoice-report-123', + type: CONST.REPORT.TYPE.INVOICE, + policyID: 'different-policy', + invoiceReceiver: { + policyID: 'another-different-policy', + type: CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS, + }, + }; + + const result = doesReportBelongToWorkspace(invoiceReport, [], policyID, conciergeReportID); + expect(result).toBe(false); + }); + + it('should return true for invoice report with matching policyID in invoiceReceiver', () => { + const invoiceReport: Report = { + reportID: 'invoice-report-123', + type: CONST.REPORT.TYPE.INVOICE, + policyID: 'different-policy', + invoiceReceiver: { + policyID, + type: CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS, + }, + }; + + const result = doesReportBelongToWorkspace(invoiceReport, [], policyID, conciergeReportID); + expect(result).toBe(true); + }); + }); + + describe('getHelpPaneReportType', () => { + const conciergeReportID = 'concierge-report-456'; + + it('should return undefined for undefined report', () => { + const result = getHelpPaneReportType(undefined, conciergeReportID); + expect(result).toBeUndefined(); + }); + + it('should return CHAT_CONCIERGE for concierge chat report', () => { + const conciergeReport: Report = { + reportID: conciergeReportID, + type: CONST.REPORT.TYPE.CHAT, + }; + + const result = getHelpPaneReportType(conciergeReport, conciergeReportID); + expect(result).toBe(CONST.REPORT.HELP_TYPE.CHAT_CONCIERGE); + }); + + it('should return chatType for report with chatType', () => { + const groupChatReport: Report = { + reportID: 'group-chat-123', + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + }; + + const result = getHelpPaneReportType(groupChatReport, conciergeReportID); + expect(result).toBe(CONST.REPORT.CHAT_TYPE.GROUP); + }); + + it('should return EXPENSE_REPORT for expense report type', () => { + const expenseReport: Report = { + reportID: 'expense-report-123', + type: CONST.REPORT.TYPE.EXPENSE, + }; + + const result = getHelpPaneReportType(expenseReport, conciergeReportID); + expect(result).toBe(CONST.REPORT.HELP_TYPE.EXPENSE_REPORT); + }); + + it('should return CHAT for chat report type without chatType', () => { + const chatReport: Report = { + reportID: 'chat-report-123', + type: CONST.REPORT.TYPE.CHAT, + }; + + const result = getHelpPaneReportType(chatReport, conciergeReportID); + expect(result).toBe(CONST.REPORT.HELP_TYPE.CHAT); + }); + + it('should return IOU for IOU report type', () => { + const iouReport: Report = { + reportID: 'iou-report-123', + type: CONST.REPORT.TYPE.IOU, + }; + + const result = getHelpPaneReportType(iouReport, conciergeReportID); + expect(result).toBe(CONST.REPORT.HELP_TYPE.IOU); + }); + + it('should return INVOICE for invoice report type', () => { + const invoiceReport: Report = { + reportID: 'invoice-report-123', + type: CONST.REPORT.TYPE.INVOICE, + }; + + const result = getHelpPaneReportType(invoiceReport, conciergeReportID); + expect(result).toBe(CONST.REPORT.HELP_TYPE.INVOICE); + }); + + it('should return TASK for task report type', () => { + const taskReport: Report = { + reportID: 'task-report-123', + type: CONST.REPORT.TYPE.TASK, + }; + + const result = getHelpPaneReportType(taskReport, conciergeReportID); + expect(result).toBe(CONST.REPORT.HELP_TYPE.TASK); + }); + + it('should return undefined for unknown report type', () => { + const unknownReport: Report = { + reportID: 'unknown-report-123', + type: 'unknown' as Report['type'], + }; + + const result = getHelpPaneReportType(unknownReport, conciergeReportID); + expect(result).toBeUndefined(); + }); + + it('should not return CHAT_CONCIERGE when conciergeReportID does not match', () => { + const chatReport: Report = { + reportID: 'regular-chat-123', + type: CONST.REPORT.TYPE.CHAT, + }; + + const result = getHelpPaneReportType(chatReport, conciergeReportID); + // This report has type CHAT but is not the concierge report + expect(result).toBe(CONST.REPORT.HELP_TYPE.CHAT); + }); + }); + describe('createDraftTransactionAndNavigateToParticipantSelector', () => { describe('when action is CATEGORIZE', () => { beforeEach(async () => {