diff --git a/src/libs/ReportNameUtils.ts b/src/libs/ReportNameUtils.ts index a9bd8efedd28..d70e91bd64c8 100644 --- a/src/libs/ReportNameUtils.ts +++ b/src/libs/ReportNameUtils.ts @@ -961,6 +961,7 @@ function computeChatThreadReportName( reports: OnyxCollection, currentUserLogin: string, transactions: OnyxCollection, + conciergeReportID: string | undefined, parentReportAction?: ReportAction, policyTags?: OnyxEntry, policy?: OnyxEntry, @@ -1000,9 +1001,6 @@ function computeChatThreadReportName( const isAttachment = isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : undefined); const reportActionMessage = getReportActionText(parentReportAction).replaceAll(/(\n+|\r\n|\n|\r)/gm, ' '); - if (isAttachment && reportActionMessage) { - return `[${translate('common.attachment')}]`; - } if ( parentReportActionMessage?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || parentReportActionMessage?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_HIDDEN || @@ -1010,6 +1008,18 @@ function computeChatThreadReportName( ) { return translate('parentReportAction.hiddenMessage'); } + + // Concierge titles each of its threads with a summary of the question, so prefer that over the question itself. + if ( + report.reportName && + report.reportName !== CONST.REPORT.DEFAULT_REPORT_NAME && + isConciergeChatReport(reports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`], conciergeReportID) + ) { + return report.reportName; + } + if (isAttachment && reportActionMessage) { + return `[${translate('common.attachment')}]`; + } if (isAdminRoom(report) || isUserCreatedPolicyRoom(report)) { return reportActionMessage; } @@ -1135,6 +1145,7 @@ function computeReportName({ reports ?? {}, currentUserLogin ?? '', transactions, + conciergeReportID, parentReportAction, policyTags, reportPolicy, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2680cbadcdf6..b73fae4b4e89 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -169,6 +169,7 @@ import { getDisplayNameForParticipant, getDisplayNamesWithTooltips, getIcons, + getIconsForParticipants, getMovedTransactionMessage, getParticipantsAccountIDsForDisplay, getPendingDeleteMemberAccountIDs, @@ -1437,19 +1438,23 @@ function getOptionData({ result.subtitle = subtitle; result.participantsList = participantPersonalDetailList; - const reportIcons = getIcons( - report, - formatPhoneNumber, - translate, - personalDetails, - personalDetail?.avatar, - personalDetail?.login, - personalDetail?.accountID ?? CONST.DEFAULT_NUMBER_ID, - policy, - invoiceReceiverPolicy, - isReportArchived, - getPendingDeleteMemberAccountIDs(reportMetadata?.pendingChatMembers), - ); + // A Concierge thread is a conversation with Concierge, so the LHN shows Concierge rather than whoever asked. + const reportIcons = + isChatThread(report) && report.parentReportID === conciergeReportID + ? getIconsForParticipants([CONST.ACCOUNT_ID.CONCIERGE], personalDetails) + : getIcons( + report, + formatPhoneNumber, + translate, + personalDetails, + personalDetail?.avatar, + personalDetail?.login, + personalDetail?.accountID ?? CONST.DEFAULT_NUMBER_ID, + policy, + invoiceReceiverPolicy, + isReportArchived, + getPendingDeleteMemberAccountIDs(reportMetadata?.pendingChatMembers), + ); // IOU icon trimming (single vs diagonal) is handled at the component level // using useReportPreviewSenderID which has access to transaction attendee data. diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index ffecf1fb648c..f3487b72568d 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -449,6 +449,7 @@ type AddAttachmentWithCommentParams = { delegateAccountID: number | undefined; sidePanelContext?: SidePanelContext; conciergeReportID: string | undefined; + conciergeThreadReportID?: string; }; type MergeReportsProps = { @@ -1163,7 +1164,6 @@ function addActions({ const optimisticThread = buildOptimisticChatReport({ participantList: [currentUserAccountID, CONST.ACCOUNT_ID.CONCIERGE], - reportName: reportCommentText, parentReportActionID: resolvedReportActionID, parentReportID: reportID, optimisticReportID: conciergeThreadReportID, @@ -1289,6 +1289,7 @@ function addAttachmentWithComment({ delegateAccountID, sidePanelContext, conciergeReportID, + conciergeThreadReportID, }: AddAttachmentWithCommentParams) { if (!report?.reportID) { return; @@ -1315,13 +1316,26 @@ function addAttachmentWithComment({ delegateAccountID, sidePanelContext, conciergeReportID, + conciergeThreadReportID, }); handlePlaySound(); return; } // Multiple attachments - first: combine text + first attachment as a single action - addActions({report, notifyReportID, ancestors, timezoneParam: timezone, currentUserAccountID, text, file: attachments?.at(0), isInSidePanel, delegateAccountID, conciergeReportID}); + addActions({ + report, + notifyReportID, + ancestors, + timezoneParam: timezone, + currentUserAccountID, + text, + file: attachments?.at(0), + isInSidePanel, + delegateAccountID, + conciergeReportID, + conciergeThreadReportID: attachments.length === 1 ? conciergeThreadReportID : undefined, + }); // Remaining: attachment-only actions (no text duplication) for (let i = 1; i < attachments?.length; i += 1) { diff --git a/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts b/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts index b3c2c6533a5d..a1062184e048 100644 --- a/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts +++ b/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts @@ -88,7 +88,12 @@ function useComposerSubmit(reportID: string) { clearAgentZeroProcessingIndicator(reportID, CONST.ACCOUNT_ID.CONCIERGE); } + // Concierge answers each question in its own thread. The side panel renders its own pinned report, + // so it stays in the DM rather than being sent to a thread it cannot show. + const shouldRespondInThread = reportID === conciergeReportID && !isInSidePanel && isBetaEnabled(CONST.BETAS.CONCIERGE_RESPOND_IN_THREAD); + if (attachmentFileRef.current) { + const attachmentCount = Array.isArray(attachmentFileRef.current) ? attachmentFileRef.current.length : 1; addAttachmentWithComment({ report: targetReport, notifyReportID: reportID, @@ -102,6 +107,9 @@ function useComposerSubmit(reportID: string) { delegateAccountID, sidePanelContext, conciergeReportID, + + // A send with several attachments posts one message per attachment, so it stays in the DM. + conciergeThreadReportID: shouldRespondInThread && attachmentCount === 1 ? generateReportID() : undefined, }); attachmentFileRef.current = null; return; @@ -197,10 +205,7 @@ function useComposerSubmit(reportID: string) { reportActionID: optimisticReportActionID, delegateAccountID, conciergeReportID, - - // Concierge answers each question in its own thread. The side panel renders its own pinned report, - // so it stays in the DM rather than being sent to a thread it cannot show. - conciergeThreadReportID: reportID === conciergeReportID && !isInSidePanel && isBetaEnabled(CONST.BETAS.CONCIERGE_RESPOND_IN_THREAD) ? generateReportID() : undefined, + conciergeThreadReportID: shouldRespondInThread ? generateReportID() : undefined, }); }; diff --git a/tests/unit/ReportNameUtilsTest.ts b/tests/unit/ReportNameUtilsTest.ts index e61bdd146f94..8fea617a33b1 100644 --- a/tests/unit/ReportNameUtilsTest.ts +++ b/tests/unit/ReportNameUtilsTest.ts @@ -404,6 +404,56 @@ describe('ReportNameUtils', () => { }); }); + describe('computeReportName - Concierge threads', () => { + const conciergeReportID = '777'; + const parentReportActionID = '888'; + const question = 'How do I set up QuickBooks?'; + + const computeConciergeThreadName = (threadReportName: string) => { + const conciergeDM = {...createRegularChat(90, [currentUserAccountID, CONST.ACCOUNT_ID.CONCIERGE]), reportID: conciergeReportID}; + const thread: Report = { + ...createRegularChat(91, [currentUserAccountID, CONST.ACCOUNT_ID.CONCIERGE]), + reportName: threadReportName, + parentReportID: conciergeReportID, + parentReportActionID, + }; + const parentAction = createMock({ + actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, + reportActionID: parentReportActionID, + message: [{type: 'COMMENT', html: question, text: question}], + created: '', + lastModified: '', + actorAccountID: currentUserAccountID, + person: [], + }); + + return computeReportNameOriginal({ + dateFnsLocale: undefined, + conciergeReportID, + report: thread, + reports: {[`${ONYXKEYS.COLLECTION.REPORT}${conciergeReportID}`]: conciergeDM}, + policies: emptyCollections.policies, + transactions: undefined, + allReportNameValuePairs: undefined, + personalDetailsList: participantsPersonalDetails, + reportActions: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${conciergeReportID}`]: {[parentReportActionID]: parentAction}}, + currentUserAccountID, + currentUserLogin, + reportTransactions: buildTransactionsByReportID(undefined), + translate: translateLocal, + isTrackIntentUser: false, + }); + }; + + test('uses the generated title once Concierge has titled the thread', () => { + expect(computeConciergeThreadName('QuickBooks setup')).toBe('QuickBooks setup'); + }); + + test('falls back to the question while the thread still has the default name', () => { + expect(computeConciergeThreadName(CONST.REPORT.DEFAULT_REPORT_NAME)).toBe(question); + }); + }); + describe('computeReportName - Thread report action names', () => { test('Submitted parent action', () => { const thread: Report = createWorkspaceThread(50);