From 1313b9db256fa5261a5ae5fa4c99924384c6fd66 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 3 Jun 2026 16:19:03 +0800 Subject: [PATCH 01/10] pass details from useOnyx to several report action messages --- .../Search/FilterComponents/InSelector.tsx | 2 +- .../Search/SearchFiltersChatsSelector.tsx | 6 +- src/libs/OptionsListUtils/index.ts | 23 +++++- src/libs/PersonalDetailsUtils.ts | 28 ++++++- src/libs/ReportActionsUtils.ts | 74 +++++++++++-------- src/libs/SidebarUtils.ts | 8 +- .../report/ContextMenu/ContextMenuActions.tsx | 27 ++++++- .../inbox/report/ReportActionItemMessage.tsx | 28 +------ .../actionContents/ActionContentRouter.tsx | 9 +++ .../actionContents/MemberChangeContent.tsx | 41 ++++++++++ .../actionContents/MentionWhisperContent.tsx | 4 +- .../actionContents/ReimbursedContent.tsx | 7 +- .../RemovedFromApprovalChainContent.tsx | 29 ++++++++ .../actionContents/SimpleMessageContent.tsx | 5 -- src/selectors/PersonalDetails.ts | 10 ++- 15 files changed, 221 insertions(+), 80 deletions(-) create mode 100644 src/pages/inbox/report/actionContents/MemberChangeContent.tsx create mode 100644 src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx diff --git a/src/components/Search/FilterComponents/InSelector.tsx b/src/components/Search/FilterComponents/InSelector.tsx index 83828fd8c075..471aacc9d7fd 100644 --- a/src/components/Search/FilterComponents/InSelector.tsx +++ b/src/components/Search/FilterComponents/InSelector.tsx @@ -71,7 +71,7 @@ function InSelector({value = [], onChange}: InSelectorProps) { const isReportArchived = !!privateIsArchived; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; const reportPolicyTags = policyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(report?.policyID)}`]; - const alternateText = getAlternateText(report, {}, {isReportArchived, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID}); + const alternateText = getAlternateText(report, {}, {isReportArchived, personalDetails, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID}); return {...report, alternateText}; }); diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index 394ef73bb420..6428e89e82ff 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -80,7 +80,11 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen const report = getSelectedOptionData(createOptionFromReport({...reportData, reportID: id}, personalDetails, privateIsArchived, reportPolicy, reportAttributesDerived)); const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; const reportPolicyTags = policyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(report?.policyID)}`]; - const alternateText = getAlternateText(report, {}, {isReportArchived: privateIsArchived, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID}); + const alternateText = getAlternateText( + report, + {}, + {isReportArchived: privateIsArchived, personalDetails, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID}, + ); return {...report, alternateText}; }); diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 38c5b708669d..fd52c35775fe 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -23,7 +23,7 @@ import {getForReportAction} from '@libs/ModifiedExpenseMessage'; import Navigation from '@libs/Navigation/Navigation'; import Parser from '@libs/Parser'; import type {OptionData as PersonalDetailOptionData} from '@libs/PersonalDetailOptionsListUtils/types'; -import {getDisplayNameOrDefault, getPersonalDetailByEmail, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils'; +import {getDisplayNameOrDefault, getLoginByAccountID, getPersonalDetailByEmail, getPersonalDetailsByIDs, getPersonalDetailsListByIDs} from '@libs/PersonalDetailsUtils'; import {addSMSDomainIfPhoneNumber, parsePhoneNumber} from '@libs/PhoneNumber'; import { canSendInvoiceFromWorkspace, @@ -427,6 +427,7 @@ function shouldShowLastActorDisplayName( type GetAlternateTextConfig = { isReportArchived: boolean | undefined; + personalDetails: OnyxEntry; // We'll make it required in the next PR. Ref: https://github.com/Expensify/App/issues/66415 policy?: OnyxEntry; lastActorDetails?: Partial | null; @@ -446,6 +447,7 @@ function getAlternateText( {showChatPreviewLine = false, forcePolicyNamePreview = false}: PreviewConfig, { isReportArchived, + personalDetails, policy, lastActorDetails = {}, visibleReportActionsData = {}, @@ -467,6 +469,7 @@ function getAlternateText( getLastMessageTextForReport({ translate: translateFn, report, + personalDetails, lastActorDetails, policy, isReportArchived, @@ -646,6 +649,7 @@ function getLastActorDisplayNameFromLastVisibleActions( function getLastMessageTextForReport({ translate, report, + personalDetails, lastActorDetails, movedFromReport, movedToReport, @@ -663,6 +667,7 @@ function getLastMessageTextForReport({ }: { translate: LocalizedTranslate; report: OnyxEntry; + personalDetails: OnyxEntry; lastActorDetails: Partial | null; movedFromReport?: OnyxEntry; movedToReport?: OnyxEntry; @@ -779,7 +784,13 @@ function getLastMessageTextForReport({ } else if (isActionOfType(lastReportAction, CONST.REPORT.ACTIONS.TYPE.MARKED_REIMBURSED)) { lastMessageTextFromReport = getMarkedReimbursedMessage(translate, lastReportAction); } else if (isActionOfType(lastReportAction, CONST.REPORT.ACTIONS.TYPE.REIMBURSED)) { - lastMessageTextFromReport = getReimbursedMessage(translate, lastReportAction, report?.ownerAccountID); + lastMessageTextFromReport = getReimbursedMessage( + translate, + lastReportAction, + report?.ownerAccountID, + getLoginByAccountID(report?.ownerAccountID, personalDetails), + getLoginByAccountID(lastReportAction.actorAccountID, personalDetails), + ); } else if (isReportMessageAttachment({text: report?.lastMessageText ?? '', html: report?.lastMessageHtml, type: ''})) { lastMessageTextFromReport = `[${translate('common.attachment')}]`; } else if (isModifiedExpenseAction(lastReportAction)) { @@ -897,7 +908,8 @@ function getLastMessageTextForReport({ } else if (isActionOfType(lastReportAction, CONST.REPORT.ACTIONS.TYPE.UNREPORTED_TRANSACTION)) { lastMessageTextFromReport = Parser.htmlToText(getUnreportedTransactionMessage(translate, lastReportAction, reportAttributesDerived)); } else if (isActionableMentionWhisper(lastReportAction)) { - lastMessageTextFromReport = Parser.htmlToText(getActionableMentionWhisperMessage(translate, lastReportAction)); + const targetAccountIDs = getOriginalMessage(lastReportAction)?.inviteeAccountIDs; + lastMessageTextFromReport = Parser.htmlToText(getActionableMentionWhisperMessage(translate, lastReportAction, getPersonalDetailsListByIDs(targetAccountIDs, personalDetails))); } else if (isActionOfType(lastReportAction, CONST.REPORT.ACTIONS.TYPE.DYNAMIC_EXTERNAL_WORKFLOW_ROUTED)) { lastMessageTextFromReport = getDynamicExternalWorkflowRoutedMessage(lastReportAction, translate); } @@ -1042,7 +1054,7 @@ function getLastMessageTextForReport({ type CreateOptionParams = { accountIDs: number[]; - personalDetails: OnyxInputOrEntry; + personalDetails: OnyxEntry; report: OnyxInputOrEntry; privateIsArchived: boolean | undefined; policy?: OnyxEntry; @@ -1146,6 +1158,7 @@ function createOption({ result.lastMessageText = getLastMessageTextForReport({ translate: translateFn, report, + personalDetails, lastActorDetails, isReportArchived: result.private_isArchived, visibleReportActionsDataParam: visibleReportActionsData, @@ -1161,6 +1174,7 @@ function createOption({ {showChatPreviewLine, forcePolicyNamePreview}, { isReportArchived: !!result.private_isArchived, + personalDetails, policy, lastActorDetails, visibleReportActionsData, @@ -2267,6 +2281,7 @@ function prepareReportOptionsForDisplay( {showChatPreviewLine, forcePolicyNamePreview}, { isReportArchived: !!option.private_isArchived, + personalDetails, policy, lastActorDetails: null, visibleReportActionsData, diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 7bb81d14d409..1d955ed0eee8 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -99,6 +99,10 @@ function getDisplayNameOrDefault( return shouldFallbackToHidden ? hiddenTranslation : ''; } +function getPersonalDetailsByID(accountID: number | undefined, personalDetailsList: OnyxEntry): PersonalDetails | undefined { + return accountID ? (personalDetailsList?.[accountID] ?? undefined) : undefined; +} + /** * Given a list of account IDs (as number) it will return an array of personal details objects. * @param accountIDs - Array of accountIDs @@ -135,7 +139,11 @@ function getPersonalDetailsByIDs({ return result; } -function newGetPersonalDetailsByIDs(accountIDs: number[], personalDetails: OnyxEntry): PersonalDetails[] { +function newGetPersonalDetailsByIDs(accountIDs: number[] | undefined, personalDetails: OnyxEntry): PersonalDetails[] { + if (!accountIDs) { + return []; + } + const result: PersonalDetails[] = []; for (const accountID of accountIDs) { const detail = personalDetails?.[accountID]; @@ -148,6 +156,22 @@ function newGetPersonalDetailsByIDs(accountIDs: number[], personalDetails: OnyxE return result; } +function getPersonalDetailsListByIDs(accountIDs: Array | undefined, personalDetails: OnyxEntry): PersonalDetailsList { + return ( + accountIDs?.reduce((acc, accountID) => { + if (!accountID) { + return acc; + } + const detail = personalDetails?.[accountID]; + if (!detail) { + return acc; + } + acc[accountID] = detail; + return acc; + }, {} as PersonalDetailsList) ?? {} + ); +} + function getDisplayNameOrYou(displayName: string, accountID: number, currentUserAccountID: number, translate: LocalizedTranslate) { if (accountID === currentUserAccountID) { return translate('common.you'); @@ -470,8 +494,10 @@ function areTravelPersonalDetailsMissing(privatePersonalDetails: OnyxEntry, reportOwnerAccountID: number | undefined, currentUserAccountID?: number): string { +function getReimbursedMessage( + translate: LocalizedTranslate, + reportAction: OnyxInputOrEntry, + reportOwnerAccountID: number | undefined, + submitterLoginParam: string | undefined, + actorLoginParam: string | undefined, + currentUserAccountID?: number, +): string { const effectiveCurrentUserAccountID = currentUserAccountID ?? deprecatedCurrentUserAccountID ?? CONST.DEFAULT_NUMBER_ID; + const submitterLogin = submitterLoginParam ?? ''; + const actorLogin = actorLoginParam ?? ''; const originalMessage = getOriginalMessage(reportAction) as OriginalMessageReimbursed | undefined; // Auth stores the payment method as `method`; the openReport path maps it to `paymentMethod` via getDisplayInformation(). @@ -493,13 +502,8 @@ function getReimbursedMessage(translate: LocalizedTranslate, reportAction: OnyxI // Resolve submitter from report owner const submitterAccountID = reportOwnerAccountID ?? CONST.DEFAULT_NUMBER_ID; - const submitterLogin = getPersonalDetailsByIDs({accountIDs: [submitterAccountID], currentUserAccountID: effectiveCurrentUserAccountID}).at(0)?.login ?? ''; const isCurrentUser = submitterAccountID === effectiveCurrentUserAccountID; - // Resolve actor from action - const actorAccountID = reportAction?.actorAccountID ?? CONST.DEFAULT_NUMBER_ID; - const actorLogin = getPersonalDetailsByIDs({accountIDs: [actorAccountID], currentUserAccountID: effectiveCurrentUserAccountID}).at(0)?.login ?? ''; - const isAutomation = !!reportAction?.delegateAccountID; let paymentSuffix = ''; @@ -1974,6 +1978,8 @@ function isReportActionAttachment(reportAction: OnyxInputOrEntry): function getMemberChangeMessageElements( translate: LocalizedTranslate, reportAction: OnyxEntry, + actorDetails: OnyxEntry, + targetAccountDetailsList: OnyxEntry, getReportNameCallback: typeof getReportName, reportAttributes?: ReportAttributesDerivedValue['reports'], ): readonly MemberChangeMessageElement[] { @@ -1991,17 +1997,15 @@ function getMemberChangeMessageElements( } if (isLeaveAction) { - verb = getPolicyChangeLogEmployeeLeftMessage(translate, reportAction); + verb = getPolicyChangeLogEmployeeLeftMessage(translate, reportAction, actorDetails); } const originalMessage = getOriginalMessage(reportAction); const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? []; - const personalDetails = getPersonalDetailsByIDs({accountIDs: targetAccountIDs, currentUserAccountID: 0}); - const personalDetailsMap = createPersonalDetailsLookupByAccountID(personalDetails); const mentionElements = targetAccountIDs.map((accountID): MemberChangeMessageUserMentionElement => { - const personalDetail = personalDetailsMap[accountID]; - const handleText = getEffectiveDisplayName(formatPhoneNumber, personalDetail) ?? translate('common.hidden'); + const personalDetail = targetAccountDetailsList?.[accountID]; + const handleText = personalDetail ? getEffectiveDisplayName(formatPhoneNumber, personalDetail) : translate('common.hidden'); return { kind: 'userMention', @@ -2319,10 +2323,19 @@ function getDynamicExternalWorkflowApproveFailedActionMessage(translate: Localiz function getMemberChangeMessageFragment( translate: LocalizedTranslate, reportAction: OnyxEntry, + actorDetails: OnyxEntry, + targetAccountDetailsList: OnyxEntry, getReportNameCallback: typeof getReportName, reportAttributes?: ReportAttributesDerivedValue['reports'], ): Message { - const messageElements: readonly MemberChangeMessageElement[] = getMemberChangeMessageElements(translate, reportAction, getReportNameCallback, reportAttributes); + const messageElements: readonly MemberChangeMessageElement[] = getMemberChangeMessageElements( + translate, + reportAction, + actorDetails, + targetAccountDetailsList, + getReportNameCallback, + reportAttributes, + ); const html = messageElements .map((messageElement) => { switch (messageElement.kind) { @@ -2444,18 +2457,20 @@ function hasRequestFromCurrentAccount(report: OnyxEntry, currentAccountI * @param reportAction * @returns the actionable mention whisper message. */ -function getActionableMentionWhisperMessage(translate: LocalizedTranslate, reportAction: OnyxEntry>): string { +function getActionableMentionWhisperMessage( + translate: LocalizedTranslate, + reportAction: OnyxEntry>, + targetAccountDetails: OnyxEntry, +): string { if (!reportAction) { return ''; } const originalMessage = getOriginalMessage(reportAction); const targetAccountIDs: number[] = originalMessage?.inviteeAccountIDs ?? []; - const personalDetails = getPersonalDetailsByIDs({accountIDs: targetAccountIDs, currentUserAccountID: 0}); - const personalDetailsMap = createPersonalDetailsLookupByAccountID(personalDetails); const mentionElements = targetAccountIDs.map((accountID): string => { - const personalDetail = personalDetailsMap[accountID]; - const displayName = getEffectiveDisplayName(formatPhoneNumber, personalDetail); + const personalDetail = targetAccountDetails?.[accountID]; + const displayName = personalDetail ? getEffectiveDisplayName(formatPhoneNumber, personalDetail) : undefined; const handleText = isEmpty(displayName) ? translate('common.hidden') : displayName; return `@${handleText}`; }); @@ -2860,16 +2875,17 @@ function getPolicyChangeLogUpdateEmployee(translate: LocalizedTranslate, reportA return buildPolicyChangeLogUpdateEmployeeSingleFieldMessage(translate, originalMessage?.field, originalMessage?.oldValue, originalMessage?.newValue, email); } -function getPolicyChangeLogEmployeeLeftMessage(translate: LocalizedTranslate, reportAction: ReportAction, useName = false): string { - if (!isLeavePolicyAction(reportAction)) { - return ''; - } +function getPolicyChangeLogEmployeeLeftMessage( + translate: LocalizedTranslate, + reportAction: ReportAction, + actorDetails: OnyxEntry, + useName = false, +): string { const originalMessage = getOriginalMessage(reportAction); - const personalDetails = getPersonalDetailsByIDs({accountIDs: reportAction.actorAccountID ? [reportAction.actorAccountID] : [], currentUserAccountID: 0})?.at(0); if (!!originalMessage && !originalMessage.email) { - originalMessage.email = personalDetails?.login; + originalMessage.email = actorDetails?.login; } - const nameOrEmail = useName && !!personalDetails?.firstName ? `${personalDetails?.firstName}:` : (originalMessage?.email ?? ''); + const nameOrEmail = useName && !!actorDetails?.firstName ? `${actorDetails?.firstName}:` : (originalMessage?.email ?? ''); const formattedNameOrEmail = formatPhoneNumber(nameOrEmail); return translate('report.actions.type.leftWorkspace', formattedNameOrEmail); } @@ -3922,12 +3938,7 @@ function getUpdatedApprovalRuleMessage(translate: LocalizedTranslate, reportActi return getReportActionText(reportAction); } -function getRemovedFromApprovalChainMessage(translate: LocalizedTranslate, reportAction: OnyxEntry>) { - const originalMessage = getOriginalMessage(reportAction); - const submittersNames = getPersonalDetailsByIDs({ - accountIDs: originalMessage?.submittersAccountIDs ?? [], - currentUserAccountID: deprecatedCurrentUserAccountID ?? CONST.DEFAULT_NUMBER_ID, - }).map(({displayName, login}) => displayName ?? login ?? 'Unknown Submitter'); +function getRemovedFromApprovalChainMessage(translate: LocalizedTranslate, submittersNames: string[]) { return translate('workspaceActions.removedFromApprovalWorkflow', {submittersNames, count: submittersNames.length}); } @@ -4649,6 +4660,7 @@ export { isDeletedAction, isDeletedParentAction, isMemberChangeAction, + isLeavePolicyAction, isExportIntegrationAction, isIntegrationMessageAction, isMessageDeleted, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index b4144fb55b8c..f825e291e5db 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -34,6 +34,7 @@ import { shouldShowLastActorDisplayName, } from './OptionsListUtils'; import Parser from './Parser'; +import {getPersonalDetailsByID} from './PersonalDetailsUtils'; import {getCleanedTagName} from './PolicyUtils'; import { getActionableCard3DSTransactionApprovalMessage, @@ -134,6 +135,7 @@ import { isActionOfType, isCardIssuedAction, isInviteOrRemovedAction, + isLeavePolicyAction, isOldDotReportAction, isRenamedAction, isTagModificationAction, @@ -964,6 +966,7 @@ function getOptionData({ lastMessageTextFromReport = getLastMessageTextForReport({ translate, report, + personalDetails, lastActorDetails, movedFromReport, movedToReport, @@ -1150,8 +1153,9 @@ function getOptionData({ result.alternateText = getPolicyChangeLogDefaultReimbursableMessage(translate, lastAction); } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_DEFAULT_TITLE_ENFORCED) { result.alternateText = getPolicyChangeLogDefaultTitleEnforcedMessage(translate, lastAction); - } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.LEAVE_POLICY) { - result.alternateText = getPolicyChangeLogEmployeeLeftMessage(translate, lastAction, true); + } else if (isLeavePolicyAction(lastAction)) { + result.alternateText = getPolicyChangeLogEmployeeLeftMessage(translate, lastAction, getPersonalDetailsByID(lastAction.actorAccountID, personalDetails), true); + console.log('alternate text', result.alternateText); } else if (isCardIssuedAction(lastAction)) { result.alternateText = getCardIssuedMessage({reportAction: lastAction, expensifyCard: card, translate}); } else if (lastAction && isOldDotReportAction(lastAction)) { diff --git a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx index 100aa94f2ede..74ce0db60115 100644 --- a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx @@ -22,6 +22,7 @@ import {getForReportAction} from '@libs/ModifiedExpenseMessage'; import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute'; import Navigation from '@libs/Navigation/Navigation'; import Parser from '@libs/Parser'; +import {getLoginByAccountID, getPersonalDetailsByID, getPersonalDetailsListByIDs} from '@libs/PersonalDetailsUtils'; import {getCleanedTagName, isPolicyAdmin} from '@libs/PolicyUtils'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import stripFollowupListFromHtml from '@libs/ReportActionFollowupUtils/stripFollowupListFromHtml'; @@ -851,6 +852,7 @@ const ContextMenuActions: ContextMenuAction[] = [ conciergeReportID, reportAttributes, originalReportOfUnapprovedTransaction, + personalDetails, }, ) => { const isReportPreviewAction = isReportPreviewActionReportActionsUtils(reportAction); @@ -895,7 +897,16 @@ const ContextMenuActions: ContextMenuAction[] = [ const taskPreviewMessage = getTaskCreatedMessage(translate, reportAction, childReport, true); Clipboard.setString(taskPreviewMessage); } else if (isMemberChangeAction(reportAction)) { - const logMessage = getMemberChangeMessageFragment(translate, reportAction, getReportName, reportAttributes).html ?? ''; + const targetAccountIDs = getOriginalMessage(reportAction)?.targetAccountIDs; + const logMessage = + getMemberChangeMessageFragment( + translate, + reportAction, + getPersonalDetailsByID(reportAction.actorAccountID, personalDetails), + getPersonalDetailsListByIDs(targetAccountIDs, personalDetails), + getReportName, + reportAttributes, + ).html ?? ''; setClipboardMessage(logMessage); } else if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_NAME) { Clipboard.setString(Str.htmlDecode(getWorkspaceNameUpdatedMessage(translate, reportAction))); @@ -1009,13 +1020,23 @@ const ContextMenuActions: ContextMenuAction[] = [ } else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.MARKED_REIMBURSED)) { Clipboard.setString(getMarkedReimbursedMessage(translate, reportAction)); } else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REIMBURSED)) { - Clipboard.setString(getReimbursedMessage(translate, reportAction, report?.ownerAccountID, currentUserPersonalDetails.accountID)); + Clipboard.setString( + getReimbursedMessage( + translate, + reportAction, + report?.ownerAccountID, + getLoginByAccountID(report?.ownerAccountID, personalDetails), + getLoginByAccountID(reportAction.actorAccountID, personalDetails), + currentUserPersonalDetails.accountID, + ), + ); } else if (isReimbursementQueuedAction(reportAction)) { Clipboard.setString( getReimbursementQueuedActionMessage({reportAction, translate, formatPhoneNumber: formatPhoneNumberPhoneUtils, report, shouldUseShortDisplayName: false}), ); } else if (isActionableMentionWhisper(reportAction)) { - const mentionWhisperMessage = getActionableMentionWhisperMessage(translate, reportAction); + const targetAccountIDs = getOriginalMessage(reportAction)?.inviteeAccountIDs; + const mentionWhisperMessage = getActionableMentionWhisperMessage(translate, reportAction, getPersonalDetailsListByIDs(targetAccountIDs, personalDetails)); setClipboardMessage(mentionWhisperMessage); } else if (isActionableTrackExpense(reportAction)) { setClipboardMessage(CONST.ACTIONABLE_TRACK_EXPENSE_WHISPER_MESSAGE); diff --git a/src/pages/inbox/report/ReportActionItemMessage.tsx b/src/pages/inbox/report/ReportActionItemMessage.tsx index 24fbba0cd03a..d1207b7e6ea4 100644 --- a/src/pages/inbox/report/ReportActionItemMessage.tsx +++ b/src/pages/inbox/report/ReportActionItemMessage.tsx @@ -4,18 +4,9 @@ import {View} from 'react-native'; import Button from '@components/Button'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; -import useReportAttributes from '@hooks/useReportAttributes'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import { - getMemberChangeMessageFragment, - getOriginalMessage, - getUpdateRoomDescriptionFragment, - isMemberChangeAction, - isMoneyRequestAction, - isReimbursementDirectionInformationRequiredAction, -} from '@libs/ReportActionsUtils'; -import {getReportName} from '@libs/ReportNameUtils'; +import {getOriginalMessage, getUpdateRoomDescriptionFragment, isMoneyRequestAction, isReimbursementDirectionInformationRequiredAction} from '@libs/ReportActionsUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {ReportAction} from '@src/types/onyx'; @@ -43,7 +34,6 @@ type ReportActionItemMessageProps = { function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHidden = false}: ReportActionItemMessageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const reportAttributes = useReportAttributes(); if (isMoneyRequestAction(action)) { return ( @@ -57,22 +47,6 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid ); } - if (isMemberChangeAction(action)) { - const fragment = getMemberChangeMessageFragment(translate, action, getReportName, reportAttributes); - - return ( - - - - ); - } - if (action.actionName === CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.UPDATE_ROOM_DESCRIPTION) { const fragment = getUpdateRoomDescriptionFragment(translate, action); return ( diff --git a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx index e391db04f066..74ee6cc5d8bb 100644 --- a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx +++ b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx @@ -34,6 +34,7 @@ import { isCardIssuedAction, isCreatedTaskReportAction, isIOURequestReportAction, + isMemberChangeAction, isMoneyRequestAction, isReimbursementDeQueuedOrCanceledAction, isReimbursementQueuedAction, @@ -53,6 +54,7 @@ import ConfirmWhisperContent from './ConfirmWhisperContent'; import FraudAlertContent from './FraudAlertContent'; import IntegrationSyncFailedMessage from './IntegrationSyncFailedMessage'; import JoinRequestContent from './JoinRequestContent'; +import MemberChangeContent from './MemberChangeContent'; import MentionWhisperContent from './MentionWhisperContent'; import ModifiedExpenseContent from './ModifiedExpenseContent'; import PaymentContent from './PaymentContent'; @@ -61,6 +63,7 @@ import ReceiptScanFailedContent from './ReceiptScanFailedContent'; import ReimbursedContent from './ReimbursedContent'; import ReimbursementDeQueuedContent from './ReimbursementDeQueuedContent'; import ReimbursementQueuedContent from './ReimbursementQueuedContent'; +import RemovedFromApprovalChainContent from './RemovedFromApprovalChainContent'; import ReportMentionWhisperContent from './ReportMentionWhisperContent'; import SimpleMessageContent, {isSimpleMessageAction} from './SimpleMessageContent'; @@ -303,6 +306,12 @@ function ActionContentRouter({ } return ; } + if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.REMOVED_FROM_APPROVAL_CHAIN)) { + return ; + } + if (isMemberChangeAction(action)) { + return ; + } if (isHandledPolicyChangeLogAction(action)) { return ( >; +}; + +function MemberChangeContent({action}: MemberChangeContentProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const reportAttributes = useReportAttributes(); + const [actorDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsSelector(action.actorAccountID)}); + const [targetAccountDetailsList] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsListSelector(getOriginalMessage(action)?.targetAccountIDs)}); + const fragment = getMemberChangeMessageFragment(translate, action, actorDetails, targetAccountDetailsList, getReportName, reportAttributes); + + return ( + + + + ); +} + +export default MemberChangeContent; diff --git a/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx b/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx index 8ac1954bd93b..f664a311f7d6 100644 --- a/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx +++ b/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx @@ -13,6 +13,7 @@ import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBa import {resolveActionableMentionWhisper} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import {personalDetailsListSelector} from '@src/selectors/PersonalDetails'; import type {Report, ReportAction} from '@src/types/onyx'; type MentionWhisperContentProps = { @@ -42,6 +43,7 @@ function MentionWhisperContent({action, actionOwnerReportStable, parentReport, o // Subscribe to the full report here — the resolve action needs heartbeat fields for its failure-revert payload. const [actionOwnerReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${actionOwnerReportStable?.reportID}`); + const [targetAccountDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsListSelector(getOriginalMessage(action)?.inviteeAccountIDs)}); const isReportInPolicy = !!policyID && policyID !== CONST.POLICY.ID_FAKE && personalPolicyID !== policyID; const hasMentionedPolicyMembers = getOriginalMessage(action)?.inviteeEmails?.every((login) => isPolicyMember(policy, login)); @@ -76,7 +78,7 @@ function MentionWhisperContent({action, actionOwnerReportStable, parentReport, o return ( - + {buttons.length > 0 && ( ; } diff --git a/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx b/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx new file mode 100644 index 000000000000..986294323fcd --- /dev/null +++ b/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import {getOriginalMessage, getRemovedFromApprovalChainMessage} from '@libs/ReportActionsUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {multiPersonalDetailsSelector} from '@src/selectors/PersonalDetails'; +import type {PersonalDetailsList, ReportAction} from '@src/types/onyx'; +import ReportActionItemBasicMessage from '../ReportActionItemBasicMessage'; + +type RemovedFromApprovalChainContentProps = { + action: ReportAction; +}; + +function submitterNamesSelector(submittersAccountIDs: number[] | undefined) { + return (personalDetailsList: OnyxEntry) => { + multiPersonalDetailsSelector(submittersAccountIDs)(personalDetailsList)?.map(({displayName, login}) => displayName ?? login ?? 'Unknown Submitter'); + }; +} + +function RemovedFromApprovalChainContent({action}: RemovedFromApprovalChainContentProps) { + const {translate} = useLocalize(); + const [submitterNames] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: submitterNamesSelector(getOriginalMessage(action)?.submittersAccountIDs)}); + + return ; +} + +export default RemovedFromApprovalChainContent; diff --git a/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx b/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx index 78794c7162c7..5cf39c87e658 100644 --- a/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx +++ b/src/pages/inbox/report/actionContents/SimpleMessageContent.tsx @@ -7,7 +7,6 @@ import { getMarkedReimbursedMessage, getMessageOfOldDotReportAction, getOriginalMessage, - getRemovedFromApprovalChainMessage, getReportActionText, isActionOfType, isRejectedAction, @@ -38,7 +37,6 @@ const SIMPLE_MESSAGE_ACTION_TYPES = new Set([ CONST.REPORT.ACTIONS.TYPE.RESOLVED_DUPLICATES, CONST.REPORT.ACTIONS.TYPE.DEMOTED_FROM_WORKSPACE, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_CARD_3DS_TRANSACTION_APPROVAL, - CONST.REPORT.ACTIONS.TYPE.REMOVED_FROM_APPROVAL_CHAIN, CONST.REPORT.ACTIONS.TYPE.MARK_REIMBURSED_FROM_INTEGRATION, ]); @@ -100,9 +98,6 @@ function SimpleMessageContent({action}: SimpleMessageContentProps) { if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_CARD_3DS_TRANSACTION_APPROVAL)) { return ; } - if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.REMOVED_FROM_APPROVAL_CHAIN)) { - return ; - } if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.MARK_REIMBURSED_FROM_INTEGRATION)) { return ; } diff --git a/src/selectors/PersonalDetails.ts b/src/selectors/PersonalDetails.ts index 7004252a6f25..c938a882125c 100644 --- a/src/selectors/PersonalDetails.ts +++ b/src/selectors/PersonalDetails.ts @@ -1,12 +1,15 @@ import type {OnyxEntry} from 'react-native-onyx'; -import {getDisplayNameOrDefault, getLoginByAccountID, newGetPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils'; +import {getDisplayNameOrDefault, getLoginByAccountID, getPersonalDetailsByID, getPersonalDetailsListByIDs, newGetPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils'; import CONST from '@src/CONST'; import type {PersonalDetailsList, Report} from '@src/types/onyx'; import type PersonalDetails from '@src/types/onyx/PersonalDetails'; -const personalDetailsSelector = (accountID: number | undefined) => (personalDetailsList: OnyxEntry) => (accountID ? personalDetailsList?.[accountID] : undefined); +const personalDetailsSelector = (accountID: number | undefined) => (personalDetailsList: OnyxEntry) => getPersonalDetailsByID(accountID, personalDetailsList); -const multiPersonalDetailsSelector = (accountIDs: number[]) => (personalDetails: OnyxEntry) => newGetPersonalDetailsByIDs(accountIDs, personalDetails); +const multiPersonalDetailsSelector = (accountIDs: number[] | undefined) => (personalDetails: OnyxEntry) => newGetPersonalDetailsByIDs(accountIDs, personalDetails); + +const personalDetailsListSelector = (accountIDs: Array | undefined) => (personalDetailsList: OnyxEntry) => + getPersonalDetailsListByIDs(accountIDs, personalDetailsList); const personalDetailsLoginSelector = (accountID: number | undefined) => (personalDetailsList: OnyxEntry) => getLoginByAccountID(accountID, personalDetailsList); @@ -33,6 +36,7 @@ const accountIDToLoginSelector = (reportsToArchive: Report[]) => (personalDetail export { personalDetailsSelector, multiPersonalDetailsSelector, + personalDetailsListSelector, personalDetailsDisplayNameSelector, personalDetailsLoginSelector, personalDetailByAccountIDSelector, From d988223fec3d3ad7535687aa8b242982a5592e25 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 3 Jun 2026 16:44:59 +0800 Subject: [PATCH 02/10] fix missing return --- .../report/actionContents/RemovedFromApprovalChainContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx b/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx index 986294323fcd..7932cd01bd80 100644 --- a/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx +++ b/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx @@ -15,7 +15,7 @@ type RemovedFromApprovalChainContentProps = { function submitterNamesSelector(submittersAccountIDs: number[] | undefined) { return (personalDetailsList: OnyxEntry) => { - multiPersonalDetailsSelector(submittersAccountIDs)(personalDetailsList)?.map(({displayName, login}) => displayName ?? login ?? 'Unknown Submitter'); + return multiPersonalDetailsSelector(submittersAccountIDs)(personalDetailsList)?.map(({displayName, login}) => displayName ?? login ?? 'Unknown Submitter'); }; } From f28becbf2104fb300d6b5946de830b7bc2af938b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 3 Jun 2026 16:46:12 +0800 Subject: [PATCH 03/10] lint --- src/libs/SidebarUtils.ts | 1 - src/pages/inbox/report/actionContents/MemberChangeContent.tsx | 4 ++-- .../report/actionContents/RemovedFromApprovalChainContent.tsx | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index f825e291e5db..8ef5bc083600 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1155,7 +1155,6 @@ function getOptionData({ result.alternateText = getPolicyChangeLogDefaultTitleEnforcedMessage(translate, lastAction); } else if (isLeavePolicyAction(lastAction)) { result.alternateText = getPolicyChangeLogEmployeeLeftMessage(translate, lastAction, getPersonalDetailsByID(lastAction.actorAccountID, personalDetails), true); - console.log('alternate text', result.alternateText); } else if (isCardIssuedAction(lastAction)) { result.alternateText = getCardIssuedMessage({reportAction: lastAction, expensifyCard: card, translate}); } else if (lastAction && isOldDotReportAction(lastAction)) { diff --git a/src/pages/inbox/report/actionContents/MemberChangeContent.tsx b/src/pages/inbox/report/actionContents/MemberChangeContent.tsx index 75a72413979e..edb6136338c4 100644 --- a/src/pages/inbox/report/actionContents/MemberChangeContent.tsx +++ b/src/pages/inbox/report/actionContents/MemberChangeContent.tsx @@ -7,11 +7,11 @@ import useReportAttributes from '@hooks/useReportAttributes'; import useThemeStyles from '@hooks/useThemeStyles'; import {getMemberChangeMessageFragment, getOriginalMessage} from '@libs/ReportActionsUtils'; import {getReportName} from '@libs/ReportNameUtils'; -import CONST from '@src/CONST'; +import type CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {personalDetailsListSelector, personalDetailsSelector} from '@src/selectors/PersonalDetails'; import type {ReportAction} from '@src/types/onyx'; -import TextCommentFragment from '../comment/TextCommentFragment'; +import TextCommentFragment from '@pages/inbox/report/comment/TextCommentFragment'; type MemberChangeContentProps = { action: ReportAction>; diff --git a/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx b/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx index 7932cd01bd80..8c430f70e3e1 100644 --- a/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx +++ b/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx @@ -3,11 +3,11 @@ import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import {getOriginalMessage, getRemovedFromApprovalChainMessage} from '@libs/ReportActionsUtils'; -import CONST from '@src/CONST'; +import type CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {multiPersonalDetailsSelector} from '@src/selectors/PersonalDetails'; import type {PersonalDetailsList, ReportAction} from '@src/types/onyx'; -import ReportActionItemBasicMessage from '../ReportActionItemBasicMessage'; +import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; type RemovedFromApprovalChainContentProps = { action: ReportAction; From c15bacb071314a96b527435aa27f764a82d61bd7 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 3 Jun 2026 17:40:09 +0800 Subject: [PATCH 04/10] fix missing param --- tests/unit/OptionsListUtilsTest.tsx | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 8a2f8d55cc13..3c20f99e95c8 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -4114,6 +4114,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4177,6 +4178,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4225,6 +4227,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4273,6 +4276,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4321,6 +4325,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4368,6 +4373,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4403,6 +4409,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4464,6 +4471,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4511,6 +4519,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4544,6 +4553,7 @@ describe('OptionsListUtils', () => { [movedTransactionAction.reportActionID]: movedTransactionAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4570,6 +4580,7 @@ describe('OptionsListUtils', () => { [submittedAction.reportActionID]: submittedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4597,6 +4608,7 @@ describe('OptionsListUtils', () => { [approvedAction.reportActionID]: approvedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4624,6 +4636,7 @@ describe('OptionsListUtils', () => { [forwardedAction.reportActionID]: forwardedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4648,6 +4661,7 @@ describe('OptionsListUtils', () => { [corporateForceUpgradeAction.reportActionID]: corporateForceUpgradeAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4671,6 +4685,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4693,6 +4708,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4715,6 +4731,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4737,6 +4754,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4759,6 +4777,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4780,6 +4799,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4801,6 +4821,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4822,6 +4843,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4843,6 +4865,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4864,6 +4887,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4885,6 +4909,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4906,6 +4931,7 @@ describe('OptionsListUtils', () => { [takeControlAction.reportActionID]: takeControlAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4928,6 +4954,7 @@ describe('OptionsListUtils', () => { [rerouteAction.reportActionID]: rerouteAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4950,6 +4977,7 @@ describe('OptionsListUtils', () => { [movedAction.reportActionID]: movedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4976,6 +5004,7 @@ describe('OptionsListUtils', () => { // When getting the last message text for the report const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5004,6 +5033,7 @@ describe('OptionsListUtils', () => { const expectedVisibleText = ''; const result = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5023,6 +5053,7 @@ describe('OptionsListUtils', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5054,6 +5085,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const result = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5094,6 +5126,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const result = getLastMessageTextForReport({ + personalDetails: undefined, report, translate: translateLocal, lastActorDetails: null, @@ -5145,6 +5178,7 @@ describe('OptionsListUtils', () => { [submittedAction.reportActionID]: submittedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5180,6 +5214,7 @@ describe('OptionsListUtils', () => { [dewSubmitFailedAction.reportActionID]: dewSubmitFailedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5211,6 +5246,7 @@ describe('OptionsListUtils', () => { [dewSubmitFailedAction.reportActionID]: dewSubmitFailedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5249,6 +5285,7 @@ describe('OptionsListUtils', () => { }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5290,6 +5327,7 @@ describe('OptionsListUtils', () => { }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, From d16cdbbd0195dc181efdfc3fcfc300f5245db27e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 3 Jun 2026 20:59:27 +0800 Subject: [PATCH 05/10] prettier --- src/pages/inbox/report/actionContents/MemberChangeContent.tsx | 2 +- .../report/actionContents/RemovedFromApprovalChainContent.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/inbox/report/actionContents/MemberChangeContent.tsx b/src/pages/inbox/report/actionContents/MemberChangeContent.tsx index edb6136338c4..d6a571a1575e 100644 --- a/src/pages/inbox/report/actionContents/MemberChangeContent.tsx +++ b/src/pages/inbox/report/actionContents/MemberChangeContent.tsx @@ -7,11 +7,11 @@ import useReportAttributes from '@hooks/useReportAttributes'; import useThemeStyles from '@hooks/useThemeStyles'; import {getMemberChangeMessageFragment, getOriginalMessage} from '@libs/ReportActionsUtils'; import {getReportName} from '@libs/ReportNameUtils'; +import TextCommentFragment from '@pages/inbox/report/comment/TextCommentFragment'; import type CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {personalDetailsListSelector, personalDetailsSelector} from '@src/selectors/PersonalDetails'; import type {ReportAction} from '@src/types/onyx'; -import TextCommentFragment from '@pages/inbox/report/comment/TextCommentFragment'; type MemberChangeContentProps = { action: ReportAction>; diff --git a/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx b/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx index 8c430f70e3e1..d9ca71796d0b 100644 --- a/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx +++ b/src/pages/inbox/report/actionContents/RemovedFromApprovalChainContent.tsx @@ -3,11 +3,11 @@ import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import {getOriginalMessage, getRemovedFromApprovalChainMessage} from '@libs/ReportActionsUtils'; +import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; import type CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {multiPersonalDetailsSelector} from '@src/selectors/PersonalDetails'; import type {PersonalDetailsList, ReportAction} from '@src/types/onyx'; -import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; type RemovedFromApprovalChainContentProps = { action: ReportAction; From 7360cd73347f99cb687f82175c309b46970b2b4d Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 3 Jun 2026 21:08:52 +0800 Subject: [PATCH 06/10] add test --- tests/unit/PersonalDetailsSelectorTest.ts | 5 +++++ tests/unit/libs/PersonalDetailsUtilsTest.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/tests/unit/PersonalDetailsSelectorTest.ts b/tests/unit/PersonalDetailsSelectorTest.ts index 6805df5cfad6..1e39d63939b2 100644 --- a/tests/unit/PersonalDetailsSelectorTest.ts +++ b/tests/unit/PersonalDetailsSelectorTest.ts @@ -107,6 +107,11 @@ describe('PersonalDetailsSelector', () => { expect(result).toEqual([]); }); + it('should return an empty array if accountIDs is undefined', () => { + const result = multiPersonalDetailsSelector(undefined)(personalDetailsList); + expect(result).toEqual([]); + }); + it('should return an empty array if the personalDetailsList is undefined', () => { const result = multiPersonalDetailsSelector([accountID])(undefined); expect(result).toEqual([]); diff --git a/tests/unit/libs/PersonalDetailsUtilsTest.ts b/tests/unit/libs/PersonalDetailsUtilsTest.ts index 57de72b4220a..2e18fa469521 100644 --- a/tests/unit/libs/PersonalDetailsUtilsTest.ts +++ b/tests/unit/libs/PersonalDetailsUtilsTest.ts @@ -724,6 +724,11 @@ describe('PersonalDetailsUtils', () => { }, }; + it('should return an empty array if accountIDs is undefined', () => { + const result = newGetPersonalDetailsByIDs(undefined, personalDetails); + expect(result).toEqual([]); + }); + it('should return an empty array if accountIDs is empty', () => { const result = newGetPersonalDetailsByIDs([], personalDetails); expect(result).toEqual([]); From b59848c18d5adaa4b66c38924ee1206d71e9d15f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 4 Jun 2026 00:47:22 +0800 Subject: [PATCH 07/10] fix type --- tests/unit/OptionsListUtilsTest.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 5e3893cb1a4a..19be4b40415f 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -5363,6 +5363,7 @@ describe('OptionsListUtils', () => { }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, From 98b384cee28826fa215903aef4298dec8c89d154 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 11 Jun 2026 11:20:54 +0800 Subject: [PATCH 08/10] add test --- tests/unit/libs/PersonalDetailsUtilsTest.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/libs/PersonalDetailsUtilsTest.ts b/tests/unit/libs/PersonalDetailsUtilsTest.ts index 8769c1bd0d47..9493acfa68f1 100644 --- a/tests/unit/libs/PersonalDetailsUtilsTest.ts +++ b/tests/unit/libs/PersonalDetailsUtilsTest.ts @@ -762,6 +762,11 @@ describe('PersonalDetailsUtils', () => { }, }; + it('should return an empty object if accountIDs is undefined', () => { + const result = getPersonalDetailsListByIDs(undefined, personalDetails); + expect(result).toEqual({}); + }); + it('should return an empty object if accountIDs is empty', () => { const result = getPersonalDetailsListByIDs([], personalDetails); expect(result).toEqual({}); From 80e437b5abf752b69d7098e7d4e149dbf2facf56 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 11 Jun 2026 11:33:00 +0800 Subject: [PATCH 09/10] fix type --- tests/unit/OptionsListUtilsTest.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index e2fae312be7e..a476899622bc 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -4646,6 +4646,7 @@ describe('OptionsListUtils', () => { [forwardedAction.reportActionID]: forwardedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, From 89dff7024cf2601bce025584e3660e0264b4ae8b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 17 Jun 2026 22:06:19 +0800 Subject: [PATCH 10/10] lint --- src/components/Search/FilterComponents/InSelector.tsx | 6 +++++- tests/unit/OptionsListUtilsTest.tsx | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/Search/FilterComponents/InSelector.tsx b/src/components/Search/FilterComponents/InSelector.tsx index 977f41a9aff5..624012522976 100644 --- a/src/components/Search/FilterComponents/InSelector.tsx +++ b/src/components/Search/FilterComponents/InSelector.tsx @@ -89,7 +89,11 @@ function InSelector({value = [], selectionListTextInputStyle, selectionListStyle const isReportArchived = !!privateIsArchived; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; const reportPolicyTags = policyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(report?.policyID)}`]; - const alternateText = getAlternateText(report, {}, {isReportArchived, personalDetails, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID, isTrackIntentUser}); + const alternateText = getAlternateText( + report, + {}, + {isReportArchived, personalDetails, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID, isTrackIntentUser}, + ); return {...report, alternateText}; }); diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index c2ad01e4f25e..26c3f42b47be 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -5436,6 +5436,7 @@ describe('OptionsListUtils', () => { report, lastActorDetails: null, policy: undefined, + personalDetails: undefined, isReportArchived: false, currentUserLogin: CURRENT_USER_EMAIL, });