Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/AvatarWithDisplayName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils';
import {getHumanAgentAccountIDFromReportAction, getHumanAgentDisplayName} from '@libs/ReportActionsUtils';
import {getHumanAgentAccountIDFromReportAction, getHumanAgentFirstName} from '@libs/ReportActionsUtils';
import {getReportName} from '@libs/ReportNameUtils';
import type {DisplayNameWithTooltips} from '@libs/ReportUtils';
import {
Expand Down Expand Up @@ -215,7 +215,7 @@ function AvatarWithDisplayName({

const parentReportAction = report?.parentReportActionID ? parentReportActions?.[report.parentReportActionID] : undefined;
const humanAgentAccountID = getHumanAgentAccountIDFromReportAction(parentReportAction);
const humanAgentName = getHumanAgentDisplayName(parentReportAction, personalDetails);
const humanAgentName = getHumanAgentFirstName(parentReportAction, personalDetails);

const parentReportActionActorAccountID = parentReportAction?.actorAccountID;
const actorAccountID = useRef<number | null>(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {addSMSDomainIfPhoneNumber} from '@libs/PhoneNumber';
import {
getDelegateAccountIDFromReportAction,
getHumanAgentAccountIDFromReportAction,
getHumanAgentFirstName,
getOriginalMessage,
getReportAction,
getReportActionActorAccountID,
Expand Down Expand Up @@ -347,11 +348,12 @@ function useReportActionAvatars({
if (humanAgentAccountID && avatarType === CONST.REPORT_ACTION_AVATARS.TYPE.SINGLE) {
const humanAgentDetails = personalDetails?.[humanAgentAccountID];
if (humanAgentDetails) {
const agentFirstName = getHumanAgentFirstName(action, personalDetails);
const agentAvatar: IconType = {
id: humanAgentAccountID,
type: CONST.ICON_TYPE_AVATAR,
source: humanAgentDetails.avatar ?? defaultAvatars.FallbackAvatar,
name: humanAgentDetails.displayName ?? translate('reportAction.humanSupportAgent'),
name: agentFirstName ?? translate('reportAction.humanSupportAgent'),
};
const [conciergeAvatar] = avatars;
avatars = [conciergeAvatar, agentAvatar];
Expand Down
13 changes: 9 additions & 4 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
}

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 95 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand All @@ -104,7 +104,7 @@
});

let allReports: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 107 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -118,7 +118,7 @@
});

let deprecatedCurrentUserAccountID: number | undefined;
Onyx.connect({

Check warning on line 121 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
// When signed out, value is undefined
Expand Down Expand Up @@ -549,13 +549,18 @@
return undefined;
}

function getHumanAgentDisplayName(reportAction: OnyxInputOrEntry<ReportAction>, personalDetails: OnyxEntry<PersonalDetailsList>): string | undefined {
/**
* Returns the human Concierge agent's first name for the "assisted by [Name]" label.
* We intentionally use the first name only (not `displayName`, which is "First Last")
* to keep the label casual and minimize exposed agent identity.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could they still see it when they hover over the avatar? Or its going to be onyx so we are only making it harder for them to find the full identity, or are we going to limit sending the last name in the personal details? Then we could just use the display name if we only going to send the first name

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Neil's AI agent)

On hover: the subscript avatar tooltip also uses first name only now. I updated useReportActionAvatars to pull the name via the same getHumanAgentFirstName helper, so hovering the small agent avatar shows just the first name (or "a human support agent" if firstName is missing/blank). And clicking either avatar opens Concierge's details page, not the agent's, so the last name is never surfaced in the UI.

On Onyx: you're right that Auth sends the agent's full public personal details (firstName, lastName, displayName, avatar, accountID) via humanAgentPersonalDetails on OpenReport/ReconnectApp, so a curious user could still find the last name by poking at Onyx. Neil is fine with that — these are considered public personal details, so we don't think it's critical to strip lastName on the backend. This PR is just about the default UI presentation matching the "assisted by FirstName" design, so we'll keep using the separate firstName field rather than displayName.

*/
function getHumanAgentFirstName(reportAction: OnyxInputOrEntry<ReportAction>, personalDetails: OnyxEntry<PersonalDetailsList>): string | undefined {
const humanAgentAccountID = getHumanAgentAccountIDFromReportAction(reportAction);
if (!humanAgentAccountID) {
return undefined;
}
const displayName = personalDetails?.[humanAgentAccountID]?.displayName;
return displayName?.trim() ? displayName : undefined;
const firstName = personalDetails?.[humanAgentAccountID]?.firstName;
return firstName?.trim() ? firstName : undefined;
}

function isExportIntegrationAction(reportAction: OnyxInputOrEntry<ReportAction>): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION> {
Expand Down Expand Up @@ -4838,7 +4843,7 @@
getUpdatedSharedBudgetNotificationMessage,
getDelegateAccountIDFromReportAction,
getHumanAgentAccountIDFromReportAction,
getHumanAgentDisplayName,
getHumanAgentFirstName,
isPendingHide,
filterOutDeprecatedReportActions,
getActionableCardFraudAlertMessage,
Expand Down
4 changes: 2 additions & 2 deletions src/pages/inbox/HeaderView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import Navigation from '@libs/Navigation/Navigation';
import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils';
import Parser from '@libs/Parser';
import {getHumanAgentAccountIDFromReportAction, getHumanAgentDisplayName} from '@libs/ReportActionsUtils';
import {getHumanAgentAccountIDFromReportAction, getHumanAgentFirstName} from '@libs/ReportActionsUtils';
import {getReportName} from '@libs/ReportNameUtils';
import {
canJoinChat,
Expand Down Expand Up @@ -158,7 +158,7 @@ function HeaderView({onNavigationMenuButtonClicked, reportID}: HeaderViewProps)
const isParentReportHeaderDataArchived = useReportIsArchived(reportHeaderData?.parentReportID);
const parentNavigationSubtitleData = getParentNavigationSubtitle(parentNavigationReport, policy, conciergeReportID, isParentReportHeaderDataArchived);
const humanAgentAccountID = getHumanAgentAccountIDFromReportAction(parentReportAction);
const humanAgentName = getHumanAgentDisplayName(parentReportAction, personalDetails);
const humanAgentName = getHumanAgentFirstName(parentReportAction, personalDetails);
const reportDescription = StringUtils.lineBreaksToSpaces(Parser.htmlToText(getReportDescription(report)));
const policyName = getPolicyName({report, returnEmptyIfNotFound: true});
const policyDescription = StringUtils.lineBreaksToSpaces(getPolicyDescriptionText(policy));
Expand Down
4 changes: 2 additions & 2 deletions src/pages/inbox/report/ReportActionItemSingle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils';
import {
getDelegateAccountIDFromReportAction,
getHumanAgentAccountIDFromReportAction,
getHumanAgentDisplayName,
getHumanAgentFirstName,
getManagerOnVacation,
getOriginalMessage,
getReportActionMessage,
Expand Down Expand Up @@ -100,7 +100,7 @@ function ReportActionItemSingle({
const [primaryAvatar, secondaryAvatar] = avatars;
const delegateAccountID = getDelegateAccountIDFromReportAction(action);
const humanAgentAccountID = getHumanAgentAccountIDFromReportAction(action);
const humanAgentName = getHumanAgentDisplayName(action, personalDetails);
const humanAgentName = getHumanAgentFirstName(action, personalDetails);
const mainAccountID = delegateAccountID ? (reportPreviewSenderID ?? potentialIOUReport?.ownerAccountID ?? action?.childOwnerAccountID) : (details.accountID ?? CONST.DEFAULT_NUMBER_ID);
const mainAccountLogin = mainAccountID ? (personalDetails?.[mainAccountID]?.login ?? details.login) : details.login;
const accountOwnerDetails = getPersonalDetailByEmail(String(mainAccountLogin ?? ''));
Expand Down
25 changes: 13 additions & 12 deletions tests/unit/ReportActionsUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
getCustomTaxNameUpdateMessage,
getForeignCurrencyDefaultTaxUpdateMessage,
getHumanAgentAccountIDFromReportAction,
getHumanAgentDisplayName,
getHumanAgentFirstName,
getInvoiceCompanyNameUpdateMessage,
getInvoiceCompanyWebsiteUpdateMessage,
getOneTransactionThreadReportID,
Expand Down Expand Up @@ -5105,7 +5105,7 @@ describe('ReportActionsUtils', () => {
});
});

describe('getHumanAgentDisplayName', () => {
describe('getHumanAgentFirstName', () => {
const HUMAN_AGENT_ACCOUNT_ID = 54321;

function makeConciergeAction(originalMessageOverrides: Record<string, unknown> = {}): ReportAction {
Expand All @@ -5122,43 +5122,44 @@ describe('ReportActionsUtils', () => {
};
}

function makePersonalDetails(displayName: string | undefined): PersonalDetailsList {
function makePersonalDetails(firstName: string | undefined, displayName = 'Pat Agent'): PersonalDetailsList {
return {
[HUMAN_AGENT_ACCOUNT_ID]: {
accountID: HUMAN_AGENT_ACCOUNT_ID,
firstName,
displayName,
},
};
}

it('returns undefined when there is no human agent on the action', () => {
const action = makeConciergeAction();
expect(getHumanAgentDisplayName(action, makePersonalDetails('Pat Agent'))).toBeUndefined();
expect(getHumanAgentFirstName(action, makePersonalDetails('Pat'))).toBeUndefined();
});

it('returns the agent displayName when available', () => {
it('returns the agent firstName (not the full displayName) when available', () => {
const action = makeConciergeAction({humanAgentAccountID: HUMAN_AGENT_ACCOUNT_ID});
expect(getHumanAgentDisplayName(action, makePersonalDetails('Pat Agent'))).toBe('Pat Agent');
expect(getHumanAgentFirstName(action, makePersonalDetails('Pat', 'Pat Agent'))).toBe('Pat');
});

it('returns undefined when personalDetails has no entry for the agent', () => {
const action = makeConciergeAction({humanAgentAccountID: HUMAN_AGENT_ACCOUNT_ID});
expect(getHumanAgentDisplayName(action, {})).toBeUndefined();
expect(getHumanAgentFirstName(action, {})).toBeUndefined();
});

it('returns undefined when personalDetails are not loaded yet', () => {
const action = makeConciergeAction({humanAgentAccountID: HUMAN_AGENT_ACCOUNT_ID});
expect(getHumanAgentDisplayName(action, undefined)).toBeUndefined();
expect(getHumanAgentFirstName(action, undefined)).toBeUndefined();
});

it('returns undefined when the agent displayName is an empty string so the generic fallback can be used', () => {
it('returns undefined when the agent firstName is an empty string so the generic fallback can be used', () => {
const action = makeConciergeAction({humanAgentAccountID: HUMAN_AGENT_ACCOUNT_ID});
expect(getHumanAgentDisplayName(action, makePersonalDetails(''))).toBeUndefined();
expect(getHumanAgentFirstName(action, makePersonalDetails(''))).toBeUndefined();
});

it('returns undefined when the agent displayName is only whitespace so the generic fallback can be used', () => {
it('returns undefined when the agent firstName is only whitespace so the generic fallback can be used', () => {
const action = makeConciergeAction({humanAgentAccountID: HUMAN_AGENT_ACCOUNT_ID});
expect(getHumanAgentDisplayName(action, makePersonalDetails(' '))).toBeUndefined();
expect(getHumanAgentFirstName(action, makePersonalDetails(' '))).toBeUndefined();
});
});
});
Loading