Skip to content
Draft
17 changes: 14 additions & 3 deletions src/libs/ReportNameUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@ function computeChatThreadReportName(
reports: OnyxCollection<Report>,
currentUserLogin: string,
transactions: OnyxCollection<Transaction>,
conciergeReportID: string | undefined,
parentReportAction?: ReportAction,
policyTags?: OnyxEntry<PolicyTagLists>,
policy?: OnyxEntry<Policy>,
Expand Down Expand Up @@ -1000,16 +1001,25 @@ 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 ||
parentReportActionMessage?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE
) {
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;
}
Expand Down Expand Up @@ -1135,6 +1145,7 @@ function computeReportName({
reports ?? {},
currentUserLogin ?? '',
transactions,
conciergeReportID,
parentReportAction,
policyTags,
reportPolicy,
Expand Down
31 changes: 18 additions & 13 deletions src/libs/SidebarUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ import {
getDisplayNameForParticipant,
getDisplayNamesWithTooltips,
getIcons,
getIconsForParticipants,
getMovedTransactionMessage,
getParticipantsAccountIDsForDisplay,
getPendingDeleteMemberAccountIDs,
Expand Down Expand Up @@ -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.
Expand Down
18 changes: 16 additions & 2 deletions src/libs/actions/Report/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ type AddAttachmentWithCommentParams = {
delegateAccountID: number | undefined;
sidePanelContext?: SidePanelContext;
conciergeReportID: string | undefined;
conciergeThreadReportID?: string;
};

type MergeReportsProps = {
Expand Down Expand Up @@ -1163,7 +1164,6 @@ function addActions({

const optimisticThread = buildOptimisticChatReport({
participantList: [currentUserAccountID, CONST.ACCOUNT_ID.CONCIERGE],
reportName: reportCommentText,
parentReportActionID: resolvedReportActionID,
parentReportID: reportID,
optimisticReportID: conciergeThreadReportID,
Expand Down Expand Up @@ -1289,6 +1289,7 @@ function addAttachmentWithComment({
delegateAccountID,
sidePanelContext,
conciergeReportID,
conciergeThreadReportID,
}: AddAttachmentWithCommentParams) {
if (!report?.reportID) {
return;
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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,
});
};

Expand Down
50 changes: 50 additions & 0 deletions tests/unit/ReportNameUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReportAction>({
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);
Expand Down
Loading