diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 0751d9a2e57b..f0e99f80430d 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1513,9 +1513,9 @@ function MoneyReportHeader({ if (!hasFinishedPDFDownload || !canTriggerAutomaticPDFDownload.current) { return; } - downloadReportPDF(reportPDFFilename, moneyRequestReport?.reportName ?? '', translate); + downloadReportPDF(reportPDFFilename, moneyRequestReport?.reportName ?? '', translate, email ?? ''); canTriggerAutomaticPDFDownload.current = false; - }, [hasFinishedPDFDownload, reportPDFFilename, moneyRequestReport?.reportName, translate]); + }, [hasFinishedPDFDownload, reportPDFFilename, moneyRequestReport?.reportName, translate, email]); const shouldShowBackButton = shouldDisplayBackButton || shouldUseNarrowLayout; @@ -1825,7 +1825,7 @@ function MoneyReportHeader({ if (!hasFinishedPDFDownload) { setIsPDFModalVisible(false); } else { - downloadReportPDF(reportPDFFilename, moneyRequestReport?.reportName ?? '', translate); + downloadReportPDF(reportPDFFilename, moneyRequestReport?.reportName ?? '', translate, email ?? ''); } }} text={hasFinishedPDFDownload ? translate('common.download') : translate('common.cancel')} diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 98a40b4b5263..0dcee12e18f6 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -321,7 +321,7 @@ function MoneyRequestView({ const companyCardPageURL = `${environmentURL}/${ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(transactionThreadReport?.policyID)}`; const [originalTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transaction?.comment?.originalTransactionID)}`, {canBeMissing: true}); const {isExpenseSplit} = getOriginalTransactionWithSplitInfo(transaction, originalTransaction); - const isSplitAvailable = moneyRequestReport && transaction && isSplitAction(moneyRequestReport, [transaction], originalTransaction, policy); + const isSplitAvailable = moneyRequestReport && transaction && isSplitAction(moneyRequestReport, [transaction], originalTransaction, currentUserPersonalDetails.email ?? '', policy); const canEditTaxFields = canEdit && !isDistanceRequest; const canEditAmount = diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index c02c8db81fb6..c6acbe104aad 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -100,7 +100,6 @@ function mapTransactionItemToSelectedEntry( ): [string, SelectedTransactionInfo] { const {canHoldRequest, canUnholdRequest} = canHoldUnholdReportAction(item.report, item.reportAction, item.holdReportAction, item, item.policy); const canRejectRequest = item.report ? canRejectReportAction(currentUserLogin, item.report, item.policy) : false; - return [ item.keyForList, { @@ -109,7 +108,7 @@ function mapTransactionItemToSelectedEntry( canHold: canHoldRequest, isHeld: isOnHold(item), canUnhold: canUnholdRequest, - canSplit: isSplitAction(item.report, [itemTransaction], originalItemTransaction, item.policy), + canSplit: isSplitAction(item.report, [itemTransaction], originalItemTransaction, currentUserLogin, item.policy), hasBeenSplit: getOriginalTransactionWithSplitInfo(itemTransaction, originalItemTransaction).isExpenseSplit, canChangeReport: canEditFieldOfMoneyRequest( item.reportAction, @@ -161,7 +160,7 @@ function prepareTransactionsList( canHold: canHoldRequest, isHeld: isOnHold(item), canUnhold: canUnholdRequest, - canSplit: isSplitAction(item.report, [itemTransaction], originalItemTransaction, item.policy), + canSplit: isSplitAction(item.report, [itemTransaction], originalItemTransaction, currentUserLogin, item.policy), hasBeenSplit: getOriginalTransactionWithSplitInfo(itemTransaction, originalItemTransaction).isExpenseSplit, canChangeReport: canEditFieldOfMoneyRequest( item.reportAction, @@ -519,7 +518,7 @@ function Search({ canHold: canHoldRequest, isHeld: isOnHold(transactionItem), canUnhold: canUnholdRequest, - canSplit: isSplitAction(transactionItem.report, [itemTransaction], originalItemTransaction, transactionItem.policy), + canSplit: isSplitAction(transactionItem.report, [itemTransaction], originalItemTransaction, email ?? '', transactionItem.policy), hasBeenSplit: getOriginalTransactionWithSplitInfo(itemTransaction, originalItemTransaction).isExpenseSplit, canChangeReport: canEditFieldOfMoneyRequest( transactionItem.reportAction, @@ -572,7 +571,7 @@ function Search({ canHold: canHoldRequest, isHeld: isOnHold(transactionItem), canUnhold: canUnholdRequest, - canSplit: isSplitAction(transactionItem.report, [itemTransaction], originalItemTransaction, transactionItem.policy), + canSplit: isSplitAction(transactionItem.report, [itemTransaction], originalItemTransaction, email ?? '', transactionItem.policy), hasBeenSplit: getOriginalTransactionWithSplitInfo(itemTransaction, originalItemTransaction).isExpenseSplit, canChangeReport: canEditFieldOfMoneyRequest( transactionItem.reportAction, diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index a50965e1e33f..99a7938243d3 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -27,6 +27,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report, ReportAction, Session, Transaction} from '@src/types/onyx'; import useAllTransactions from './useAllTransactions'; +import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails'; import useDeleteTransactions from './useDeleteTransactions'; import useDuplicateTransactionsAndViolations from './useDuplicateTransactionsAndViolations'; import {useMemoizedLazyExpensifyIcons} from './useLazyAsset'; @@ -76,6 +77,7 @@ function useSelectedTransactionsActions({ const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(selectedTransactionIDs); const isReportArchived = useReportIsArchived(report?.reportID); const {deleteTransactions} = useDeleteTransactions({report, reportActions, policy}); + const {email} = useCurrentUserPersonalDetails(); const selectedTransactionsList = useMemo( () => selectedTransactionIDs.reduce((acc, transactionID) => { @@ -329,7 +331,7 @@ function useSelectedTransactionsActions({ const originalTransaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${firstTransaction?.comment?.originalTransactionID}`]; const {isExpenseSplit} = getOriginalTransactionWithSplitInfo(firstTransaction, originalTransaction); - const canSplitTransaction = selectedTransactionsList.length === 1 && report && !isExpenseSplit && isSplitAction(report, [firstTransaction], originalTransaction, policy); + const canSplitTransaction = selectedTransactionsList.length === 1 && report && !isExpenseSplit && isSplitAction(report, [firstTransaction], originalTransaction, email ?? '', policy); if (canSplitTransaction) { options.push({ @@ -398,6 +400,7 @@ function useSelectedTransactionsActions({ expensifyIcons.Trashcan, localeCompare, isOnSearch, + email, ]); return { diff --git a/src/hooks/useTodos.ts b/src/hooks/useTodos.ts index cc5859a3c6d4..26b354e43148 100644 --- a/src/hooks/useTodos.ts +++ b/src/hooks/useTodos.ts @@ -53,7 +53,7 @@ export default function useTodos() { if (isPrimaryPayAction(report, accountID, email, policy, reportNameValuePair)) { reportsToPay.push(report); } - if (isExportAction(report, policy, reportActions)) { + if (isExportAction(report, email, policy, reportActions)) { reportsToExport.push(report); } } diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 08a5ffcf1e3f..3c90c63c4780 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -34,7 +34,6 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import {getBankAccountFromID} from './actions/BankAccounts'; import {hasSynchronizationErrorMessage, isConnectionUnverified} from './actions/connections'; import {shouldShowQBOReimbursableExportDestinationAccountError} from './actions/connections/QuickbooksOnline'; -import {getCurrentUserEmail} from './actions/Report'; import {getCategoryApproverRule} from './CategoryUtils'; import Navigation from './Navigation/Navigation'; import {isOffline as isOfflineNetworkStore} from './Network/NetworkStore'; @@ -1613,8 +1612,7 @@ const getDescriptionForPolicyDomainCard = (domainName: string): string => { return domainName; }; -function isPreferredExporter(policy: Policy) { - const user = getCurrentUserEmail(); +function isPreferredExporter(policy: Policy, currentUserLogin: string) { const exporters = [ policy.connections?.intacct?.config?.export?.exporter, policy.connections?.netsuite?.options?.config?.exporter, @@ -1623,7 +1621,7 @@ function isPreferredExporter(policy: Policy) { policy.connections?.xero?.config?.export?.exporter, ]; - return exporters.some((exporter) => exporter && exporter === user); + return exporters.some((exporter) => exporter && exporter === currentUserLogin); } /** diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts index 5199b2bcf756..65254ef7b9ae 100644 --- a/src/libs/ReportPreviewActionUtils.ts +++ b/src/libs/ReportPreviewActionUtils.ts @@ -135,9 +135,9 @@ function canPay(report: Report, isReportArchived: boolean, currentUserAccountID: return invoiceReceiverPolicy?.role === CONST.POLICY.ROLE.ADMIN && reimbursableSpend > 0; } -function canExport(report: Report, policy?: Policy) { +function canExport(report: Report, currentUserLogin: string, policy?: Policy) { const isExpense = isExpenseReport(report); - const isExporter = policy ? isPreferredExporter(policy) : false; + const isExporter = policy ? isPreferredExporter(policy, currentUserLogin) : false; const isReimbursed = isSettled(report); const isClosed = isClosedReport(report); const isApproved = isReportApproved({report}); @@ -222,7 +222,7 @@ function getReportPreviewAction({ if (canPay(report, isReportArchived, currentUserAccountID, currentUserEmail, policy, invoiceReceiverPolicy)) { return CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY; } - if (canExport(report, policy)) { + if (canExport(report, currentUserEmail, policy)) { return CONST.REPORT.REPORT_PREVIEW_ACTIONS.EXPORT_TO_ACCOUNTING; } diff --git a/src/libs/ReportPrimaryActionUtils.ts b/src/libs/ReportPrimaryActionUtils.ts index f1bbd576cdbf..32262d0fc420 100644 --- a/src/libs/ReportPrimaryActionUtils.ts +++ b/src/libs/ReportPrimaryActionUtils.ts @@ -212,7 +212,7 @@ function isPrimaryPayAction( return invoiceReceiverPolicy?.role === CONST.POLICY.ROLE.ADMIN && reimbursableSpend > 0; } -function isExportAction(report: Report, policy?: Policy, reportActions?: ReportAction[]) { +function isExportAction(report: Report, currentUserLogin: string, policy?: Policy, reportActions?: ReportAction[]) { if (!policy) { return false; } @@ -226,7 +226,7 @@ function isExportAction(report: Report, policy?: Policy, reportActions?: ReportA const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; - const isReportExporter = isPreferredExporter(policy); + const isReportExporter = isPreferredExporter(policy, currentUserLogin); if (!isReportExporter && !isAdmin) { return false; } @@ -460,7 +460,7 @@ function getReportPrimaryAction(params: GetReportPrimaryActionParams): ValueOf, reportTransactions: Array>, originalTransaction: OnyxEntry, policy?: OnyxEntry): boolean { +function isSplitAction( + report: OnyxEntry, + reportTransactions: Array>, + originalTransaction: OnyxEntry, + currentUserLogin: string, + policy?: OnyxEntry, +): boolean { if (Number(reportTransactions?.length) !== 1 || !report) { return false; } @@ -128,8 +134,7 @@ function isSplitAction(report: OnyxEntry, reportTransactions: Array, key: string, currentSearch: SearchKey, - currentUserEmail: string, + currentUserLogin: string, reportActions: OnyxTypes.ReportAction[] = [], ): SearchTransactionAction[] { const isTransaction = isTransactionEntry(key); @@ -1402,7 +1402,7 @@ function getActions( } const policy = getPolicyFromKey(data, report); - const isExportAvailable = isExportAction(report, policy, reportActions) && !isTransaction; + const isExportAvailable = isExportAction(report, currentUserLogin, policy, reportActions) && !isTransaction; if (isSettled(report) && !isExportAvailable) { return [CONST.SEARCH.ACTION_TYPES.PAID]; @@ -1477,7 +1477,7 @@ function getActions( } // We check for isAllowedToApproveExpenseReport because if the policy has preventSelfApprovals enabled, we disable the Submit action and in that case we want to show the View action instead - if (canSubmitReport(report, policy, allReportTransactions, allViolations, isIOUReportArchived || isChatReportArchived, currentUserEmail) && isAllowedToApproveExpenseReport) { + if (canSubmitReport(report, policy, allReportTransactions, allViolations, isIOUReportArchived || isChatReportArchived, currentUserLogin) && isAllowedToApproveExpenseReport) { allActions.push(CONST.SEARCH.ACTION_TYPES.SUBMIT); } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 9179e7583e9b..dc4822e5aadc 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -273,8 +273,9 @@ type ReportError = { const addNewMessageWithText = new Set([WRITE_COMMANDS.ADD_COMMENT, WRITE_COMMANDS.ADD_TEXT_AND_ATTACHMENT]); let conciergeReportID: string | undefined; -let currentUserAccountID = -1; -let currentUserEmail: string | undefined; +let deprecatedCurrentUserAccountID = -1; +/** @deprecated This value is deprecated and will be removed soon after migration. Use the email from useCurrentUserPersonalDetails hook instead. */ +let deprecatedCurrentUserLogin: string | undefined; Onyx.connect({ key: ONYXKEYS.SESSION, @@ -284,8 +285,9 @@ Onyx.connect({ conciergeReportID = undefined; return; } - currentUserEmail = value.email; - currentUserAccountID = value.accountID; + // eslint-disable-next-line @typescript-eslint/no-deprecated + deprecatedCurrentUserLogin = value.email; + deprecatedCurrentUserAccountID = value.accountID; }, }); @@ -424,7 +426,7 @@ function subscribeToReportTypingEvents(reportID: string) { } // Don't show the typing indicator if the user is typing on another platform - if (Number(accountIDOrLogin) === currentUserAccountID) { + if (Number(accountIDOrLogin) === deprecatedCurrentUserAccountID) { return; } @@ -473,7 +475,7 @@ function subscribeToReportLeavingEvents(reportID: string | undefined) { return; } - if (Number(accountIDOrLogin) !== currentUserAccountID) { + if (Number(accountIDOrLogin) !== deprecatedCurrentUserAccountID) { return; } @@ -530,7 +532,7 @@ function notifyNewAction(reportID: string | undefined, accountID: number | undef if (!actionSubscriber) { return; } - const isFromCurrentUser = accountID === currentUserAccountID; + const isFromCurrentUser = accountID === deprecatedCurrentUserAccountID; actionSubscriber.callback(isFromCurrentUser, reportAction); } @@ -586,14 +588,14 @@ function addActions(report: OnyxEntry, notifyReportID: string, ancestors lastVisibleActionCreated: lastAction?.created, lastMessageText: lastCommentText, lastMessageHtml: lastCommentText, - lastActorAccountID: currentUserAccountID, + lastActorAccountID: deprecatedCurrentUserAccountID, lastReadTime: currentTime, }; const shouldUpdateNotificationPreference = !isEmptyObject(report) && isHiddenForCurrentUser(report); if (shouldUpdateNotificationPreference) { optimisticReport.participants = { - [currentUserAccountID]: {notificationPreference: getDefaultNotificationPreferenceForReport(report)}, + [deprecatedCurrentUserAccountID]: {notificationPreference: getDefaultNotificationPreferenceForReport(report)}, }; } @@ -690,13 +692,13 @@ function addActions(report: OnyxEntry, notifyReportID: string, ancestors ]; // Update the timezone if it's been 5 minutes from the last time the user added a comment - if (DateUtils.canUpdateTimezone() && currentUserAccountID) { + if (DateUtils.canUpdateTimezone() && deprecatedCurrentUserAccountID) { const timezone = DateUtils.getCurrentTimezone(timezoneParam); parameters.timezone = JSON.stringify(timezone); optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.PERSONAL_DETAILS_LIST, - value: {[currentUserAccountID]: {timezone}}, + value: {[deprecatedCurrentUserAccountID]: {timezone}}, }); DateUtils.setTimezoneUpdated(); } @@ -891,7 +893,7 @@ function updatePolicyRoomAvatar(reportID: string, file?: File | CustomRNImageMan onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { - lastActorAccountID: currentUserAccountID, + lastActorAccountID: deprecatedCurrentUserAccountID, lastVisibleActionCreated: optimisticAction.created, lastMessageText: (optimisticAction.message as Message[]).at(0)?.text, }, @@ -1071,8 +1073,9 @@ function openReport( // Use optimisticSelfDMReport if provided (when selfDM exists but wasn't in allReports) const parentReport = transactionParentReportID === optimisticSelfDMReport?.reportID ? optimisticSelfDMReport : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionParentReportID}`]; - const submitterAccountID = parentReport?.ownerAccountID ?? currentUserAccountID; - const submitterEmail = PersonalDetailsUtils.getLoginsByAccountIDs([submitterAccountID]).at(0) ?? currentUserEmail ?? ''; + const submitterAccountID = parentReport?.ownerAccountID ?? deprecatedCurrentUserAccountID; + // eslint-disable-next-line @typescript-eslint/no-deprecated + const submitterEmail = PersonalDetailsUtils.getLoginsByAccountIDs([submitterAccountID]).at(0) ?? deprecatedCurrentUserLogin ?? ''; const submitterPersonalDetails = PersonalDetailsUtils.getPersonalDetailByEmail(submitterEmail); const optimisticIOUAction = buildOptimisticIOUReportAction({ @@ -1199,7 +1202,8 @@ function openReport( const isGroupChat = isGroupChatReportUtils(newReportObject); if (isGroupChat) { parameters.chatType = CONST.REPORT.CHAT_TYPE.GROUP; - parameters.groupChatAdminLogins = currentUserEmail; + // eslint-disable-next-line @typescript-eslint/no-deprecated + parameters.groupChatAdminLogins = deprecatedCurrentUserLogin; parameters.optimisticAccountIDList = Object.keys(newReportObject?.participants ?? {}).join(','); parameters.reportName = newReportObject?.reportName ?? ''; @@ -1394,7 +1398,7 @@ function prepareOnyxDataForCleanUpOptimisticParticipants( */ function getOptimisticChatReport(accountID: number): OptimisticChatReport { return buildOptimisticChatReport({ - participantList: [accountID, currentUserAccountID], + participantList: [accountID, deprecatedCurrentUserAccountID], notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, }); } @@ -1441,7 +1445,8 @@ function createTransactionThreadReport( openReport( optimisticTransactionThreadReportID, undefined, - currentUserEmail ? [currentUserEmail] : [], + // eslint-disable-next-line @typescript-eslint/no-deprecated + deprecatedCurrentUserLogin ? [deprecatedCurrentUserLogin] : [], optimisticTransactionThread, iouReportAction?.reportActionID, false, @@ -1478,16 +1483,16 @@ function navigateToAndOpenReport( // If we are not creating a new Group Chat then we are creating a 1:1 DM and will look for an existing chat if (!isGroupChat) { - chat = getChatByParticipants([...participantAccountIDs, currentUserAccountID]); + chat = getChatByParticipants([...participantAccountIDs, deprecatedCurrentUserAccountID]); } if (isEmptyObject(chat)) { if (isGroupChat) { - // If we are creating a group chat then participantAccountIDs is expected to contain currentUserAccountID + // If we are creating a group chat then participantAccountIDs is expected to contain deprecatedCurrentUserAccountID newChat = buildOptimisticGroupChatReport(participantAccountIDs, reportName ?? '', avatarUri ?? '', optimisticReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); } else { newChat = buildOptimisticChatReport({ - participantList: [...participantAccountIDs, currentUserAccountID], + participantList: [...participantAccountIDs, deprecatedCurrentUserAccountID], notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, }); } @@ -1522,10 +1527,10 @@ function navigateToAndOpenReport( */ function navigateToAndOpenReportWithAccountIDs(participantAccountIDs: number[]) { let newChat: OptimisticChatReport | undefined; - const chat = getChatByParticipants([...participantAccountIDs, currentUserAccountID]); + const chat = getChatByParticipants([...participantAccountIDs, deprecatedCurrentUserAccountID]); if (!chat) { newChat = buildOptimisticChatReport({ - participantList: [...participantAccountIDs, currentUserAccountID], + participantList: [...participantAccountIDs, deprecatedCurrentUserAccountID], }); // We want to pass newChat here because if anything is passed in that param (even an existing chat), we will try to create a chat on the server openReport(newChat?.reportID, '', [], newChat, '0', false, participantAccountIDs); @@ -1547,7 +1552,7 @@ function navigateToAndOpenChildReport(childReportID: string | undefined, parentR if (childReport?.reportID) { Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID, undefined, undefined, Navigation.getActiveRoute())); } else { - const participantAccountIDs = [...new Set([currentUserAccountID, Number(parentReportAction.actorAccountID)])]; + const participantAccountIDs = [...new Set([deprecatedCurrentUserAccountID, Number(parentReportAction.actorAccountID)])]; const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`]; // Threads from DMs and selfDMs don't have a chatType. All other threads inherit the chatType from their parent const childReportChatType = parentReport && isSelfDM(parentReport) ? undefined : parentReport?.chatType; @@ -1819,7 +1824,7 @@ function markCommentAsUnread(reportID: string | undefined, reportAction: ReportA const latestReportActionFromOtherUsers = Object.values(reportActions ?? {}).reduce((latest: ReportAction | null, current: ReportAction) => { if ( !ReportActionsUtils.isDeletedAction(current) && - current.actorAccountID !== currentUserAccountID && + current.actorAccountID !== deprecatedCurrentUserAccountID && (!latest || current.created > latest.created) && // Whisper action doesn't affect lastVisibleActionCreated, so skip whisper action except actionable mention whisper (!ReportActionsUtils.isWhisperAction(current) || current.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER) @@ -1907,7 +1912,7 @@ function saveReportDraftComment(reportID: string, comment: string | null, callba function broadcastUserIsTyping(reportID: string) { const privateReportChannelName = getReportChannelName(reportID); const typingStatus: UserIsTypingEvent = { - [currentUserAccountID]: true, + [deprecatedCurrentUserAccountID]: true, }; Pusher.sendEvent(privateReportChannelName, Pusher.TYPE.USER_IS_TYPING, typingStatus); } @@ -1916,7 +1921,7 @@ function broadcastUserIsTyping(reportID: string) { function broadcastUserIsLeavingRoom(reportID: string) { const privateReportChannelName = getReportChannelName(reportID); const leavingStatus: UserIsLeavingRoomEvent = { - [currentUserAccountID]: true, + [deprecatedCurrentUserAccountID]: true, }; Pusher.sendEvent(privateReportChannelName, Pusher.TYPE.USER_IS_LEAVING_ROOM, leavingStatus); } @@ -2140,12 +2145,12 @@ function removeLinksFromHtml(html: string, links: string[]): string { * @param originalCommentMarkdown original markdown of the comment before editing. * @param videoAttributeCache cache of video attributes ([videoSource]: videoAttributes) */ -function handleUserDeletedLinksInHtml(newCommentText: string, originalCommentMarkdown: string, videoAttributeCache?: Record): string { +function handleUserDeletedLinksInHtml(newCommentText: string, originalCommentMarkdown: string, currentUserLogin: string, videoAttributeCache?: Record): string { if (newCommentText.length > CONST.MAX_MARKUP_LENGTH) { return newCommentText; } - const userEmailDomain = isEmailPublicDomain(currentUserEmail ?? '') ? '' : Str.extractEmailDomain(currentUserEmail ?? ''); + const userEmailDomain = isEmailPublicDomain(currentUserLogin) ? '' : Str.extractEmailDomain(currentUserLogin); const allPersonalDetailLogins = Object.values(allPersonalDetails ?? {}).map((personalDetail) => personalDetail?.login ?? ''); const htmlForNewComment = getParsedMessageWithShortMentions({ @@ -2169,7 +2174,7 @@ function editReportComment( textForNewComment: string, isOriginalReportArchived: boolean | undefined, isOriginalParentReportArchived: boolean | undefined, - currentEmail: string, + currentUserLogin: string, videoAttributeCache?: Record, ) { const originalReportID = originalReport?.reportID; @@ -2189,7 +2194,7 @@ function editReportComment( if (originalCommentMarkdown === textForNewComment) { return; } - const htmlForNewComment = handleUserDeletedLinksInHtml(textForNewComment, originalCommentMarkdown, videoAttributeCache); + const htmlForNewComment = handleUserDeletedLinksInHtml(textForNewComment, originalCommentMarkdown, currentUserLogin, videoAttributeCache); const reportComment = Parser.htmlToText(htmlForNewComment); @@ -2203,7 +2208,7 @@ function editReportComment( // Delete the comment if it's empty if (!htmlForNewComment) { - deleteReportComment(originalReportID, originalReportAction, ancestors, isOriginalReportArchived, isOriginalParentReportArchived, currentEmail); + deleteReportComment(originalReportID, originalReportAction, ancestors, isOriginalReportArchived, isOriginalParentReportArchived, currentUserLogin); return; } @@ -2329,7 +2334,7 @@ function updateNotificationPreference( key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { participants: { - [currentUserAccountID]: { + [deprecatedCurrentUserAccountID]: { notificationPreference: newValue, }, }, @@ -2343,7 +2348,7 @@ function updateNotificationPreference( key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { participants: { - [currentUserAccountID]: { + [deprecatedCurrentUserAccountID]: { notificationPreference: previousValue, }, }, @@ -2418,7 +2423,7 @@ function toggleSubscribeToChildReport( updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, parentReportID, parentReportActionID); } } else { - const participantAccountIDs = [...new Set([currentUserAccountID, Number(parentReportAction?.actorAccountID)])]; + const participantAccountIDs = [...new Set([deprecatedCurrentUserAccountID, Number(parentReportAction?.actorAccountID)])]; const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`]; const newChat = buildOptimisticChatReport({ participantList: participantAccountIDs, @@ -2753,7 +2758,7 @@ function updateDescription(reportID: string, currentDescription: string, newMark value: { description: parsedDescription, pendingFields: {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, - lastActorAccountID: currentUserAccountID, + lastActorAccountID: deprecatedCurrentUserAccountID, lastVisibleActionCreated: optimisticDescriptionUpdatedReportAction.created, lastMessageText: (optimisticDescriptionUpdatedReportAction?.message as Message[])?.at(0)?.text, }, @@ -3419,7 +3424,7 @@ function shouldShowReportActionNotification(reportID: string, action: ReportActi } // If this comment is from the current user we don't want to parrot whatever they wrote back to them. - if (action && action.actorAccountID === currentUserAccountID) { + if (action && action.actorAccountID === deprecatedCurrentUserAccountID) { Log.info(`${tag} No notification because comment is from the currently logged in user`); return false; } @@ -3507,7 +3512,7 @@ function addEmojiReaction(reportID: string, reportActionID: string, emoji: Emoji createdAt, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, users: { - [currentUserAccountID]: { + [deprecatedCurrentUserAccountID]: { skinTones: { [skinTone]: createdAt, }, @@ -3565,7 +3570,7 @@ function removeEmojiReaction(reportID: string, reportActionID: string, emoji: Em value: { [emoji.name]: { users: { - [currentUserAccountID]: null, + [deprecatedCurrentUserAccountID]: null, }, }, }, @@ -3613,7 +3618,7 @@ function toggleEmojiReaction( // Only use skin tone if emoji supports it const skinTone = emoji.types === undefined ? CONST.EMOJI_DEFAULT_SKIN_TONE : paramSkinTone; - if (existingReactionObject && EmojiUtils.hasAccountIDEmojiReacted(currentUserAccountID, existingReactionObject.users, ignoreSkinToneOnCompare ? undefined : skinTone)) { + if (existingReactionObject && EmojiUtils.hasAccountIDEmojiReacted(deprecatedCurrentUserAccountID, existingReactionObject.users, ignoreSkinToneOnCompare ? undefined : skinTone)) { removeEmojiReaction(originalReportID, reportAction.reportActionID, emoji); return; } @@ -3626,11 +3631,7 @@ function doneCheckingPublicRoom() { } function getCurrentUserAccountID(): number { - return currentUserAccountID; -} - -function getCurrentUserEmail(): string | undefined { - return currentUserEmail; + return deprecatedCurrentUserAccountID; } function navigateToMostRecentReport(currentReport: OnyxEntry) { @@ -3687,7 +3688,7 @@ function leaveGroupChat(reportID: string, shouldClearQuickAction: boolean) { stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.CLOSED, participants: { - [currentUserAccountID]: { + [deprecatedCurrentUserAccountID]: { notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, }, }, @@ -3750,7 +3751,7 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal isWorkspaceMemberLeavingWorkspaceRoom || isChatThread ? { participants: { - [currentUserAccountID]: { + [deprecatedCurrentUserAccountID]: { notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, }, }, @@ -3760,7 +3761,7 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.CLOSED, participants: { - [currentUserAccountID]: { + [deprecatedCurrentUserAccountID]: { notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, }, }, @@ -3775,7 +3776,7 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { participants: { - [currentUserAccountID]: { + [deprecatedCurrentUserAccountID]: { notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, }, }, @@ -3815,7 +3816,7 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, value: { [report.parentReportActionID]: { - childReportNotificationPreference: report?.participants?.[currentUserAccountID]?.notificationPreference ?? getDefaultNotificationPreferenceForReport(report), + childReportNotificationPreference: report?.participants?.[deprecatedCurrentUserAccountID]?.notificationPreference ?? getDefaultNotificationPreferenceForReport(report), }, }, }); @@ -4897,12 +4898,12 @@ function exportReportToPDF({reportID}: ExportReportPDFParams) { API.write(WRITE_COMMANDS.EXPORT_REPORT_TO_PDF, params, {optimisticData, failureData}); } -function downloadReportPDF(fileName: string, reportName: string, translate: LocalizedTranslate) { +function downloadReportPDF(fileName: string, reportName: string, translate: LocalizedTranslate, currentUserLogin: string) { const baseURL = addTrailingForwardSlash(getOldDotURLFromEnvironment(environment)); const downloadFileName = `${reportName}.pdf`; setDownload(fileName, true); const pdfURL = `${baseURL}secure?secureType=pdfreport&filename=${encodeURIComponent(fileName)}&downloadName=${encodeURIComponent(downloadFileName)}&email=${encodeURIComponent( - currentUserEmail ?? '', + currentUserLogin, )}`; fileDownload(translate, addEncryptedAuthTokenToURL(pdfURL, true), downloadFileName, '', Browser.isMobileSafari()).then(() => setDownload(fileName, false)); } @@ -5850,7 +5851,7 @@ function buildOptimisticChangePolicyData( if (newStatusNum === CONST.REPORT.STATUS_NUM.OPEN) { shouldSetOutstandingChildRequest = isCurrentUserSubmitter(report); } else if (isProcessingReport(report)) { - shouldSetOutstandingChildRequest = report.managerID === currentUserAccountID; + shouldSetOutstandingChildRequest = report.managerID === deprecatedCurrentUserAccountID; } } @@ -6405,7 +6406,6 @@ export { exportToIntegration, flagComment, getCurrentUserAccountID, - getCurrentUserEmail, getMostRecentReportID, getNewerActions, getOlderActions, diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx index e8fdc8231216..5a7bf2a675b0 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx @@ -12,6 +12,7 @@ import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -51,6 +52,7 @@ function PrivateNotesEditPageInternal({route, report, accountID, privateNoteDraf const styles = useThemeStyles(); const {translate} = useLocalize(); const personalDetailsList = usePersonalDetails(); + const {email} = useCurrentUserPersonalDetails(); // We need to edit the note in markdown format, but display it in HTML format const [privateNote, setPrivateNote] = useState(() => privateNoteDraft || Parser.htmlToMarkdown(report?.privateNotes?.[Number(route.params.accountID)]?.note ?? '').trim()); @@ -91,7 +93,7 @@ function PrivateNotesEditPageInternal({route, report, accountID, privateNoteDraf const originalNote = report?.privateNotes?.[Number(route.params.accountID)]?.note ?? ''; let editedNote = ''; if (privateNote.trim() !== originalNote.trim()) { - editedNote = handleUserDeletedLinksInHtml(privateNote.trim(), Parser.htmlToMarkdown(originalNote).trim()); + editedNote = handleUserDeletedLinksInHtml(privateNote.trim(), Parser.htmlToMarkdown(originalNote).trim(), email ?? '', undefined); updatePrivateNotes(report.reportID, Number(route.params.accountID), editedNote); } diff --git a/src/pages/iou/SplitExpenseCreateDateRagePage.tsx b/src/pages/iou/SplitExpenseCreateDateRagePage.tsx index 7cb001e41d9a..eb4fe5658d6d 100644 --- a/src/pages/iou/SplitExpenseCreateDateRagePage.tsx +++ b/src/pages/iou/SplitExpenseCreateDateRagePage.tsx @@ -9,6 +9,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import {useSearchContext} from '@components/Search/SearchContext'; import useAllTransactions from '@hooks/useAllTransactions'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; @@ -51,13 +52,14 @@ function SplitExpenseCreateDateRagePage({route}: SplitExpenseCreateDateRagePageP const currentPolicy = Object.keys(policy?.employeeList ?? {}).length ? policy : currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(currentReport?.policyID)}`]; + const {email} = useCurrentUserPersonalDetails(); const updateDate = (value: FormOnyxValues) => { resetSplitExpensesByDateRange(transaction, value[INPUT_IDS.START_DATE], value[INPUT_IDS.END_DATE]); Navigation.goBack(backTo); }; - const isSplitAvailable = report && transaction && isSplitAction(currentReport, [transaction], originalTransaction, currentPolicy); + const isSplitAvailable = report && transaction && isSplitAction(currentReport, [transaction], originalTransaction, email ?? '', currentPolicy); const validate = (values: FormOnyxValues) => { const errors: FormInputErrors = {}; diff --git a/src/pages/iou/SplitExpenseEditPage.tsx b/src/pages/iou/SplitExpenseEditPage.tsx index 96ed74401c3d..cfd3ecec0775 100644 --- a/src/pages/iou/SplitExpenseEditPage.tsx +++ b/src/pages/iou/SplitExpenseEditPage.tsx @@ -9,6 +9,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import {useSearchContext} from '@components/Search/SearchContext'; import useAllTransactions from '@hooks/useAllTransactions'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; @@ -71,6 +72,7 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) { const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${currentReport?.policyID}`, {canBeMissing: false}); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${currentReport?.policyID}`, {canBeMissing: false}); + const {email} = useCurrentUserPersonalDetails(); const fetchData = useCallback(() => { if (!policyCategories) { @@ -104,7 +106,7 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) { const transactionTag = getTag(splitExpenseDraftTransaction); const policyTagLists = useMemo(() => getTagLists(policyTags), [policyTags]); - const isSplitAvailable = report && transaction && isSplitAction(currentReport, [transaction], originalTransaction, currentPolicy); + const isSplitAvailable = report && transaction && isSplitAction(currentReport, [transaction], originalTransaction, email ?? '', currentPolicy); const isCategoryRequired = !!currentPolicy?.requiresCategory; const reportName = computeReportName(currentReport); diff --git a/src/pages/iou/SplitExpensePage.tsx b/src/pages/iou/SplitExpensePage.tsx index 450398995790..b17a9c1e1d54 100644 --- a/src/pages/iou/SplitExpensePage.tsx +++ b/src/pages/iou/SplitExpensePage.tsx @@ -81,6 +81,7 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const searchHash = searchContext?.currentSearchHash ?? CONST.DEFAULT_NUMBER_ID; const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchHash}`, {canBeMissing: true}); const allTransactions = useAllTransactions(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`]; const originalTransaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transaction?.comment?.originalTransactionID)}`]; @@ -97,7 +98,7 @@ function SplitExpensePage({route}: SplitExpensePageProps) { ? policy : currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(currentReport?.policyID)}`]; - const isSplitAvailable = report && transaction && isSplitAction(currentReport, [transaction], originalTransaction, currentPolicy); + const isSplitAvailable = report && transaction && isSplitAction(currentReport, [transaction], originalTransaction, currentUserPersonalDetails.email ?? '', currentPolicy); const transactionDetails = useMemo>(() => getTransactionDetails(transaction) ?? {}, [transaction]); const transactionDetailsAmount = transactionDetails?.amount ?? 0; @@ -117,7 +118,6 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const childTransactions = useMemo(() => getChildTransactions(allTransactions, allReports, transactionID), [allReports, allTransactions, transactionID]); const splitFieldDataFromChildTransactions = useMemo(() => childTransactions.map((currentTransaction) => initSplitExpenseItemData(currentTransaction)), [childTransactions]); const splitFieldDataFromOriginalTransaction = useMemo(() => initSplitExpenseItemData(transaction), [transaction]); - const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); const icons = useMemoizedLazyExpensifyIcons(['ArrowsLeftRight', 'Plus'] as const); diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 2071112bc4a2..d71aaf0c6913 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -541,7 +541,7 @@ describe('actions/Report', () => { return setPromise; }); - it('Should properly update comment with links', () => { + it('Should properly update comment with links', async () => { /* This tests a variety of scenarios when a user edits a comment. * We should generate a link when editing a message unless the link was * already in the comment and the user deleted it on purpose. @@ -549,11 +549,13 @@ describe('actions/Report', () => { global.fetch = TestHelper.getGlobalFetchMock(); + const TEST_USER_LOGIN = 'test@expensify.com'; + // User edits comment to add link // We should generate link let originalCommentMarkdown = 'Original Comment'; let afterEditCommentText = 'Original Comment www.google.com'; - let newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown); + let newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown, TEST_USER_LOGIN); let expectedOutput = 'Original Comment www.google.com'; expect(newCommentHTML).toBe(expectedOutput); @@ -561,7 +563,7 @@ describe('actions/Report', () => { // We should not generate link originalCommentMarkdown = 'Comment [www.google.com](https://www.google.com)'; afterEditCommentText = 'Comment www.google.com'; - newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown); + newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown, TEST_USER_LOGIN); expectedOutput = 'Comment www.google.com'; expect(newCommentHTML).toBe(expectedOutput); @@ -569,7 +571,7 @@ describe('actions/Report', () => { // We should not generate link originalCommentMarkdown = 'Comment [www.google.com](https://www.google.com)'; afterEditCommentText = 'Comment [www.google.com]'; - newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown); + newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown, TEST_USER_LOGIN); expectedOutput = 'Comment [www.google.com]'; expect(newCommentHTML).toBe(expectedOutput); @@ -577,7 +579,7 @@ describe('actions/Report', () => { // We should generate both links originalCommentMarkdown = 'Comment'; afterEditCommentText = 'Comment www.google.com www.facebook.com'; - newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown); + newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown, TEST_USER_LOGIN); expectedOutput = 'Comment www.google.com ' + 'www.facebook.com'; @@ -587,7 +589,7 @@ describe('actions/Report', () => { // Should not generate link again for the deleted one originalCommentMarkdown = 'Comment [www.google.com](https://www.google.com) [www.facebook.com](https://www.facebook.com)'; afterEditCommentText = 'Comment www.google.com [www.facebook.com](https://www.facebook.com)'; - newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown); + newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown, TEST_USER_LOGIN); expectedOutput = 'Comment www.google.com www.facebook.com'; expect(newCommentHTML).toBe(expectedOutput); @@ -595,7 +597,7 @@ describe('actions/Report', () => { // We should generate link originalCommentMarkdown = 'Comment'; afterEditCommentText = 'https://www.facebook.com/hashtag/__main/?__eep__=6'; - newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown); + newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown, TEST_USER_LOGIN); expectedOutput = 'https://www.facebook.com/hashtag/__main/?__eep__=6'; expect(newCommentHTML).toBe(expectedOutput); @@ -603,7 +605,7 @@ describe('actions/Report', () => { // We should not generate link originalCommentMarkdown = '[https://www.facebook.com/hashtag/__main/?__eep__=6](https://www.facebook.com/hashtag/__main/?__eep__=6)'; afterEditCommentText = 'https://www.facebook.com/hashtag/__main/?__eep__=6'; - newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown); + newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown, TEST_USER_LOGIN); expectedOutput = 'https://www.facebook.com/hashtag/__main/?__eep__=6'; expect(newCommentHTML).toBe(expectedOutput); @@ -611,7 +613,7 @@ describe('actions/Report', () => { // We should generate link originalCommentMarkdown = 'Comment'; afterEditCommentText = 'http://example.com/foo/*/bar/*/test.txt'; - newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown); + newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown, TEST_USER_LOGIN); expectedOutput = 'http://example.com/foo/*/bar/*/test.txt'; expect(newCommentHTML).toBe(expectedOutput); @@ -619,9 +621,25 @@ describe('actions/Report', () => { // We should not generate link originalCommentMarkdown = '[http://example.com/foo/*/bar/*/test.txt](http://example.com/foo/*/bar/*/test.txt)'; afterEditCommentText = 'http://example.com/foo/*/bar/*/test.txt'; - newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown); + newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown, TEST_USER_LOGIN); expectedOutput = 'http://example.com/foo/*/bar/*/test.txt'; expect(newCommentHTML).toBe(expectedOutput); + + // User edits comment to add mention + // We should generate mention-user tag + const privateDomainAccount = { + accountID: 2, + login: 'user@expensify.com', + email: 'user@expensify.com', + }; + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { + [privateDomainAccount.accountID]: privateDomainAccount, + }); + originalCommentMarkdown = 'Comment'; + afterEditCommentText = 'Comment @user'; + newCommentHTML = Report.handleUserDeletedLinksInHtml(afterEditCommentText, originalCommentMarkdown, TEST_USER_LOGIN); + expectedOutput = 'Comment @user@expensify.com'; + expect(newCommentHTML).toBe(expectedOutput); }); it('should show a notification for report action updates with shouldNotify', () => { @@ -1746,6 +1764,69 @@ describe('actions/Report', () => { TestHelper.expectAPICommandToHaveBeenCalled(WRITE_COMMANDS.UPDATE_COMMENT, 1); }); + it('should convert short mentions to full format when editing comments', async () => { + global.fetch = TestHelper.getGlobalFetchMock(); + + const TEST_USER_LOGIN = 'alice@expensify.com'; + const TEST_USER_ACCOUNT_ID = 1; + const MENTIONED_USER_ACCOUNT_ID = 2; + const MENTIONED_USER_LOGIN = 'bob@expensify.com'; + const REPORT_ID = '1'; + const REPORT: OnyxTypes.Report = createRandomReport(1, undefined); + + const TEN_MINUTES_AGO = subMinutes(new Date(), 10); + const created = format(addSeconds(TEN_MINUTES_AGO, 10), CONST.DATE.FNS_DB_FORMAT_STRING); + + // Set up personal details with private domain users + await TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { + [MENTIONED_USER_ACCOUNT_ID]: { + accountID: MENTIONED_USER_ACCOUNT_ID, + login: MENTIONED_USER_LOGIN, + displayName: 'Bob', + }, + }); + + await Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}); + + Report.addComment(REPORT, REPORT.reportID, [], 'Initial comment', CONST.DEFAULT_TIME_ZONE); + + // Get the reportActionID to edit and delete the comment + const newComment = PersistedRequests.getAll().at(0); + const reportActionID = newComment?.data?.reportActionID as string | undefined; + const newReportAction = TestHelper.buildTestReportComment(created, TEST_USER_ACCOUNT_ID, reportActionID); + const originalReport = { + reportID: REPORT_ID, + }; + + const {result: ancestors} = renderHook(() => useAncestors(originalReport)); + + // Edit the comment to add a short mention + Report.editReportComment(originalReport, newReportAction, ancestors.current, 'Initial comment with @bob', undefined, undefined, TEST_USER_LOGIN); + + await waitForBatchedUpdates(); + + // Verify the mention was converted in the edited comment + await new Promise((resolve) => { + const connection = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, + callback: (reportActions) => { + Onyx.disconnect(connection); + + const reportAction = reportActionID ? reportActions?.[reportActionID] : null; + const message = reportAction?.message; + const editedMessage = Array.isArray(message) && message.length > 0 ? message.at(0)?.html : undefined; + // Verify the mention was converted to full mention with domain + expect(editedMessage).toContain('@bob@expensify.com'); + expect(editedMessage).toBe('Initial comment with @bob@expensify.com'); + resolve(); + }, + }); + }); + await Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); + await waitForBatchedUpdates(); + }); + it('it should only send the last sequential UpdateComment request to BE with currentUserEmail', async () => { global.fetch = TestHelper.getGlobalFetchMock(); await Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}); diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 22961bd59d36..6f714f9583b9 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -1811,6 +1811,42 @@ describe('SearchUIUtils', () => { expect(action).toEqual(CONST.SEARCH.ACTION_TYPES.PAY); }); + test('Should return EXPORT_TO_ACCOUNTING action when report is approved and policy has verified accounting integration', () => { + const exportReportID = 'report_export'; + const localSearchResults = { + ...searchResults.data, + [`policy_${policyID}`]: { + ...searchResults.data[`policy_${policyID}`], + role: CONST.POLICY.ROLE.ADMIN, + exporter: adminEmail, + connections: { + [CONST.POLICY.CONNECTIONS.NAME.NETSUITE]: { + verified: true, + lastSync: { + errorDate: '', + errorMessage: '', + isAuthenticationError: false, + isConnected: true, + isSuccessful: true, + source: 'NEWEXPENSIFY', + successfulDate: '', + }, + }, + } as Connections, + }, + [`report_${exportReportID}`]: { + ...searchResults.data[`report_${reportID2}`], + reportID: exportReportID, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + statusNum: CONST.REPORT.STATUS_NUM.APPROVED, + }, + }; + + const actions = SearchUIUtils.getActions(localSearchResults, {}, `report_${exportReportID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminEmail, []); + + expect(actions).toContain(CONST.SEARCH.ACTION_TYPES.EXPORT_TO_ACCOUNTING); + }); + test('Should return `Submit` action when report has DEW_SUBMIT_FAILED action and is still OPEN', async () => { const dewReportID = '999'; const dewTransactionID = '9999';