diff --git a/src/components/Search/FilterComponents/InSelector.tsx b/src/components/Search/FilterComponents/InSelector.tsx index ae36fdd18795..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, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID, isTrackIntentUser}); + const alternateText = getAlternateText( + report, + {}, + {isReportArchived, personalDetails, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID, isTrackIntentUser}, + ); return {...report, alternateText}; }); diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index 2b8bd8a0fe68..cff1b07fd8b1 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -99,7 +99,7 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen const alternateText = getAlternateText( report, {}, - {isReportArchived: privateIsArchived, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID, isTrackIntentUser}, + {isReportArchived: privateIsArchived, personalDetails, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID, isTrackIntentUser}, ); return {...report, alternateText}; }); diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index fddc847eeb4f..37ad143360c4 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, @@ -430,6 +430,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; @@ -451,6 +452,7 @@ function getAlternateText( {showChatPreviewLine = false, forcePolicyNamePreview = false}: PreviewConfig, { isReportArchived, + personalDetails, policy, lastActorDetails = {}, visibleReportActionsData = {}, @@ -473,6 +475,7 @@ function getAlternateText( getLastMessageTextForReport({ translate: translateFn, report, + personalDetails, lastActorDetails, policy, isReportArchived, @@ -653,6 +656,7 @@ function getLastActorDisplayNameFromLastVisibleActions( function getLastMessageTextForReport({ translate, report, + personalDetails, lastActorDetails, movedFromReport, movedToReport, @@ -670,6 +674,7 @@ function getLastMessageTextForReport({ }: { translate: LocalizedTranslate; report: OnyxEntry; + personalDetails: OnyxEntry; lastActorDetails: Partial | null; movedFromReport?: OnyxEntry; movedToReport?: OnyxEntry; @@ -793,7 +798,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)) { @@ -917,7 +928,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); } @@ -1071,7 +1083,7 @@ function getLastMessageTextForReport({ type CreateOptionParams = { accountIDs: number[]; - personalDetails: OnyxInputOrEntry; + personalDetails: OnyxEntry; report: OnyxInputOrEntry; privateIsArchived: boolean | undefined; policy?: OnyxEntry; @@ -1180,6 +1192,7 @@ function createOption({ result.lastMessageText = getLastMessageTextForReport({ translate: translateFn, report, + personalDetails, lastActorDetails, policy, isReportArchived: result.private_isArchived, @@ -1198,6 +1211,7 @@ function createOption({ {showChatPreviewLine, forcePolicyNamePreview}, { isReportArchived: !!result.private_isArchived, + personalDetails, policy, lastActorDetails, visibleReportActionsData, @@ -2345,6 +2359,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 2b04841225ba..baf47cafd9ac 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -141,7 +141,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 = getPersonalDetailsByID(accountID, personalDetails); @@ -154,18 +158,20 @@ function newGetPersonalDetailsByIDs(accountIDs: number[], personalDetails: OnyxE return result; } -function getPersonalDetailsListByIDs(accountIDs: Array, personalDetails: OnyxEntry): PersonalDetailsList { - return accountIDs.reduce((acc, accountID) => { - if (!accountID) { - return acc; - } - const detail = personalDetails?.[accountID]; - if (!detail) { +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; - } - acc[accountID] = detail; - return acc; - }, {} as PersonalDetailsList); + }, {} as PersonalDetailsList) ?? {} + ); } /** diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 5585120418ba..0228949193b8 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -60,7 +60,7 @@ import createDynamicRoute from './Navigation/helpers/dynamicRoutesUtils/createDy import getReportURLForCurrentContext from './Navigation/helpers/getReportURLForCurrentContext'; import {getIsOffline, subscribe as subscribeNetworkState} from './NetworkState'; import Parser from './Parser'; -import {arePersonalDetailsMissing, createPersonalDetailsLookupByAccountID, getEffectiveDisplayName, getPersonalDetailByEmail, getPersonalDetailsByIDs} from './PersonalDetailsUtils'; +import {arePersonalDetailsMissing, getEffectiveDisplayName, getPersonalDetailByEmail} from './PersonalDetailsUtils'; import stripFollowupListFromHtml from './ReportActionFollowupUtils/stripFollowupListFromHtml'; import type {getReportName} from './ReportNameUtils'; import type {OptimisticIOUReportAction, PartialReportAction} from './ReportUtils'; @@ -469,8 +469,17 @@ function getMarkedReimbursedMessage(translate: LocalizedTranslate, reportAction: return translate('iou.paidElsewhere', {comment: originalMessage?.message?.trim()}); } -function getReimbursedMessage(translate: LocalizedTranslate, reportAction: OnyxInputOrEntry, 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(). @@ -499,13 +508,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 = ''; @@ -1979,6 +1983,8 @@ function isReportActionAttachment(reportAction: OnyxInputOrEntry): function getMemberChangeMessageElements( translate: LocalizedTranslate, reportAction: OnyxEntry, + actorDetails: OnyxEntry, + targetAccountDetailsList: OnyxEntry, getReportNameCallback: typeof getReportName, reportAttributes?: ReportAttributesDerivedValue['reports'], ): readonly MemberChangeMessageElement[] { @@ -1996,17 +2002,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', @@ -2324,10 +2328,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) { @@ -2449,18 +2462,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}`; }); @@ -2865,16 +2880,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); } @@ -4005,12 +4021,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}); } @@ -4732,6 +4743,7 @@ export { isDeletedAction, isDeletedParentAction, isMemberChangeAction, + isLeavePolicyAction, isExportIntegrationAction, isIntegrationMessageAction, isMessageDeleted, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index aa26db8dd98d..52fac7a361b2 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, @@ -136,6 +137,7 @@ import { isActionOfType, isCardIssuedAction, isInviteOrRemovedAction, + isLeavePolicyAction, isOldDotReportAction, isRenamedAction, isTagModificationAction, @@ -976,6 +978,7 @@ function getOptionData({ lastMessageTextFromReport = getLastMessageTextForReport({ translate, report, + personalDetails, lastActorDetails, movedFromReport, movedToReport, @@ -1167,8 +1170,8 @@ 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); } 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 5de393f0bf9e..6d9549c02eb4 100644 --- a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx @@ -22,7 +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 {getParticipantsPersonalDetails} from '@libs/PersonalDetailsUtils'; +import {getLoginByAccountID, getParticipantsPersonalDetails, getPersonalDetailsByID, getPersonalDetailsListByIDs} from '@libs/PersonalDetailsUtils'; import {getCleanedTagName, isPolicyAdmin} from '@libs/PolicyUtils'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import stripFollowupListFromHtml from '@libs/ReportActionFollowupUtils/stripFollowupListFromHtml'; @@ -901,6 +901,7 @@ const ContextMenuActions: ContextMenuAction[] = [ bankAccountList, reportAttributes, originalReportOfUnapprovedTransaction, + personalDetails, }, ) => { const isReportPreviewAction = isReportPreviewActionReportActionsUtils(reportAction); @@ -950,7 +951,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))); @@ -1068,13 +1078,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 5924422cbe07..8cb368566359 100644 --- a/src/pages/inbox/report/actionContents/ActionContentRouter.tsx +++ b/src/pages/inbox/report/actionContents/ActionContentRouter.tsx @@ -35,6 +35,7 @@ import { isCardIssuedAction, isCreatedTaskReportAction, isIOURequestReportAction, + isMemberChangeAction, isMoneyRequestAction, isReimbursementDeQueuedOrCanceledAction, isReimbursementQueuedAction, @@ -57,6 +58,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'; @@ -65,6 +67,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'; @@ -301,6 +304,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..d9ca71796d0b --- /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 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'; + +type RemovedFromApprovalChainContentProps = { + action: ReportAction; +}; + +function submitterNamesSelector(submittersAccountIDs: number[] | undefined) { + return (personalDetailsList: OnyxEntry) => { + return 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 ca21155ac77d..a87e1bd96bcb 100644 --- a/src/selectors/PersonalDetails.ts +++ b/src/selectors/PersonalDetails.ts @@ -5,9 +5,9 @@ import type {PersonalDetailsList, Report} from '@src/types/onyx'; 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) => (personalDetailsList: OnyxEntry) => +const personalDetailsListSelector = (accountIDs: Array | undefined) => (personalDetailsList: OnyxEntry) => getPersonalDetailsListByIDs(accountIDs, personalDetailsList); const personalDetailsLoginSelector = (accountID: number | undefined) => (personalDetailsList: OnyxEntry) => getLoginByAccountID(accountID, personalDetailsList); diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index cecdb056132e..26c3f42b47be 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -4152,6 +4152,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4215,6 +4216,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4263,6 +4265,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4311,6 +4314,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4359,6 +4363,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4406,6 +4411,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4441,6 +4447,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4502,6 +4509,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4549,6 +4557,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4582,6 +4591,7 @@ describe('OptionsListUtils', () => { [movedTransactionAction.reportActionID]: movedTransactionAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4608,6 +4618,7 @@ describe('OptionsListUtils', () => { [submittedAction.reportActionID]: submittedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4635,6 +4646,7 @@ describe('OptionsListUtils', () => { [approvedAction.reportActionID]: approvedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4664,6 +4676,7 @@ describe('OptionsListUtils', () => { [forwardedAction.reportActionID]: forwardedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4690,6 +4703,7 @@ describe('OptionsListUtils', () => { [forwardedAction.reportActionID]: forwardedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4714,6 +4728,7 @@ describe('OptionsListUtils', () => { [corporateForceUpgradeAction.reportActionID]: corporateForceUpgradeAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4737,6 +4752,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4759,6 +4775,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4781,6 +4798,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4803,6 +4821,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4825,6 +4844,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4846,6 +4866,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4867,6 +4888,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4888,6 +4910,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4909,6 +4932,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4930,6 +4954,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4951,6 +4976,7 @@ describe('OptionsListUtils', () => { [action.reportActionID]: action, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4972,6 +4998,7 @@ describe('OptionsListUtils', () => { [takeControlAction.reportActionID]: takeControlAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -4994,6 +5021,7 @@ describe('OptionsListUtils', () => { [rerouteAction.reportActionID]: rerouteAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5016,6 +5044,7 @@ describe('OptionsListUtils', () => { [movedAction.reportActionID]: movedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5042,6 +5071,7 @@ describe('OptionsListUtils', () => { // When getting the last message text for the report const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5070,6 +5100,7 @@ describe('OptionsListUtils', () => { const expectedVisibleText = ''; const result = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5089,6 +5120,7 @@ describe('OptionsListUtils', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5120,6 +5152,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const result = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5160,6 +5193,7 @@ describe('OptionsListUtils', () => { await waitForBatchedUpdates(); const result = getLastMessageTextForReport({ + personalDetails: undefined, report, translate: translateLocal, lastActorDetails: null, @@ -5211,6 +5245,7 @@ describe('OptionsListUtils', () => { [submittedAction.reportActionID]: submittedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5246,6 +5281,7 @@ describe('OptionsListUtils', () => { [dewSubmitFailedAction.reportActionID]: dewSubmitFailedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5277,6 +5313,7 @@ describe('OptionsListUtils', () => { [dewSubmitFailedAction.reportActionID]: dewSubmitFailedAction, }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5315,6 +5352,7 @@ describe('OptionsListUtils', () => { }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5356,6 +5394,7 @@ describe('OptionsListUtils', () => { }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, @@ -5397,6 +5436,7 @@ describe('OptionsListUtils', () => { report, lastActorDetails: null, policy: undefined, + personalDetails: undefined, isReportArchived: false, currentUserLogin: CURRENT_USER_EMAIL, }); @@ -5423,6 +5463,7 @@ describe('OptionsListUtils', () => { }); const lastMessage = getLastMessageTextForReport({ + personalDetails: undefined, translate: translateLocal, report, lastActorDetails: null, diff --git a/tests/unit/PersonalDetailsSelectorTest.ts b/tests/unit/PersonalDetailsSelectorTest.ts index 447948543978..e787a7bd3eaf 100644 --- a/tests/unit/PersonalDetailsSelectorTest.ts +++ b/tests/unit/PersonalDetailsSelectorTest.ts @@ -113,6 +113,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 fdbc5edf4368..9493acfa68f1 100644 --- a/tests/unit/libs/PersonalDetailsUtilsTest.ts +++ b/tests/unit/libs/PersonalDetailsUtilsTest.ts @@ -725,6 +725,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([]); @@ -757,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({});