diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index da879139ed15e..a5060d93a676a 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -111,18 +111,19 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { const participantPersonalDetailListExcludeCurrentUser = Object.values( getPersonalDetailsForAccountIDs(participantAccountIDsExcludeCurrentUser, personalDetails as OnyxInputOrEntry), ); - const welcomeMessage = SidebarUtils.getWelcomeMessage( + const welcomeMessage = SidebarUtils.getWelcomeMessage({ report, policy, invoiceReceiverPolicy, - participantPersonalDetailListExcludeCurrentUser, + participantPersonalDetailList: participantPersonalDetailListExcludeCurrentUser, translate, localeCompare, + conciergeReportID, isReportArchived, reportDetailsLink, shouldShowUsePlusButtonText, additionalText, - ); + }); return ( <> diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 7ba03985c93fe..665fd53ca8362 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -190,6 +190,20 @@ import {getTaskReportActionMessage} from './TaskUtils'; type WelcomeMessage = {phrase1?: string; messageText?: string; messageHtml?: string}; +type WelcomeMessageParams = { + report: OnyxEntry; + policy: OnyxEntry; + invoiceReceiverPolicy: OnyxEntry; + participantPersonalDetailList: PersonalDetails[]; + translate: LocalizedTranslate; + localeCompare: LocaleContextProps['localeCompare']; + conciergeReportID: string | undefined; + isReportArchived?: boolean; + reportDetailsLink?: string; + shouldShowUsePlusButtonText?: boolean; + additionalText?: string; +}; + function compareStringDates(a: string, b: string): 0 | 1 | -1 { if (a < b) { return -1; @@ -1126,8 +1140,16 @@ function getOptionData({ if (!result.alternateText) { result.alternateText = formatReportLastMessageText( - getWelcomeMessage(report, policy, invoiceReceiverPolicy, participantPersonalDetailListExcludeCurrentUser, translate, localeCompare, isReportArchived).messageText ?? - translate('report.noActivityYet'), + getWelcomeMessage({ + report, + policy, + invoiceReceiverPolicy, + participantPersonalDetailList: participantPersonalDetailListExcludeCurrentUser, + translate, + localeCompare, + conciergeReportID, + isReportArchived, + }).messageText ?? translate('report.noActivityYet'), ); } } @@ -1135,9 +1157,17 @@ function getOptionData({ } else { if (!lastMessageText) { lastMessageText = formatReportLastMessageText( - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - getWelcomeMessage(report, policy, invoiceReceiverPolicy, participantPersonalDetailListExcludeCurrentUser, translate, localeCompare, isReportArchived).messageText || - translate('report.noActivityYet'), + getWelcomeMessage({ + report, + policy, + invoiceReceiverPolicy, + participantPersonalDetailList: participantPersonalDetailListExcludeCurrentUser, + translate, + localeCompare, + conciergeReportID, + isReportArchived, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + }).messageText || translate('report.noActivityYet'), ); } if (shouldShowLastActorDisplayName(report, lastActorDetails, lastAction, currentUserAccountID) && !isReportArchived) { @@ -1201,25 +1231,28 @@ function getOptionData({ return result; } -function getWelcomeMessage( - report: OnyxEntry, - policy: OnyxEntry, - invoiceReceiverPolicy: OnyxEntry, - participantPersonalDetailList: PersonalDetails[], - translate: LocalizedTranslate, - localeCompare: LocaleContextProps['localeCompare'], - isReportArchived = false, - reportDetailsLink = '', - shouldShowUsePlusButtonText = false, - additionalText = '', -): WelcomeMessage { +function getWelcomeMessage(params: WelcomeMessageParams): WelcomeMessage { + const { + report, + policy, + invoiceReceiverPolicy, + participantPersonalDetailList, + translate, + localeCompare, + conciergeReportID, + isReportArchived = false, + reportDetailsLink = '', + shouldShowUsePlusButtonText = false, + additionalText = '', + } = params; + const welcomeMessage: WelcomeMessage = {}; if (isChatThread(report) || isTaskReport(report)) { return welcomeMessage; } if (isChatRoom(report)) { - return getRoomWelcomeMessage(translate, report, invoiceReceiverPolicy, isReportArchived, reportDetailsLink); + return getRoomWelcomeMessage(translate, report, invoiceReceiverPolicy, conciergeReportID, isReportArchived, reportDetailsLink); } if (isPolicyExpenseChat(report)) { @@ -1262,7 +1295,7 @@ function getWelcomeMessage( if (shouldShowUsePlusButtonText) { messageHtml += translate('reportActionsView.usePlusButton', {additionalText}); } - if (isConciergeChatReport(report)) { + if (isConciergeChatReport(report, conciergeReportID)) { messageHtml = translate('reportActionsView.askConcierge'); } @@ -1278,13 +1311,14 @@ function getRoomWelcomeMessage( translate: LocalizedTranslate, report: OnyxEntry, invoiceReceiverPolicy: OnyxEntry, + conciergeReportID: string | undefined, isReportArchived = false, reportDetailsLink = '', ): WelcomeMessage { const welcomeMessage: WelcomeMessage = {}; const workspaceName = getPolicyName({report}); // eslint-disable-next-line @typescript-eslint/no-deprecated - const reportName = getReportName({report}); + const reportName = getReportName({report, conciergeReportID}); if (report?.description) { welcomeMessage.messageHtml = getReportDescription(report); diff --git a/src/pages/Debug/Report/DebugReportActions.tsx b/src/pages/Debug/Report/DebugReportActions.tsx index e9da92529d5d9..0d6adfbe74617 100644 --- a/src/pages/Debug/Report/DebugReportActions.tsx +++ b/src/pages/Debug/Report/DebugReportActions.tsx @@ -36,6 +36,7 @@ function DebugReportActions({reportID}: DebugReportActionsProps) { const isReportArchived = useReportIsArchived(reportID); const ifUserCanPerformWriteAction = canUserPerformWriteAction(report, isReportArchived); const [personalDetails] = useMappedPersonalDetails(personalDetailMapper); + const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); const getSortedAllReportActionsSelector = useCallback( (allReportActions: OnyxEntry): ReportAction[] => { @@ -70,8 +71,8 @@ function DebugReportActions({reportID}: DebugReportActionsProps) { if (isCreatedAction(reportAction)) { return formatReportLastMessageText( - SidebarUtils.getWelcomeMessage(report, policy, invoiceReceiverPolicy, participantPersonalDetailList, translate, localeCompare, isReportArchived).messageText ?? - translate('report.noActivityYet'), + SidebarUtils.getWelcomeMessage({report, policy, invoiceReceiverPolicy, participantPersonalDetailList, translate, localeCompare, conciergeReportID, isReportArchived}) + .messageText ?? translate('report.noActivityYet'), ); } @@ -81,7 +82,7 @@ function DebugReportActions({reportID}: DebugReportActionsProps) { return getReportActionMessageText(reportAction); }, - [translate, report, policy, invoiceReceiverPolicy, participantPersonalDetailList, localeCompare, isReportArchived], + [translate, report, policy, invoiceReceiverPolicy, participantPersonalDetailList, localeCompare, conciergeReportID, isReportArchived], ); const searchedReportActions = useMemo(() => { diff --git a/tests/unit/SidebarUtilsTest.ts b/tests/unit/SidebarUtilsTest.ts index 70ae80cb9a3e5..6d91bb62fc8cd 100644 --- a/tests/unit/SidebarUtilsTest.ts +++ b/tests/unit/SidebarUtilsTest.ts @@ -1134,6 +1134,8 @@ describe('SidebarUtils', () => { }); describe('getWelcomeMessage', () => { + const MOCK_CONCIERGE_REPORT_ID = 'concierge-report-id'; + it('do not return pronouns in the welcome message text when it is group chat', async () => { const MOCK_REPORT: Report = { ...LHNTestUtils.getFakeReport(), @@ -1156,7 +1158,15 @@ describe('SidebarUtils', () => { }), ) .then(() => { - const result = SidebarUtils.getWelcomeMessage(MOCK_REPORT, undefined, undefined, participantPersonalDetailList, translateLocal, localeCompare); + const result = SidebarUtils.getWelcomeMessage({ + report: MOCK_REPORT, + policy: undefined, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList, + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + }); expect(result.messageHtml).toContain('This chat is with'); expect(result.messageHtml).toContain(''); expect(result.messageHtml).toContain(''); @@ -1182,7 +1192,15 @@ describe('SidebarUtils', () => { }); }); - const result = SidebarUtils.getWelcomeMessage(MOCK_REPORT, undefined, undefined, participantPersonalDetailList, translateLocal, localeCompare); + const result = SidebarUtils.getWelcomeMessage({ + report: MOCK_REPORT, + policy: undefined, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList, + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + }); expect(result.messageText).toBe('This chat is with Email One.'); expect(result.messageHtml).toContain('Email One'); }); @@ -1205,7 +1223,15 @@ describe('SidebarUtils', () => { }); }); - const result = SidebarUtils.getWelcomeMessage(MOCK_REPORT, undefined, undefined, participantPersonalDetailList, translateLocal, localeCompare); + const result = SidebarUtils.getWelcomeMessage({ + report: MOCK_REPORT, + policy: undefined, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList, + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + }); expect(result.messageText).toMatch(/^This chat is with .+ and .+\.$/); expect(result.messageText).toContain(' and '); expect(result.messageText).not.toContain(' { }); }); - const result = SidebarUtils.getWelcomeMessage(MOCK_REPORT, undefined, undefined, participantPersonalDetailList, translateLocal, localeCompare); + const result = SidebarUtils.getWelcomeMessage({ + report: MOCK_REPORT, + policy: undefined, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList, + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + }); expect(result.messageText).toMatch(/^This chat is with .+, .+, and .+\.$/); expect(result.messageText).toContain(', and '); expect(result.messageText).not.toContain(' { .then(() => { // Simulate how components call getWelcomeMessage() by using the hook useReportIsArchived() to see if the report is archived const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_REPORT?.reportID)); - return SidebarUtils.getWelcomeMessage(MOCK_REPORT, undefined, undefined, participantPersonalDetailList, translateLocal, localeCompare, isReportArchived.current); + return SidebarUtils.getWelcomeMessage({ + report: MOCK_REPORT, + policy: undefined, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList, + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + isReportArchived: isReportArchived.current, + }); }) // Then the welcome message should indicate the report is archived @@ -1295,7 +1338,16 @@ describe('SidebarUtils', () => { .then(() => { // Simulate how components call getWelcomeMessage() by using the hook useReportIsArchived() to see if the report is archived const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_REPORT?.reportID)); - return SidebarUtils.getWelcomeMessage(MOCK_REPORT, undefined, undefined, participantPersonalDetailList, translateLocal, localeCompare, isReportArchived.current); + return SidebarUtils.getWelcomeMessage({ + report: MOCK_REPORT, + policy: undefined, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList, + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + isReportArchived: isReportArchived.current, + }); }) // Then the welcome message should explain the purpose of the room @@ -1328,7 +1380,15 @@ describe('SidebarUtils', () => { }, }; - const result = SidebarUtils.getWelcomeMessage(invoiceRoom, senderPolicy, invoiceReceiverPolicy, [], translateLocal, localeCompare); + const result = SidebarUtils.getWelcomeMessage({ + report: invoiceRoom, + policy: senderPolicy, + invoiceReceiverPolicy, + participantPersonalDetailList: [], + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + }); expect(result.messageText).toContain('Client Corporation'); expect(result.messageText).toContain('Vendor Workspace'); @@ -1356,7 +1416,15 @@ describe('SidebarUtils', () => { }, }; - const result = SidebarUtils.getWelcomeMessage(invoiceRoom, senderPolicy, undefined, [], translateLocal, localeCompare); + const result = SidebarUtils.getWelcomeMessage({ + report: invoiceRoom, + policy: senderPolicy, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList: [], + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + }); // When invoiceReceiverPolicy is undefined (individual payer), it should handle gracefully expect(result.messageText).toBeTruthy(); @@ -1384,7 +1452,15 @@ describe('SidebarUtils', () => { policyName: policy.name, }; - const result = SidebarUtils.getWelcomeMessage(regularRoom, policy, invoiceReceiverPolicy, [], translateLocal, localeCompare); + const result = SidebarUtils.getWelcomeMessage({ + report: regularRoom, + policy, + invoiceReceiverPolicy, + participantPersonalDetailList: [], + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + }); // Should not contain invoice-specific messaging expect(result.messageText).not.toContain('Some Policy'); @@ -1417,16 +1493,17 @@ describe('SidebarUtils', () => { }, }; - const result = SidebarUtils.getWelcomeMessage( - archivedInvoiceRoom, - senderPolicy, + const result = SidebarUtils.getWelcomeMessage({ + report: archivedInvoiceRoom, + policy: senderPolicy, invoiceReceiverPolicy, - [], - translateLocal, + participantPersonalDetailList: [], + translate: translateLocal, localeCompare, - true, // isReportArchived - 'https://example.com/report', - ); + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + isReportArchived: true, + reportDetailsLink: 'https://example.com/report', + }); // Should show archived message expect(result.messageText).toContain('You missed the party'); @@ -1452,19 +1529,126 @@ describe('SidebarUtils', () => { }, }; - const result = SidebarUtils.getWelcomeMessage( - invoiceRoom, - senderPolicy, - undefined, // invoiceReceiverPolicy is undefined - [], - translateLocal, + const result = SidebarUtils.getWelcomeMessage({ + report: invoiceRoom, + policy: senderPolicy, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList: [], + translate: translateLocal, localeCompare, - ); + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + }); // Should still return a message, even if invoiceReceiverPolicy is missing expect(result.messageText).toBeTruthy(); expect(result.messageText).toContain('Sender Workspace'); }); + + it('returns concierge welcome message when report is a concierge chat', async () => { + const conciergeReportID = 'concierge-42'; + const MOCK_REPORT: Report = { + ...LHNTestUtils.getFakeReport(), + reportID: conciergeReportID, + chatType: undefined, + type: 'chat', + }; + const participantPersonalDetailList: PersonalDetails[] = [ + {accountID: 1, displayName: 'Concierge', avatar: 'https://example.com/concierge.png', login: 'concierge@expensify.com'} as unknown as PersonalDetails, + ]; + + await waitForBatchedUpdates(); + await act(async () => { + await Onyx.multiSet({ + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + }); + }); + + // When the report ID matches the conciergeReportID, the welcome message should be the concierge message + const result = SidebarUtils.getWelcomeMessage({ + report: MOCK_REPORT, + policy: undefined, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList, + translate: translateLocal, + localeCompare, + conciergeReportID, + }); + expect(result.messageText).toBe('This is your chat with Concierge, your personal AI agent. I can do almost anything, try me!'); + }); + + it('does not return concierge welcome message when conciergeReportID does not match', async () => { + const MOCK_REPORT: Report = { + ...LHNTestUtils.getFakeReport(), + reportID: 'some-other-report', + chatType: undefined, + type: 'chat', + }; + const participantPersonalDetailList: PersonalDetails[] = [ + {accountID: 1, displayName: 'Email One', avatar: 'https://example.com/one.png', login: 'email1@test.com'} as unknown as PersonalDetails, + ]; + + await waitForBatchedUpdates(); + await act(async () => { + await Onyx.multiSet({ + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + }); + }); + + // When the report ID does NOT match the conciergeReportID, the welcome message should be the normal DM message + const result = SidebarUtils.getWelcomeMessage({ + report: MOCK_REPORT, + policy: undefined, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList, + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + }); + expect(result.messageText).toBe('This chat is with Email One.'); + expect(result.messageText).not.toContain('Concierge'); + }); + + it('returns empty welcome message for chat thread even with conciergeReportID', () => { + const MOCK_REPORT: Report = { + ...LHNTestUtils.getFakeReport(), + chatType: undefined, + type: 'chat', + parentReportID: 'parent-123', + parentReportActionID: 'action-456', + }; + + const result = SidebarUtils.getWelcomeMessage({ + report: MOCK_REPORT, + policy: undefined, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList: [], + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + }); + expect(result.messageHtml).toBeUndefined(); + expect(result.messageText).toBeUndefined(); + }); + + it('returns selfDM welcome message regardless of conciergeReportID', () => { + const MOCK_REPORT: Report = { + ...LHNTestUtils.getFakeReport(), + chatType: CONST.REPORT.CHAT_TYPE.SELF_DM, + type: 'chat', + }; + + const result = SidebarUtils.getWelcomeMessage({ + report: MOCK_REPORT, + policy: undefined, + invoiceReceiverPolicy: undefined, + participantPersonalDetailList: [], + translate: translateLocal, + localeCompare, + conciergeReportID: MOCK_CONCIERGE_REPORT_ID, + }); + expect(result.messageText).toBeTruthy(); + expect(result.messageText).not.toContain('Concierge'); + }); }); describe('getOptionData', () => {